feat(pulje): flådefanen viser puljetraktorer og -redskaber med status og hvorfor de ikke er med; puljetraktorer omdøbes ikke efter lånte redskaber (v1.48.3.0)

This commit is contained in:
2026-09-28 18:52:43 +02:00
parent 7730f68dec
commit 6eff1dcae8
8 changed files with 207 additions and 4 deletions
+5 -1
View File
@@ -136,6 +136,8 @@ local function isBusyVehicle(vehicle, id)
or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)) or (ADPool ~= nil and ADPool.isReturning(vehicle)) or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)) or (ADPool ~= nil and ADPool.isReturning(vehicle))
end end
ADFieldWork.isBusyVehicle = isBusyVehicle
function ADFieldWork.getRigs(adEnv) function ADFieldWork.getRigs(adEnv)
local rigs = {} local rigs = {}
local state = ADFieldJobs.state local state = ADFieldJobs.state
@@ -1112,7 +1114,9 @@ function ADFieldWork.autoNameFleet(adEnv)
local vehicle = rig.vehicle local vehicle = rig.vehicle
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
local model = vehicle.getName ~= nil and tostring(vehicle:getName()) or nil local model = vehicle.getName ~= nil and tostring(vehicle:getName()) or nil
if stateModule ~= nil and stateModule.setName ~= nil and model ~= nil and ADRuns.vehicleLabel(vehicle) == model and not rig.controlled then -- puljetraktor med et lånt redskab: ikke opkaldt efter redskabet (in-game 28/9: Challenger blev "Kultivator 1")
local pooled = ADHitch ~= nil and ADPool ~= nil and #ADHitch.poolParkIds(adEnv, vehicle) > 0
if stateModule ~= nil and stateModule.setName ~= nil and model ~= nil and ADRuns.vehicleLabel(vehicle) == model and not rig.controlled and not pooled then
local name = ADNaming.fleetName(rig.role, rig.role == "harvester" and harvesterSubtype(rig) or nil, names) local name = ADNaming.fleetName(rig.role, rig.role == "harvester" and harvesterSubtype(rig) or nil, names)
if name ~= nil then if name ~= nil then
stateModule:setName(name) stateModule:setName(name)
+7 -1
View File
@@ -299,7 +299,8 @@ end
-- Puljeredskaber bag traktoren sættes af på deres parkering, og traktoren kører hjem til sin egen. -- Puljeredskaber bag traktoren sættes af på deres parkering, og traktoren kører hjem til sin egen.
-- Kun redskaber med en Connect-markør til deres parkering er puljeredskaber; andre bliver siddende. -- Kun redskaber med en Connect-markør til deres parkering er puljeredskaber; andre bliver siddende.
function ADHitch.returnHome(adEnv, tractor, onDone) -- Parkeringer (waypoint-id) for puljeredskaberne bag traktoren (tom = ingen puljeredskaber bag).
function ADHitch.poolParkIds(adEnv, tractor)
local markers = adEnv.ADGraphManager:getMapMarkers() local markers = adEnv.ADGraphManager:getMapMarkers()
local parkIds = {} local parkIds = {}
for _, implement in ipairs(ADHitch.rearImplementsOf(tractor)) do for _, implement in ipairs(ADHitch.rearImplementsOf(tractor)) do
@@ -307,6 +308,11 @@ function ADHitch.returnHome(adEnv, tractor, onDone)
local park = parkId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(parkId) or nil local park = parkId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(parkId) or nil
if park ~= nil and ADPool.connectFor(park.name, markers) ~= nil then table.insert(parkIds, parkId) end if park ~= nil and ADPool.connectFor(park.name, markers) ~= nil then table.insert(parkIds, parkId) end
end end
return parkIds
end
function ADHitch.returnHome(adEnv, tractor, onDone)
local parkIds = ADHitch.poolParkIds(adEnv, tractor)
if #parkIds == 0 then return false end if #parkIds == 0 then return false end
local route = ADHitch.homeRoute(parkIds, parkIdOf(adEnv, tractor)) local route = ADHitch.homeRoute(parkIds, parkIdOf(adEnv, tractor))
ADPool.setReturning(tractor, true) ADPool.setReturning(tractor, true)
+99 -1
View File
@@ -95,6 +95,29 @@ function ADPool.mayUsePool(decision, hasOwnForRole)
return not decision.helper and (decision.count or 1) == 1 and not hasOwnForRole return not decision.helper and (decision.count or 1) == 1 and not hasOwnForRole
end end
-- Status til flådefanen. s = {parkName, hasConnect, attachedTo, quarantineMin, reserved} -> nøgle, argument
function ADPool.implementStatus(s)
if s.parkName == nil then return "noPark" end
if not s.hasConnect then return "noConnect", s.parkName end
if s.attachedTo ~= nil then return "attached", s.attachedTo end
if (s.quarantineMin or 0) > 0 then return "quarantine", s.quarantineMin end
if s.reserved then return "reserved" end
return "parked", s.parkName
end
-- s = {hasPark, enabled, controlled, returning, fieldId, busy, offNetwork, hitched} -> nøgle, argument
function ADPool.tractorStatus(s)
if not s.hasPark then return "noPark" end
if s.controlled then return "player" end
if s.returning then return "returning" end
if s.fieldId ~= nil then return "job", s.fieldId end
if s.busy then return "busy" end
if not s.enabled then return "notInFleet" end
if s.offNetwork then return "offNetwork" end
if s.hitched then return "hitched" end
return "free"
end
-- Redskab med rollen nærmest marken, derefter nærmeste ledige traktor med nok hk til redskabet. -- Redskab med rollen nærmest marken, derefter nærmeste ledige traktor med nok hk til redskabet.
-- blocked = {[id] = true} (reserverede og redskaber i karantæne). accept (valgfri): implement -> bool. -- blocked = {[id] = true} (reserverede og redskaber i karantæne). accept (valgfri): implement -> bool.
function ADPool.pairFor(role, fieldX, fieldZ, implements, tractors, blocked, accept) function ADPool.pairFor(role, fieldX, fieldZ, implements, tractors, blocked, accept)
@@ -153,6 +176,19 @@ function ADPool.quarantine(id, nowMs)
quarantined[id] = nowMs + ADPool.QUARANTINE_MS quarantined[id] = nowMs + ADPool.QUARANTINE_MS
end end
function ADPool.quarantineMinutes(id, nowMs)
local untilMs = quarantined[id]
if untilMs == nil or nowMs >= untilMs then return 0 end
return math.ceil((untilMs - nowMs) / 60000)
end
function ADPool.isReserved(id)
for _, ids in pairs(reservations) do
for _, reserved in ipairs(ids) do if reserved == id then return true end end
end
return false
end
function ADPool.blocked(nowMs) function ADPool.blocked(nowMs)
local result = {} local result = {}
for _, ids in pairs(reservations) do for _, ids in pairs(reservations) do
@@ -213,6 +249,15 @@ local function isAttached(vehicle)
return vehicle.getAttacherVehicle ~= nil and vehicle:getAttacherVehicle() ~= nil return vehicle.getAttacherVehicle ~= nil and vehicle:getAttacherVehicle() ~= nil
end end
-- Traktorens egen rolle uden de redskaber der hænger på (mejetærsker/selvkørende har en, en traktor ikke).
function ADPool.ownRole(vehicle)
local specs = {}
for _, name in ipairs(ADCpBridge.SPECS) do
if vehicle[name] ~= nil then specs[name] = true end
end
return ADFieldFleet.roleFromSpecs(specs)
end
-- Puljeredskaber (frakoblet, parkering "X", markør "Connect X") og puljetraktorer. -- Puljeredskaber (frakoblet, parkering "X", markør "Connect X") og puljetraktorer.
-- isBusy(vehicle): markarbejdets optaget-regel (opgave, AD/CP kører, lånt af marksilo, på hjemtur). -- isBusy(vehicle): markarbejdets optaget-regel (opgave, AD/CP kører, lånt af marksilo, på hjemtur).
function ADPool.collect(adEnv, isBusy) function ADPool.collect(adEnv, isBusy)
@@ -245,7 +290,7 @@ function ADPool.collect(adEnv, isBusy)
if id ~= nil and park ~= nil and vehicle.spec_attacherJoints ~= nil and #ADHitch.rearImplementsOf(vehicle) == 0 if id ~= nil and park ~= nil and vehicle.spec_attacherJoints ~= nil and #ADHitch.rearImplementsOf(vehicle) == 0
and ADRunsController.getRunForVehicle(vehicle) == nil and not controlled and not isBusy(vehicle) and ADRunsController.getRunForVehicle(vehicle) == nil and not controlled and not isBusy(vehicle)
and not ADPool.isReturning(vehicle) and ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id) and not ADPool.isReturning(vehicle) and ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id)
and ADPool.canPull(ADFieldFleet.roleFromSpecs(ADCpBridge.getSpecs(vehicle))) then and ADPool.canPull(ADPool.ownRole(vehicle)) then
local x, z = positionOf(vehicle) local x, z = positionOf(vehicle)
-- uden for AD-vejnettet: den kan ikke køre ud efter redskabet (ville sende redskaber i karantæne) -- uden for AD-vejnettet: den kan ikke køre ud efter redskabet (ville sende redskaber i karantæne)
local networkDistance = ADFieldWork.distanceToNetwork(adEnv, x, z) local networkDistance = ADFieldWork.distanceToNetwork(adEnv, x, z)
@@ -256,3 +301,56 @@ function ADPool.collect(adEnv, isBusy)
end end
return implements, tractors return implements, tractors
end end
-- Oversigt til flådefanen: alle redskaber med en markrolle (også dem der mangler parkering/Connect, så
-- spilleren kan se hvorfor) og alle traktorer uden egen arbejdsrolle, der ikke hører til et flow.
function ADPool.overview(adEnv)
local markers = adEnv.ADGraphManager:getMapMarkers()
local farmId = g_currentMission:getFarmId()
local nowMs = ADFieldJobs.now or 0
local implements, tractors = {}, {}
for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do
local owned = vehicle.getOwnerFarmId ~= nil and vehicle:getOwnerFarmId() == farmId
if owned and vehicle.spec_motorized == nil and vehicle.spec_attachable ~= nil then
local role = ADPool.roleOf(ADCpBridge.getSpecs(vehicle))
if role ~= nil then
local id = ADRunsController.getVehicleId(vehicle)
local park = parkMarker(adEnv, vehicle)
local attacher = isAttached(vehicle) and vehicle:getAttacherVehicle() or nil
local key, arg = ADPool.implementStatus({
parkName = park ~= nil and park.name or nil,
hasConnect = park ~= nil and ADPool.connectFor(park.name, markers) ~= nil,
attachedTo = attacher ~= nil and ADRuns.vehicleLabel(attacher) or nil,
quarantineMin = id ~= nil and ADPool.quarantineMinutes(id, nowMs) or 0,
reserved = id ~= nil and ADPool.isReserved(id),
})
table.insert(implements, {id = id, label = ADRuns.vehicleLabel(vehicle), role = role, status = key, arg = arg})
end
end
end
local state = ADFieldJobs.state
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
local id = ADRunsController.getVehicleId(vehicle)
if id ~= nil and vehicle.spec_attacherJoints ~= nil and ADRunsController.getRunForVehicle(vehicle) == nil
and ADPool.canPull(ADPool.ownRole(vehicle)) then
local x, z = positionOf(vehicle)
local distance = ADFieldWork.distanceToNetwork(adEnv, x, z)
local assignment = state.assignments[id]
local rear = ADHitch.rearImplementsOf(vehicle)
local key, arg = ADPool.tractorStatus({
hasPark = parkMarker(adEnv, vehicle) ~= nil,
enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id),
controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled(),
returning = ADPool.isReturning(vehicle),
fieldId = assignment ~= nil and assignment.fieldId or nil,
busy = ADFieldWork.isBusyVehicle(vehicle, id),
offNetwork = distance ~= nil and distance > ADFieldWork.MAX_OFF_NETWORK,
hitched = #rear > 0,
})
table.insert(tractors, {id = id, label = ADRuns.vehicleLabel(vehicle), hp = tractorHpOf(vehicle), status = key, arg = arg,
enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id)})
end
end
return implements, tractors
end
@@ -249,6 +249,35 @@ function SmartPickupFrame:fillFleetRows(adEnv)
if #rigs == 0 then if #rigs == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noRigs")) self:addTextRow(" " .. i18n:getText("spu_field_noRigs"))
end end
if ADPool ~= nil and ADPool.overview ~= nil then self:fillPoolRows(adEnv) end
end
-- Maskinpuljen: solo-traktorer (flueben som resten af flåden) og redskaber med status og hvorfor de ikke er med.
function SmartPickupFrame:fillPoolRows(adEnv)
local i18n = self.i18n
local ok, implements, tractors = pcall(ADPool.overview, adEnv)
if not ok then return end
local byLabel = function(a, b) return ADFlowView.naturalLess(a.label, b.label) end
table.sort(tractors, byLabel)
table.sort(implements, function(a, b)
if a.role ~= b.role then return a.role < b.role end
return byLabel(a, b)
end)
self:addTextRow(i18n:getText("spu_pool_section_tractors"))
for _, tractor in ipairs(tractors) do
local row = self:addRow(self.vehiclePrefab, {kind = "fleetVehicle", vehicleId = tractor.id})
local status = string.format(i18n:getText("spu_pool_tractor_" .. tractor.status), tostring(tractor.arg))
local hp = tractor.hp ~= nil and string.format(", %.0f hk", tractor.hp) or ""
row:getDescendantByName("title"):setText(string.format(" %s%s — %s", tostring(tractor.label), hp, status))
row:getDescendantByName("option"):setState(tractor.enabled and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
if #tractors == 0 then self:addTextRow(" " .. i18n:getText("spu_pool_noTractors")) end
self:addTextRow(i18n:getText("spu_pool_section_implements"))
for _, implement in ipairs(implements) do
local status = string.format(i18n:getText("spu_pool_implement_" .. implement.status), tostring(implement.arg))
self:addTextRow(string.format(" %s — %s: %s", tostring(implement.label), self:roleText(implement.role), status))
end
if #implements == 0 then self:addTextRow(" " .. i18n:getText("spu_pool_noImplements")) end
end end
-- ---- live ---- -- ---- live ----
+19
View File
@@ -85,6 +85,25 @@
<text name="spu_field_reasonShort_rain" text="venter på tørvejr"/> <text name="spu_field_reasonShort_rain" text="venter på tørvejr"/>
<text name="spu_field_reasonShort_text" text="se detaljer"/> <text name="spu_field_reasonShort_text" text="se detaljer"/>
<text name="spu_field_reason_allBusy" text="venter på ledig %s"/> <text name="spu_field_reason_allBusy" text="venter på ledig %s"/>
<text name="spu_pool_section_tractors" text="Maskinpulje — traktorer (henter redskaber når ingen færdig kombination er ledig)"/>
<text name="spu_pool_section_implements" text="Maskinpulje — redskaber (parkering &quot;X&quot; + markør &quot;Connect X&quot;)"/>
<text name="spu_pool_noTractors" text="ingen traktorer uden redskab"/>
<text name="spu_pool_noImplements" text="ingen redskaber med en markrolle"/>
<text name="spu_pool_tractor_free" text="ledig%.0s"/>
<text name="spu_pool_tractor_noPark" text="mangler egen AD-parkering%.0s"/>
<text name="spu_pool_tractor_player" text="spilleren kører den%.0s"/>
<text name="spu_pool_tractor_returning" text="sætter redskab af og kører hjem%.0s"/>
<text name="spu_pool_tractor_job" text="opgave F%s"/>
<text name="spu_pool_tractor_busy" text="AutoDrive/Courseplay kører%.0s"/>
<text name="spu_pool_tractor_notInFleet" text="ikke i markflåden (flueben)%.0s"/>
<text name="spu_pool_tractor_offNetwork" text="står uden for AutoDrive-vejnettet%.0s"/>
<text name="spu_pool_tractor_hitched" text="har et redskab på (kobl det af for at bruge den i puljen)%.0s"/>
<text name="spu_pool_implement_parked" text="parkeret ved %s"/>
<text name="spu_pool_implement_noPark" text="ikke i puljen — mangler AD-parkering%.0s"/>
<text name="spu_pool_implement_noConnect" text="ikke i puljen — mangler markøren &quot;Connect %s&quot;"/>
<text name="spu_pool_implement_attached" text="på %s"/>
<text name="spu_pool_implement_quarantine" text="kunne ikke kobles på — prøves igen om %s min"/>
<text name="spu_pool_implement_reserved" text="reserveret — en traktor er på vej%.0s"/>
<text name="spu_field_reason_noPoolTractor" text="ingen ledig traktor til at hente et redskab til %s"/> <text name="spu_field_reason_noPoolTractor" text="ingen ledig traktor til at hente et redskab til %s"/>
<text name="spu_field_reasonShort_noPoolTractor" text="venter på traktor"/> <text name="spu_field_reasonShort_noPoolTractor" text="venter på traktor"/>
<text name="spu_field_reason_fetching" text="henter '%s'"/> <text name="spu_field_reason_fetching" text="henter '%s'"/>
+19
View File
@@ -85,6 +85,25 @@
<text name="spu_field_reasonShort_rain" text="waiting for dry weather"/> <text name="spu_field_reasonShort_rain" text="waiting for dry weather"/>
<text name="spu_field_reasonShort_text" text="see details"/> <text name="spu_field_reasonShort_text" text="see details"/>
<text name="spu_field_reason_allBusy" text="waiting for a free %s"/> <text name="spu_field_reason_allBusy" text="waiting for a free %s"/>
<text name="spu_pool_section_tractors" text="Machine pool — tractors (fetch implements when no ready combination is free)"/>
<text name="spu_pool_section_implements" text="Machine pool — implements (park &quot;X&quot; + marker &quot;Connect X&quot;)"/>
<text name="spu_pool_noTractors" text="no tractors without implement"/>
<text name="spu_pool_noImplements" text="no implements with a field role"/>
<text name="spu_pool_tractor_free" text="free%.0s"/>
<text name="spu_pool_tractor_noPark" text="needs its own AD park destination%.0s"/>
<text name="spu_pool_tractor_player" text="player is driving%.0s"/>
<text name="spu_pool_tractor_returning" text="dropping implement and driving home%.0s"/>
<text name="spu_pool_tractor_job" text="job F%s"/>
<text name="spu_pool_tractor_busy" text="AutoDrive/Courseplay active%.0s"/>
<text name="spu_pool_tractor_notInFleet" text="not in field fleet (tick)%.0s"/>
<text name="spu_pool_tractor_offNetwork" text="off the AutoDrive network%.0s"/>
<text name="spu_pool_tractor_hitched" text="has an implement attached (detach it to use the pool)%.0s"/>
<text name="spu_pool_implement_parked" text="parked at %s"/>
<text name="spu_pool_implement_noPark" text="not in pool — needs an AD park destination%.0s"/>
<text name="spu_pool_implement_noConnect" text="not in pool — needs marker &quot;Connect %s&quot;"/>
<text name="spu_pool_implement_attached" text="on %s"/>
<text name="spu_pool_implement_quarantine" text="could not attach — retry in %s min"/>
<text name="spu_pool_implement_reserved" text="reserved — a tractor is on its way%.0s"/>
<text name="spu_field_reason_noPoolTractor" text="no free tractor to fetch an implement for %s"/> <text name="spu_field_reason_noPoolTractor" text="no free tractor to fetch an implement for %s"/>
<text name="spu_field_reasonShort_noPoolTractor" text="waiting for tractor"/> <text name="spu_field_reasonShort_noPoolTractor" text="waiting for tractor"/>
<text name="spu_field_reason_fetching" text="fetching '%s'"/> <text name="spu_field_reason_fetching" text="fetching '%s'"/>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.48.2.0</version> <version>1.48.3.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>
+28
View File
@@ -102,5 +102,33 @@ check("I4: almindelig", P.mayUsePool({count = 1}, false), true)
check("I4: tømmevogn", P.mayUsePool({count = 1, helper = true}, false), false) check("I4: tømmevogn", P.mayUsePool({count = 1, helper = true}, false), false)
check("I4: multi-tool", P.mayUsePool({count = 2}, false), false) check("I4: multi-tool", P.mayUsePool({count = 2}, false), false)
check("I4: marken har faste", P.mayUsePool({count = 1}, true), false) check("I4: marken har faste", P.mayUsePool({count = 1}, true), false)
-- status til flådefanen
local function st(key, arg) return tostring(key) .. "|" .. tostring(arg) end
local function ist(s) return st(P.implementStatus(s)) end
check("redskab: uden parkering", ist({}), "noPark|nil")
check("redskab: uden Connect", ist({parkName = "Plov"}), "noConnect|Plov")
check("redskab: på traktor", ist({parkName = "Plov", hasConnect = true, attachedTo = "MT635"}), "attached|MT635")
check("redskab: karantæne", ist({parkName = "Plov", hasConnect = true, quarantineMin = 8}), "quarantine|8")
check("redskab: reserveret", ist({parkName = "Plov", hasConnect = true, reserved = true}), "reserved|nil")
check("redskab: parkeret", ist({parkName = "Plov", hasConnect = true}), "parked|Plov")
local function tst(s) return st(P.tractorStatus(s)) end
local base = {hasPark = true, enabled = true}
check("traktor: uden parkering", tst({enabled = true}), "noPark|nil")
check("traktor: spiller", tst({hasPark = true, enabled = true, controlled = true}), "player|nil")
check("traktor: hjemtur", tst({hasPark = true, enabled = true, returning = true}), "returning|nil")
check("traktor: opgave", tst({hasPark = true, enabled = true, fieldId = 52}), "job|52")
check("traktor: optaget", tst({hasPark = true, enabled = true, busy = true}), "busy|nil")
check("traktor: ikke i flåden", tst({hasPark = true}), "notInFleet|nil")
check("traktor: uden for vejnet", tst({hasPark = true, enabled = true, offNetwork = true}), "offNetwork|nil")
check("traktor: har andet redskab på", tst({hasPark = true, enabled = true, hitched = true}), "hitched|nil")
check("traktor: ledig", tst(base), "free|nil")
-- karantæne-rest i hele minutter (rundet op) og reservation
P.quarantine("q1", 0)
check("karantæne: minutter tilbage", P.quarantineMinutes("q1", 60000), 9)
check("karantæne: udløbet", P.quarantineMinutes("q1", P.QUARANTINE_MS), 0)
P.reserve("owner", {"r1"})
check("reserveret: ja", P.isReserved("r1"), true)
P.release("owner")
check("reserveret: nej", P.isReserved("r1"), false)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)