diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua
index e8cf0d0..67e3137 100644
--- a/FS25_ADSmartPickup/adFieldWork.lua
+++ b/FS25_ADSmartPickup/adFieldWork.lua
@@ -136,6 +136,8 @@ local function isBusyVehicle(vehicle, id)
or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)) or (ADPool ~= nil and ADPool.isReturning(vehicle))
end
+ADFieldWork.isBusyVehicle = isBusyVehicle
+
function ADFieldWork.getRigs(adEnv)
local rigs = {}
local state = ADFieldJobs.state
@@ -1112,7 +1114,9 @@ function ADFieldWork.autoNameFleet(adEnv)
local vehicle = rig.vehicle
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule 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)
if name ~= nil then
stateModule:setName(name)
diff --git a/FS25_ADSmartPickup/adHitch.lua b/FS25_ADSmartPickup/adHitch.lua
index 4bef086..77fa89b 100644
--- a/FS25_ADSmartPickup/adHitch.lua
+++ b/FS25_ADSmartPickup/adHitch.lua
@@ -299,7 +299,8 @@ end
-- 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.
-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 parkIds = {}
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
if park ~= nil and ADPool.connectFor(park.name, markers) ~= nil then table.insert(parkIds, parkId) end
end
+ return parkIds
+end
+
+function ADHitch.returnHome(adEnv, tractor, onDone)
+ local parkIds = ADHitch.poolParkIds(adEnv, tractor)
if #parkIds == 0 then return false end
local route = ADHitch.homeRoute(parkIds, parkIdOf(adEnv, tractor))
ADPool.setReturning(tractor, true)
diff --git a/FS25_ADSmartPickup/adPool.lua b/FS25_ADSmartPickup/adPool.lua
index 0d45732..b38dc2f 100644
--- a/FS25_ADSmartPickup/adPool.lua
+++ b/FS25_ADSmartPickup/adPool.lua
@@ -95,6 +95,29 @@ function ADPool.mayUsePool(decision, hasOwnForRole)
return not decision.helper and (decision.count or 1) == 1 and not hasOwnForRole
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.
-- blocked = {[id] = true} (reserverede og redskaber i karantæne). accept (valgfri): implement -> bool.
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
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)
local result = {}
for _, ids in pairs(reservations) do
@@ -213,6 +249,15 @@ local function isAttached(vehicle)
return vehicle.getAttacherVehicle ~= nil and vehicle:getAttacherVehicle() ~= nil
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.
-- isBusy(vehicle): markarbejdets optaget-regel (opgave, AD/CP kører, lånt af marksilo, på hjemtur).
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
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 ADPool.canPull(ADFieldFleet.roleFromSpecs(ADCpBridge.getSpecs(vehicle))) then
+ and ADPool.canPull(ADPool.ownRole(vehicle)) then
local x, z = positionOf(vehicle)
-- 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)
@@ -256,3 +301,56 @@ function ADPool.collect(adEnv, isBusy)
end
return implements, tractors
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
+
diff --git a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
index e534ea9..1b48f09 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
@@ -249,6 +249,35 @@ function SmartPickupFrame:fillFleetRows(adEnv)
if #rigs == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noRigs"))
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
-- ---- live ----
diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml
index 3f8635e..763069d 100644
--- a/FS25_ADSmartPickup/l10n/l10n_da.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_da.xml
@@ -85,6 +85,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml
index 9ce1e9c..955ec79 100644
--- a/FS25_ADSmartPickup/l10n/l10n_en.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_en.xml
@@ -85,6 +85,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml
index 9127422..e229f90 100644
--- a/FS25_ADSmartPickup/modDesc.xml
+++ b/FS25_ADSmartPickup/modDesc.xml
@@ -1,7 +1,7 @@
masterdraco
- 1.48.2.0
+ 1.48.3.0
AD Smart Pickup
diff --git a/tests/test_adPool.lua b/tests/test_adPool.lua
index 1a7703b..c1d9f44 100644
--- a/tests/test_adPool.lua
+++ b/tests/test_adPool.lua
@@ -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: multi-tool", P.mayUsePool({count = 2}, false), 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"))
os.exit(failures == 0 and 0 or 1)