feat(markarbejde): faste maskiner pr. mark (bruges kun dér; marken venter på sin egen); fix: AD-påfyldning får varen sat (kom tom tilbage); AD der stopper undervejs startes igen 2x, ellers hjem til parkering (v1.30.0.0)
This commit is contained in:
@@ -78,6 +78,43 @@ end
|
||||
-- Nærmeste brugbare sæt. Grund når intet passer: noRig (ingen med flueben og rollen), cannotDo (ingen kan
|
||||
-- tage opgaven, fx afgrøden), allBusy, tooWide/tooNarrow (bredden, med tal fra den første).
|
||||
-- accept (valgfri): candidate -> bool, fx "høsteren kan tage hvede".
|
||||
-- Faste maskiner: rigIdsByField = {[fieldId] = {id, ...}}. En maskine der er fast på en ANDEN mark må
|
||||
-- ikke bruges her. Har marken selv faste maskiner med rollen, bruges kun de (marken venter på sin egen);
|
||||
-- ellers de frie. (in-game 26/9: sprøjten "til F22" blev sendt til F109.)
|
||||
function ADFieldFleet.filterDedicated(candidates, fieldId, role, rigIdsByField)
|
||||
local ownerOf, own = {}, {}
|
||||
for otherId, ids in pairs(rigIdsByField or {}) do
|
||||
for _, id in ipairs(ids or {}) do
|
||||
ownerOf[id] = otherId
|
||||
if otherId == fieldId then own[id] = true end
|
||||
end
|
||||
end
|
||||
local ownWithRole = {}
|
||||
for _, candidate in ipairs(candidates or {}) do
|
||||
local hasRole = candidate.role == role or (candidate.roles ~= nil and candidate.roles[role])
|
||||
if own[candidate.id] and hasRole then table.insert(ownWithRole, candidate) end
|
||||
end
|
||||
if #ownWithRole > 0 then return ownWithRole end
|
||||
local free = {}
|
||||
for _, candidate in ipairs(candidates or {}) do
|
||||
if ownerOf[candidate.id] == nil then table.insert(free, candidate) end
|
||||
end
|
||||
return free
|
||||
end
|
||||
|
||||
-- Gør en maskine fast på én mark (fjernes fra andre marker). Returnerer ny tabel {[fieldId] = ids}.
|
||||
function ADFieldFleet.dedicate(rigIdsByField, fieldId, vehicleId, isOn)
|
||||
local result = {}
|
||||
for otherId, ids in pairs(rigIdsByField or {}) do
|
||||
local copy = {}
|
||||
for _, id in ipairs(ids or {}) do if id ~= vehicleId then table.insert(copy, id) end end
|
||||
result[otherId] = copy
|
||||
end
|
||||
result[fieldId] = result[fieldId] or {}
|
||||
if isOn then table.insert(result[fieldId], vehicleId) end
|
||||
return result
|
||||
end
|
||||
|
||||
function ADFieldFleet.pick(candidates, role, x, z, requiredWidth, accept)
|
||||
local usable, reason, args = sortCandidates(candidates, role, x, z, requiredWidth, accept)
|
||||
if usable[1] ~= nil then return usable[1].candidate end
|
||||
|
||||
@@ -61,6 +61,8 @@ local function copyPlan(plan)
|
||||
for key, value in pairs(DEFAULT_PLAN) do copy[key] = value end
|
||||
for key, value in pairs(plan or {}) do copy[key] = value end
|
||||
copy.storeBuildingIds = copyList(plan ~= nil and plan.storeBuildingIds or nil)
|
||||
-- faste maskiner (køretøjs-id'er) til marken; en fast maskine bruges kun her
|
||||
copy.rigIds = copyList(plan ~= nil and plan.rigIds or nil)
|
||||
return copy
|
||||
end
|
||||
|
||||
@@ -448,6 +450,7 @@ function ADFieldModel.toRecord(field)
|
||||
return {
|
||||
id = field.id, stage = field.stage, mode = plan.mode, product = plan.product,
|
||||
rake = flag(plan.rake), store = table.concat(plan.storeBuildingIds, " "),
|
||||
dedicated = table.concat(plan.rigIds or {}, " "),
|
||||
rigs = plan.rigs, unloaders = plan.unloaders, mulch = flag(plan.mulch), lime = flag(plan.lime),
|
||||
soil = plan.soil, sow = flag(plan.sow), sowFruit = plan.sowFruit or "", roll = flag(plan.roll),
|
||||
fertilize = flag(plan.fertilize), weed = flag(plan.weed),
|
||||
@@ -479,6 +482,7 @@ function ADFieldModel.fromRecord(record)
|
||||
plan.product = PRODUCTS[record.product] and record.product or "auto"
|
||||
plan.rake = readFlag(record.rake, false)
|
||||
for id in string.gmatch(record.store or "", "%S+") do table.insert(plan.storeBuildingIds, id) end
|
||||
for id in string.gmatch(record.dedicated or "", "%S+") do table.insert(plan.rigIds, id) end
|
||||
plan.rigs = clamp(record.rigs or 1, 1, ADFieldModel.MAX_RIGS)
|
||||
plan.unloaders = clamp(record.unloaders or 1, 1, ADFieldModel.MAX_UNLOADERS)
|
||||
plan.mulch = readFlag(record.mulch, false)
|
||||
|
||||
@@ -13,6 +13,7 @@ ADFieldStorage.FIELD_TYPES = {
|
||||
sowFruit = "String", roll = "Int", fertilize = "Int", weed = "Int",
|
||||
kind = "String", lastFruit = "String", care = "String", targetFruit = "String", prepAfterMow = "Int", straw = "String",
|
||||
mowWidth = "Float", mowVehicle = "String", rakeVehicle = "String", harvestVehicle = "String", windrowOffset = "String",
|
||||
dedicated = "String",
|
||||
}
|
||||
|
||||
function ADFieldStorage.getPath()
|
||||
|
||||
@@ -15,6 +15,8 @@ ADFieldWork.PLAYER_DRIVING_KMH = 3
|
||||
ADFieldWork.WINDROW_MEASURE_MS = 10000
|
||||
-- mangler der sæt, prøves igen efter så lang tid (ikke hvert sekund over hele flåden)
|
||||
ADFieldWork.NO_RIG_RETRY_MS = 30000
|
||||
-- AutoDrive der stopper før markøren startes igen så mange gange, før turen opgives
|
||||
ADFieldWork.DRIVE_RETRIES = 2
|
||||
ADFieldWork.REFILL_ROLES = {seeder = true, fertilizer = true, limer = true, weeder = true}
|
||||
|
||||
local function log(...) ADFieldJobs.log(...) end
|
||||
@@ -57,12 +59,18 @@ local function helperCp(adEnv)
|
||||
return adEnv.ADStateModule ~= nil and adEnv.ADStateModule.HELPER_CP or 1
|
||||
end
|
||||
|
||||
local function startAd(adEnv, vehicle, mode, firstId, secondId)
|
||||
-- fillType (valgfri, LOAD): varen AutoDrive skal læsse — uden den kører AD til påfyldningen og hjem tom
|
||||
-- (in-game 26/9: sprøjten kom tom tilbage fra Buying Station Multifruit)
|
||||
local function startAd(adEnv, vehicle, mode, firstId, secondId, fillType)
|
||||
local module = stateModule(vehicle)
|
||||
stopAd(vehicle)
|
||||
module:setMode(mode)
|
||||
module:setFirstMarkerByWayPointId(firstId)
|
||||
if secondId ~= nil then module:setSecondMarkerByWayPointId(secondId) end
|
||||
if fillType ~= nil then
|
||||
module:setFillType(fillType)
|
||||
module.selectedFillTypes = {fillType}
|
||||
end
|
||||
module:setStartHelper(false)
|
||||
if module.setActualFarmId ~= nil then module:setActualFarmId(vehicle:getOwnerFarmId()) end
|
||||
module:raiseDirtyFlag()
|
||||
@@ -316,7 +324,10 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
|
||||
note(field.id, {retryAt = now() + ADFieldModel.RETRY_MS, reason = reason})
|
||||
return
|
||||
end
|
||||
local candidates = ADFieldWork.getRigs(adEnv)
|
||||
-- faste maskiner: andre markers faste bruges ikke; har marken selv faste med rollen, kun dem
|
||||
local rigIdsByField = {}
|
||||
for fieldId, other in pairs(ADFieldJobs.state.fields) do rigIdsByField[fieldId] = other.plan ~= nil and other.plan.rigIds or nil end
|
||||
local candidates = ADFieldFleet.filterDedicated(ADFieldWork.getRigs(adEnv), field.id, role, rigIdsByField)
|
||||
local required = ADFieldModel.requiredWidth(field, role)
|
||||
local accept = acceptFor(field, info, role)
|
||||
local rigs, why, args
|
||||
@@ -349,12 +360,14 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
|
||||
ADFieldJobs.state.assignments[rig.id] = assignment
|
||||
if refill ~= nil and refill.empty then
|
||||
-- AutoDrive LOAD: påfyldningen (2. markør), fyld op, videre til F-markøren (1.) — så Courseplay
|
||||
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_LOAD, info.markerId, refill.refillId)
|
||||
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_LOAD, info.markerId, refill.refillId, refill.fillType)
|
||||
assignment.drive = {mode = adEnv.AutoDrive.MODE_LOAD, first = info.markerId, second = refill.refillId, fillType = refill.fillType}
|
||||
local marker = adEnv.ADGraphManager:getMapMarkerByWayPointId(refill.refillId)
|
||||
log(field.id, "%s '%s' er tom — fyldes med %s ved %s og kører så til %s", role, rig.label, fillTypeName(refill.fillType),
|
||||
tostring(marker ~= nil and marker.name or refill.refillId), tostring(info.markerName))
|
||||
else
|
||||
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_DRIVETO, info.markerId, nil)
|
||||
assignment.drive = {mode = adEnv.AutoDrive.MODE_DRIVETO, first = info.markerId}
|
||||
log(field.id, "%s '%s' sendt til %s (%.1f m%s)", role, rig.label, tostring(info.markerName), rig.width or 0,
|
||||
assignment.lane ~= nil and string.format(", bane %d", assignment.lane) or "")
|
||||
end
|
||||
@@ -751,9 +764,20 @@ function ADFieldWork.follow(adEnv, assignment)
|
||||
local distance = wayPoint ~= nil and math.sqrt((wayPoint.x - x) ^ 2 + (wayPoint.z - z) ^ 2) or math.huge
|
||||
if distance <= ADFieldWork.ARRIVE_DISTANCE then
|
||||
beginWork(adEnv, field, info, assignment)
|
||||
elseif assignment.drive ~= nil and (assignment.driveRetries or 0) < ADFieldWork.DRIVE_RETRIES then
|
||||
-- AutoDrive stoppede undervejs (spilleren byggede, trafik, ...): kør videre i stedet for at opgive
|
||||
assignment.driveRetries = (assignment.driveRetries or 0) + 1
|
||||
assignment.since = now()
|
||||
local d = assignment.drive
|
||||
startAd(adEnv, vehicle, d.mode, d.first, d.second, d.fillType)
|
||||
log(assignment.fieldId, "'%s' stoppede %.0f m fra %s — AutoDrive startes igen (%d/%d)", ADRuns.vehicleLabel(vehicle), distance,
|
||||
tostring(info ~= nil and info.markerName or "?"), assignment.driveRetries, ADFieldWork.DRIVE_RETRIES)
|
||||
else
|
||||
ADFieldWork.release(assignment)
|
||||
fail(assignment.fieldId, string.format("%s nåede ikke %s (%.0f m væk)", ADRuns.vehicleLabel(vehicle), tostring(info ~= nil and info.markerName or "?"), distance))
|
||||
-- ikke efterladt midt på vejen/ved en mark: hjem til parkeringen
|
||||
local park = parkMarkerId(adEnv, vehicle)
|
||||
if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
|
||||
end
|
||||
end
|
||||
elseif assignment.phase == "waitingCourse" then
|
||||
|
||||
@@ -9,7 +9,7 @@ SmartPickupFrame.FIELD_ROW_KINDS = {
|
||||
fieldProduct = true, fieldMode = true, fieldRake = true, fieldStore = true, fleetVehicle = true,
|
||||
fieldRigs = true, fieldUnloaders = true, fieldMulch = true, fieldLime = true, fieldSoil = true,
|
||||
fieldSow = true, fieldSowFruit = true, fieldRoll = true, fieldFertilize = true, fieldWeed = true,
|
||||
fieldTarget = true, fleetMode = true, fieldStraw = true,
|
||||
fieldTarget = true, fleetMode = true, fieldStraw = true, fieldRig = true,
|
||||
}
|
||||
SmartPickupFrame.ROLE_KEYS = {
|
||||
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
|
||||
@@ -277,6 +277,7 @@ function SmartPickupFrame:fillFieldStoreRows(adEnv, info, field)
|
||||
fillType = info.harvestFillType
|
||||
end
|
||||
if fillType == nil then return end
|
||||
self:fillDedicatedRows(adEnv, field)
|
||||
self:addTextRow(i18n:getText("spu_field_section_store"))
|
||||
local chosen = {}
|
||||
for _, id in ipairs(field.plan.storeBuildingIds) do chosen[id] = true end
|
||||
@@ -358,6 +359,31 @@ function SmartPickupFrame:rigDetails(rig)
|
||||
return #parts > 0 and (" — " .. table.concat(parts, " — ")) or ""
|
||||
end
|
||||
|
||||
-- Faste maskiner til den valgte mark: en fast maskine bruges kun her, og marken venter på sin egen hellere
|
||||
-- end at låne en anden med samme opgave. Maskiner der er faste på en anden mark er markeret.
|
||||
function SmartPickupFrame:fillDedicatedRows(adEnv, field)
|
||||
local i18n = self.i18n
|
||||
self:addTextRow(i18n:getText("spu_field_section_dedicated"))
|
||||
local owner = {}
|
||||
for fieldId, other in pairs(ADFieldJobs.state.fields) do
|
||||
for _, id in ipairs(other.plan ~= nil and other.plan.rigIds or {}) do owner[id] = fieldId end
|
||||
end
|
||||
local rigs = ADFieldJobs.getRigs(adEnv)
|
||||
table.sort(rigs, function(a, b)
|
||||
local ma, mb = owner[a.id] == field.id, owner[b.id] == field.id
|
||||
if ma ~= mb then return ma end
|
||||
if a.role ~= b.role then return a.role < b.role end
|
||||
return ADFlowView.naturalLess(a.label, b.label)
|
||||
end)
|
||||
for _, rig in ipairs(rigs) do
|
||||
local row = self:addRow(self.vehiclePrefab, {kind = "fieldRig", vehicleId = rig.id})
|
||||
local elsewhere = owner[rig.id] ~= nil and owner[rig.id] ~= field.id
|
||||
and (" — " .. string.format(i18n:getText("spu_field_dedicatedElsewhere"), tostring(owner[rig.id]))) or ""
|
||||
row:getDescendantByName("title"):setText(string.format(" %s — %s%s", tostring(rig.label), self:roleText(rig.role), elsewhere))
|
||||
row:getDescendantByName("option"):setState(owner[rig.id] == field.id and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
|
||||
end
|
||||
end
|
||||
|
||||
function SmartPickupFrame:fillFleetRows(adEnv)
|
||||
local i18n = self.i18n
|
||||
self:addTextRow(i18n:getText("spu_field_section_fleet"))
|
||||
@@ -424,6 +450,15 @@ function SmartPickupFrame:onClickFieldRow(state, data)
|
||||
ADFieldJobs.setPlan(id, {mode = isOn and "auto" or "manual"})
|
||||
elseif SmartPickupFrame.PLAN_TOGGLES[data.kind] ~= nil then
|
||||
ADFieldJobs.setPlan(id, {[SmartPickupFrame.PLAN_TOGGLES[data.kind]] = isOn})
|
||||
elseif data.kind == "fieldRig" then
|
||||
-- fast på denne mark (og fjernes fra en anden mark)
|
||||
local byField = {}
|
||||
for fieldId, other in pairs(ADFieldJobs.state.fields) do byField[fieldId] = other.plan ~= nil and other.plan.rigIds or {} end
|
||||
local updated = ADFieldFleet.dedicate(byField, id, data.vehicleId, isOn)
|
||||
for fieldId, ids in pairs(updated) do
|
||||
local before = table.concat(byField[fieldId] or {}, " ")
|
||||
if before ~= table.concat(ids, " ") then ADFieldJobs.setPlan(fieldId, {rigIds = ids}) end
|
||||
end
|
||||
elseif data.kind == "fieldStore" then
|
||||
local ids = {}
|
||||
for _, buildingId in ipairs(ADFieldJobs.getField(id).plan.storeBuildingIds) do
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
<text name="spu_field_marker" text=" Markør %s, %.0f m fra marken"/>
|
||||
<text name="spu_field_mowWidth" text=" Slået med %.1f m — opsamling skal være mindst lige så bred"/>
|
||||
<text name="spu_field_section_store" text="— Tving lager (ellers nærmeste silo med plads) —"/>
|
||||
<text name="spu_field_section_dedicated" text="— Faste maskiner til denne mark (bruges kun her; ingen valgt = de frie) —"/>
|
||||
<text name="spu_field_dedicatedElsewhere" text="fast på F%s"/>
|
||||
<text name="spu_field_fieldSilo" text="marksilo"/>
|
||||
<text name="spu_field_storeHidden" text="+ %d siloer længere væk vises ikke"/>
|
||||
<text name="spu_field_noStore" text="ingen silo tager markens vare"/>
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
<text name="spu_field_marker" text=" Marker %s, %.0f m from the field"/>
|
||||
<text name="spu_field_mowWidth" text=" Mowed at %.1f m — collecting must be at least as wide"/>
|
||||
<text name="spu_field_section_store" text="— Force storage (else nearest silo with room) —"/>
|
||||
<text name="spu_field_section_dedicated" text="— Dedicated machines for this field (used only here; none = the free ones) —"/>
|
||||
<text name="spu_field_dedicatedElsewhere" text="dedicated to F%s"/>
|
||||
<text name="spu_field_fieldSilo" text="field silo"/>
|
||||
<text name="spu_field_storeHidden" text="+ %d silos further away not shown"/>
|
||||
<text name="spu_field_noStore" text="no silo takes this field's goods"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.29.6.0</version>
|
||||
<version>1.30.0.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
|
||||
Reference in New Issue
Block a user