feat(genstart): husk ved hvert gem hvilke maskiner Courseplay kører (uden AD) og start netop dem igen efter indlæsning fra nærmeste waypoint via CP's HUD-start — også CP-jobs uden markrolle (v1.45.0.0)
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
-- AD CP Resume
|
||||
-- Spillet stopper alle arbejdere ved gem, og Courseplay genstarter dem ikke (AutoDrive gør selv). Ved hvert gem
|
||||
-- skrives de maskiner hvor CP kører (uden AD) til ADSmartPickup_cpresume.xml; efter indlæsning startes netop
|
||||
-- dem igen fra nærmeste waypoint med CP's egen HUD-start (samme job som spilleren sidst valgte i HUD'en).
|
||||
|
||||
ADCpResume = {}
|
||||
ADCpResume.FILE_NAME = "ADSmartPickup_cpresume.xml"
|
||||
ADCpResume.ROOT = "ADSmartPickupCpResume"
|
||||
ADCpResume.LOG_PREFIX = "ADSmartPickup: CP-genstart: "
|
||||
ADCpResume.pending = nil -- {[vehicleId] = true} fra seneste gem; nil = ingen fil (gammel regel bruges)
|
||||
|
||||
-- ---- ren logik ----
|
||||
|
||||
-- vehicles = {{id, cpActive, adActive}} -> id'er der skal huskes
|
||||
function ADCpResume.activeIds(vehicles)
|
||||
local ids = {}
|
||||
for _, vehicle in ipairs(vehicles or {}) do
|
||||
if vehicle.cpActive and not vehicle.adActive then table.insert(ids, vehicle.id) end
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
function ADCpResume.fromRecords(ids)
|
||||
local set = {}
|
||||
for _, id in ipairs(ids or {}) do
|
||||
if type(id) == "string" and id ~= "" then set[id] = true end
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
-- vehicles = {{id, aiActive, controlled}} i fast rækkefølge -> id'er der skal startes (nil uden gemt liste)
|
||||
function ADCpResume.toStart(pending, vehicles)
|
||||
if pending == nil then return nil end
|
||||
local ids = {}
|
||||
for _, vehicle in ipairs(vehicles or {}) do
|
||||
if pending[vehicle.id] and not vehicle.aiActive and not vehicle.controlled then table.insert(ids, vehicle.id) end
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
-- ---- spil ----
|
||||
|
||||
local function vehicleState(adEnv, vehicle)
|
||||
local ad = vehicle.ad
|
||||
return {
|
||||
id = ADRunsController.getVehicleId(vehicle),
|
||||
cpActive = vehicle.getIsCpActive ~= nil and vehicle:getIsCpActive() == true,
|
||||
adActive = ad ~= nil and ad.stateModule ~= nil and ad.stateModule:isActive(),
|
||||
aiActive = vehicle.getIsAIActive ~= nil and vehicle:getIsAIActive() == true,
|
||||
controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() == true,
|
||||
}
|
||||
end
|
||||
|
||||
local function farmVehicles(adEnv)
|
||||
local list, byId = {}, {}
|
||||
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
|
||||
local ok, state = pcall(vehicleState, adEnv, vehicle)
|
||||
if ok and state.id ~= nil then
|
||||
table.insert(list, state)
|
||||
byId[state.id] = vehicle
|
||||
end
|
||||
end
|
||||
return list, byId
|
||||
end
|
||||
|
||||
-- Starter CP fra nærmeste waypoint (HUD-startpunktet sættes som spillerens "start ved nærmeste"-tast).
|
||||
function ADCpResume.startNearest(vehicle)
|
||||
if vehicle.cpStartStopDriver == nil or (vehicle.getIsAIActive ~= nil and vehicle:getIsAIActive()) then return false end
|
||||
if vehicle.getCpStartingPointSetting ~= nil and CpFieldWorkJobParameters ~= nil and vehicle.hasCpCourse ~= nil and vehicle:hasCpCourse() then
|
||||
local setting = vehicle:getCpStartingPointSetting()
|
||||
if setting ~= nil then setting:setValue(CpFieldWorkJobParameters.START_AT_NEAREST_POINT) end
|
||||
end
|
||||
vehicle:cpStartStopDriver(true)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Id'er der skal startes nu (nil = ingen gemt liste). Listen bruges kun én gang.
|
||||
function ADCpResume.takeToStart(adEnv)
|
||||
local list, byId = farmVehicles(adEnv)
|
||||
local ids = ADCpResume.toStart(ADCpResume.pending, list)
|
||||
ADCpResume.pending = nil
|
||||
return ids, byId
|
||||
end
|
||||
|
||||
local function getPath()
|
||||
local path = ADRunsStorage ~= nil and ADRunsStorage.getPath() or nil
|
||||
if path == nil then return nil end
|
||||
return (path:gsub(ADRunsStorage.FILE_NAME .. "$", ADCpResume.FILE_NAME))
|
||||
end
|
||||
|
||||
function ADCpResume.save(adEnv)
|
||||
local path = getPath()
|
||||
if path == nil or adEnv == nil then return false end
|
||||
local ids = ADCpResume.activeIds((farmVehicles(adEnv)))
|
||||
local xmlFile = XMLFile.create("ADSmartPickupCpResume", path, ADCpResume.ROOT)
|
||||
if xmlFile == nil then return false end
|
||||
for index, id in ipairs(ids) do
|
||||
xmlFile:setString(string.format("%s.vehicle(%d)#id", ADCpResume.ROOT, index - 1), id)
|
||||
end
|
||||
xmlFile:save()
|
||||
xmlFile:delete()
|
||||
Logging.info(ADCpResume.LOG_PREFIX .. "gemt: %d maskine(r) kører Courseplay", #ids)
|
||||
return true
|
||||
end
|
||||
|
||||
function ADCpResume.load()
|
||||
ADCpResume.pending = nil
|
||||
local path = getPath()
|
||||
if path == nil or not fileExists(path) then return end
|
||||
local xmlFile = XMLFile.load("ADSmartPickupCpResume", path)
|
||||
if xmlFile == nil then return end
|
||||
local ids = {}
|
||||
xmlFile:iterate(ADCpResume.ROOT .. ".vehicle", function(_, key) table.insert(ids, xmlFile:getString(key .. "#id")) end)
|
||||
xmlFile:delete()
|
||||
ADCpResume.pending = ADCpResume.fromRecords(ids)
|
||||
Logging.info(ADCpResume.LOG_PREFIX .. "%d maskine(r) kørte Courseplay ved sidste gem", #ids)
|
||||
end
|
||||
@@ -933,9 +933,15 @@ end
|
||||
|
||||
-- Efter genindlæsning: spillet stopper alle arbejdere ved gem, og Courseplay genstarter dem ikke (in-game 27/9).
|
||||
-- En maskine med CP-kursus og et husket waypoint (CP nulstiller det når kurset er kørt færdigt) var midt i et job:
|
||||
-- start den fra sidste waypoint med CP's eget "fortsæt"-job (HUD'ens eget startpunkt røres ikke). Står den på en
|
||||
-- start den fra sidste waypoint med CP's eget "fortsæt"-job (med gemt liste fra ADCpResume: netop de maskiner
|
||||
-- der kørte CP ved gem, fra nærmeste waypoint) (HUD'ens eget startpunkt røres ikke). Står den på en
|
||||
-- mark hvis aktuelle trin er maskinens rolle, meldes den ind på marken igen, så den følges og trinnet afsluttes.
|
||||
function ADFieldWork.resumeAfterLoad(adEnv)
|
||||
-- gemt liste over maskiner hvor CP kørte (ADCpResume): den afgør alene hvem der startes, fra nærmeste waypoint
|
||||
local toStart, cpVehicles = nil, {}
|
||||
if ADCpResume ~= nil then toStart, cpVehicles = ADCpResume.takeToStart(adEnv) end
|
||||
local startSet, handled = {}, {}
|
||||
for _, id in ipairs(toStart or {}) do startSet[id] = true end
|
||||
local fields, stageById = {}, {}
|
||||
for _, info in ipairs(ADFields.list()) do
|
||||
local field = ADFieldJobs.getField(info.id)
|
||||
@@ -948,18 +954,24 @@ function ADFieldWork.resumeAfterLoad(adEnv)
|
||||
local vehicle = rig.vehicle
|
||||
local wasWorking = vehicle.startCpAtLastWp ~= nil and vehicle.hasCpCourse ~= nil and vehicle:hasCpCourse()
|
||||
and vehicle.getCpLastRememberedWaypointIx ~= nil and vehicle:getCpLastRememberedWaypointIx() ~= nil
|
||||
if toStart ~= nil then wasWorking = startSet[rig.id] == true end
|
||||
handled[rig.id] = true
|
||||
local fieldId, distance = nil, nil
|
||||
if wasWorking then
|
||||
if wasWorking and toStart ~= nil then
|
||||
fieldId = ADFieldJobs.resumeField(rig.x, rig.z, rig.role, fields)
|
||||
elseif wasWorking then
|
||||
fieldId = ADFieldJobs.resumeField(rig.x, rig.z, rig.role, fields)
|
||||
local course = ADCpBridge.getCourse(vehicle)
|
||||
local okWp, wp = pcall(function() return course ~= nil and course:getWaypoint(vehicle:getCpLastRememberedWaypointIx()) or nil end)
|
||||
if okWp and wp ~= nil and wp.x ~= nil then distance = math.sqrt((wp.x - rig.x) ^ 2 + (wp.z - rig.z) ^ 2) end
|
||||
end
|
||||
if wasWorking and not rig.busy and not rig.controlled and not ADFieldJobs.shouldResume({fieldId = fieldId, distance = distance}) then
|
||||
if wasWorking and toStart == nil and not rig.busy and not rig.controlled and not ADFieldJobs.shouldResume({fieldId = fieldId, distance = distance}) then
|
||||
Logging.info(ADFieldJobs.LOG_PREFIX .. "'%s' genstartes ikke: husket waypoint fra et gammelt job (%s væk, ikke på en mark i gang)",
|
||||
tostring(rig.label), distance ~= nil and string.format("%.0f m", distance) or "ukendt afstand")
|
||||
elseif wasWorking and not rig.busy and not rig.controlled then
|
||||
local ok, started = pcall(vehicle.startCpAtLastWp, vehicle)
|
||||
local ok, started
|
||||
if toStart ~= nil then ok, started = pcall(ADCpResume.startNearest, vehicle)
|
||||
else ok, started = pcall(vehicle.startCpAtLastWp, vehicle) end
|
||||
if not ok then
|
||||
Logging.warning(ADFieldJobs.LOG_PREFIX .. "'%s' kunne ikke genstartes: %s", tostring(rig.label), tostring(started))
|
||||
elseif started then
|
||||
@@ -980,11 +992,24 @@ function ADFieldWork.resumeAfterLoad(adEnv)
|
||||
log(fieldId, "'%s' fortsætter efter genindlæsning (Courseplay fra sidste waypoint)%s", tostring(rig.label),
|
||||
assignment.storeId ~= nil and "" or (fillType ~= nil and " — intet lager til resten fundet" or ""))
|
||||
else
|
||||
Logging.info(ADFieldJobs.LOG_PREFIX .. "'%s' genstartet fra sidste waypoint efter genindlæsning", tostring(rig.label))
|
||||
Logging.info(ADFieldJobs.LOG_PREFIX .. "'%s' genstartet fra %s waypoint efter genindlæsning", tostring(rig.label),
|
||||
toStart ~= nil and "nærmeste" or "sidste")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- CP-maskiner uden markrolle (fx balleopsamler, tømmevogn i CP-job): start dem også
|
||||
for _, id in ipairs(toStart or {}) do
|
||||
local vehicle = cpVehicles[id]
|
||||
if not handled[id] and vehicle ~= nil then
|
||||
local ok, started = pcall(ADCpResume.startNearest, vehicle)
|
||||
if ok and started then
|
||||
Logging.info(ADFieldJobs.LOG_PREFIX .. "'%s' genstartet fra nærmeste waypoint efter genindlæsning", tostring(ADRuns.vehicleLabel(vehicle)))
|
||||
elseif not ok then
|
||||
Logging.warning(ADFieldJobs.LOG_PREFIX .. "'%s' kunne ikke genstartes: %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(started))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Markmaskiner der stadig hedder modelnavnet ("Unrealistic Engine 3") får et navn efter rollen ("Roeoptager 2").
|
||||
|
||||
@@ -1436,6 +1436,8 @@ function ADRunsController.save()
|
||||
-- navnefilen skal skrives under spillets gem: FS25 genskriver savegame-mappen, og filer ingen skriver
|
||||
-- under gemningen forsvinder (in-game 27/9: ADSmartPickup_names.xml blev slettet ved første gem)
|
||||
if ADNames ~= nil and ADNames.byId ~= nil then pcall(ADNames.save) end
|
||||
-- maskiner hvor CP kører nu, så de kan genstartes efter indlæsning (samme gem-regel som navnefilen)
|
||||
if ADCpResume ~= nil and ADFieldSilos ~= nil and ADFieldSilos.adEnv ~= nil then pcall(ADCpResume.save, ADFieldSilos.adEnv) end
|
||||
return saved
|
||||
end
|
||||
|
||||
@@ -1450,6 +1452,7 @@ function ADRunsController.load(adEnv)
|
||||
if ADNames ~= nil then pcall(ADNames.load) end
|
||||
pcall(ADRunsController.migrateBuildings, adEnv)
|
||||
if ADFieldSilos ~= nil then pcall(ADFieldSilos.load) end
|
||||
if ADCpResume ~= nil then pcall(ADCpResume.load) end
|
||||
local vehiclesById = getFarmVehiclesById(adEnv)
|
||||
for _, run in ipairs(store.runs) do
|
||||
for _, vehicleId in ipairs(run.vehicleIds) do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.44.0.0</version>
|
||||
<version>1.45.0.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
@@ -45,6 +45,7 @@
|
||||
<sourceFile filename="adFieldSiloPlanner.lua"/>
|
||||
<sourceFile filename="adFieldSiloConfig.lua"/>
|
||||
<sourceFile filename="adFieldSilos.lua"/>
|
||||
<sourceFile filename="adCpResume.lua"/>
|
||||
<sourceFile filename="adFieldJobs.lua"/>
|
||||
<sourceFile filename="adMonthView.lua"/>
|
||||
<sourceFile filename="adProfiler.lua"/>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Kør: luajit tests/test_adCpResume.lua (fra repo-roden)
|
||||
dofile("FS25_ADSmartPickup/adCpResume.lua")
|
||||
local R = ADCpResume
|
||||
local failures = 0
|
||||
local function check(name, actual, expected)
|
||||
if actual == expected then print("OK " .. name) else
|
||||
failures = failures + 1
|
||||
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
|
||||
end
|
||||
end
|
||||
-- hvem skrives ved gem: CP kører, AD styrer ikke (AD genstarter selv sine)
|
||||
local ids = R.activeIds({{id = "a", cpActive = true}, {id = "b", cpActive = true, adActive = true}, {id = "c"}, {id = "d", cpActive = true}})
|
||||
check("gem: CP-aktive uden AD", table.concat(ids, " "), "a d")
|
||||
check("gem: tom liste", #R.activeIds({}), 0)
|
||||
-- records frem og tilbage
|
||||
local set = R.fromRecords({"a", "", "d", "a"})
|
||||
check("indlæs: a", set.a, true)
|
||||
check("indlæs: tomt id springes over", set[""], nil)
|
||||
-- hvem startes ved indlæsning
|
||||
local pending = {a = true, b = true, c = true, d = true}
|
||||
local vehicles = {{id = "a"}, {id = "b", aiActive = true}, {id = "c", controlled = true}, {id = "d"}, {id = "e"}}
|
||||
local start = R.toStart(pending, vehicles)
|
||||
check("start: kun gemte, ikke allerede kørende/spiller i", table.concat(start, " "), "a d")
|
||||
check("start: ingen gemt liste -> nil (gammel regel bruges)", R.toStart(nil, vehicles), nil)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
Reference in New Issue
Block a user