diff --git a/FS25_ADSmartPickup/adSmartPickup.lua b/FS25_ADSmartPickup/adSmartPickup.lua index aff3a52..7b08de6 100644 --- a/FS25_ADSmartPickup/adSmartPickup.lua +++ b/FS25_ADSmartPickup/adSmartPickup.lua @@ -257,7 +257,7 @@ function ADSmartPickup.getLoadStationNearWayPointId(adEnv, wayPointId, farmId, m end end end - return best + return best, best ~= nil and bestDistance or nil end -- Siloens EGET indhold. Udvidelses-siloer (isExtension) deler lager med naboer diff --git a/FS25_ADSmartPickup/adSources.lua b/FS25_ADSmartPickup/adSources.lua index 6ad855a..ff23d7a 100644 --- a/FS25_ADSmartPickup/adSources.lua +++ b/FS25_ADSmartPickup/adSources.lua @@ -24,10 +24,6 @@ local function getHusbandryPalletSpawners(placeable) return spec ~= nil and spec.fillTypeIndexToPalletSpawner or nil end -local function firstSpawnerNode(spawner) - return spawner ~= nil and spawner.spawnPlaces ~= nil and spawner.spawnPlaces[1] ~= nil and spawner.spawnPlaces[1].startNode or nil -end - local function distance2(ax, az, bx, bz) local dx, dz = ax - bx, az - bz @@ -130,7 +126,8 @@ local function findPlaceableNear(wayPoint, farmId, predicate) end end end - return best + -- (afstand i meter som 2. værdi) + return best, best ~= nil and math.sqrt(bestDistance) or nil end local function isObjectStorage(placeable) @@ -177,9 +174,15 @@ function ADSources.identifyUncached(adEnv, wayPointId, farmId) end -- Det strenge opslag (trigger bag næsen) passer til silorækker; en mælketank eller fabrik ligger -- ofte ved siden af markøren. Løst opslag inden for 40 m — men aldrig for siloer. + -- stald med egne pallespawnere (hønsehus: æg) — markøren står ved spawneren, ingen læssestation + local palletBarn, palletBarnDistance = findPlaceableNear(wayPoint, farmId, function(placeable) return placeable.getHusbandryFillLevel ~= nil and getHusbandryPalletSpawners(placeable) ~= nil end) if placeable == nil and ADSmartPickup.getLoadStationNearWayPointId ~= nil then - local loose = ADSmartPickup.getLoadStationNearWayPointId(adEnv, wayPointId, farmId, ADSources.MAX_DISTANCE) + local loose, looseDistance = ADSmartPickup.getLoadStationNearWayPointId(adEnv, wayPointId, farmId, ADSources.MAX_DISTANCE) local loosePlaceable = loose ~= nil and loose.owningPlaceable or nil + -- et nærmere hønsehus vinder over en fjernere læssestation (gyllestation 23 m fra "Æg Pickup 1") + if palletBarn ~= nil and looseDistance ~= nil and palletBarnDistance ~= nil and palletBarnDistance < looseDistance then + loosePlaceable = nil + end if loosePlaceable ~= nil and loosePlaceable.spec_silo == nil then if getProductionPoint(loosePlaceable) ~= nil then return {kind = "factory", placeable = loosePlaceable, station = loose, wayPointId = wayPointId, name = getPlaceableName(loosePlaceable, markerName)} @@ -188,8 +191,6 @@ function ADSources.identifyUncached(adEnv, wayPointId, farmId) end end end - -- stald med egne pallespawnere (hønsehus: æg) — markøren står ved spawneren, ingen læssestation - local palletBarn = findPlaceableNear(wayPoint, farmId, function(placeable) return placeable.getHusbandryFillLevel ~= nil and getHusbandryPalletSpawners(placeable) ~= nil end) if palletBarn ~= nil then return {kind = "husbandry", placeable = palletBarn, station = nil, wayPointId = wayPointId, name = getPlaceableName(palletBarn, markerName)} end @@ -254,55 +255,99 @@ local function getPalletContent(vehicle) return nil, 0 end --- Liter af varen på paller der står ved fabrikkens spawner (pallespawneren tømmer lageret --- ud på jorden løbende, så det er dér varen fysisk er). --- Alle andre pallespawnere der kan lave varen (stalde med spawner for fillType, fabrikkers --- pallespawner): {{x, z}, ...}. Står bygningerne tættere end PALLET_RADIUS (hønsehuse på række), --- tilhører en palle den nærmeste spawner — ellers tæller et tomt hus naboernes paller med. +-- Spawnerens område som segmenter {ax, az, bx, bz}: hver spawnPlace fra start til slut (startNode/endNode, +-- eller PlacementUtil-felterne startX/startZ + dirX/dirZ*width). Et hønsehus har to rækker; måles kun +-- til første række, ligger den anden række tættere på naboens start end husets egen. +local function spawnerSegments(spawner) + local segments = {} + for _, place in ipairs(spawner ~= nil and spawner.spawnPlaces or {}) do + local ax, az = getNodePosition(place.startNode) + local bx, bz = getNodePosition(place.endNode) + if ax == nil and place.startX ~= nil and place.startZ ~= nil then + ax, az = place.startX, place.startZ + if place.dirX ~= nil and place.dirZ ~= nil and place.width ~= nil then + bx, bz = ax + place.dirX * place.width, az + place.dirZ * place.width + end + end + if ax ~= nil then table.insert(segments, {ax, az, bx or ax, bz or az}) end + end + return segments +end + +-- Kvadreret afstand fra (x, z) til nærmeste punkt på et af segmenterne. +local function segmentsDistance2(segments, x, z) + local best = math.huge + for _, s in ipairs(segments) do + local ax, az, bx, bz = s[1], s[2], s[3], s[4] + local dx, dz = bx - ax, bz - az + local len2 = dx * dx + dz * dz + local t = len2 > 0 and math.max(0, math.min(1, ((x - ax) * dx + (z - az) * dz) / len2)) or 0 + local d = distance2(x, z, ax + dx * t, az + dz * t) + if d < best then best = d end + end + return best +end + +-- Alle pallespawnere der kan lave varen (stalde med spawner for fillType, fabrikkers pallespawner) som +-- {placeable, segments}. Står bygningerne tættere end PALLET_RADIUS (hønsehuse på række), tilhører en +-- palle den spawner hvis område er nærmest — ellers tæller et tomt hus naboernes paller med. local function findRivalSpawners(fillType) local rivals = {} local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {} for _, placeable in pairs(placeables) do local husbandrySpawners = getHusbandryPalletSpawners(placeable) local productionPoint = getProductionPoint(placeable) - local node = firstSpawnerNode(husbandrySpawners ~= nil and husbandrySpawners[fillType] or nil) - or firstSpawnerNode(productionPoint ~= nil and productionPoint.palletSpawner or nil) - local x, z = getNodePosition(node) - if x ~= nil then table.insert(rivals, {x = x, z = z}) end + local spawner = husbandrySpawners ~= nil and husbandrySpawners[fillType] or (productionPoint ~= nil and productionPoint.palletSpawner or nil) + local segments = spawnerSegments(spawner) + if #segments > 0 then table.insert(rivals, {placeable = placeable, segments = segments}) end end return rivals end -local function isNearestSpawner(rivals, sx, sz, x, z) - local own = distance2(sx, sz, x, z) +local function isNearestSpawner(rivals, placeable, own, x, z) for _, rival in ipairs(rivals) do - if distance2(rival.x, rival.z, x, z) < own then return false end + if rival.placeable ~= placeable and segmentsDistance2(rival.segments, x, z) < own then return false end end return true end -local function getPalletsNearSpawner(placeable, fillType, farmId, spawnerNode) +ADSources.lastPalletNote = {} + +-- Liter af varen på paller ved spawneren (pallespawneren tømmer lageret ud på jorden løbende, så +-- det er dér varen fysisk er). spawner = staldens spawner for varen; nil = fabrikkens pallespawner. +local function getPalletsNearSpawner(placeable, fillType, farmId, spawner) local productionPoint = getProductionPoint(placeable) - local spawner = productionPoint ~= nil and productionPoint.palletSpawner or nil - local node = spawnerNode or (spawner ~= nil and spawner.spawnPlaces ~= nil and spawner.spawnPlaces[1] ~= nil and spawner.spawnPlaces[1].startNode) or placeable.rootNode - local sx, sz = getNodePosition(node) - if sx == nil then - return 0 + local segments = spawnerSegments(spawner or (productionPoint ~= nil and productionPoint.palletSpawner or nil)) + if #segments == 0 then + local rx, rz = getNodePosition(placeable.rootNode) + if rx == nil then return 0, 0 end + segments = {{rx, rz, rx, rz}} end local rivals = cacheGet("palletSpawners", fillType, 10000, function() return findRivalSpawners(fillType) end) local vehicles = g_currentMission ~= nil and g_currentMission.vehicleSystem ~= nil and g_currentMission.vehicleSystem.vehicles or {} - local total, count = 0, 0 + local total, count, lost = 0, 0, 0 local radius2 = ADSources.PALLET_RADIUS * ADSources.PALLET_RADIUS for _, vehicle in pairs(vehicles) do if isPalletVehicle(vehicle) and (vehicle.getOwnerFarmId == nil or vehicle:getOwnerFarmId() == farmId) then local palletFillType, level = getPalletContent(vehicle) local x, z = getNodePosition(vehicle.rootNode) - if palletFillType == fillType and x ~= nil and distance2(sx, sz, x, z) <= radius2 and isNearestSpawner(rivals, sx, sz, x, z) then - total = total + level - count = count + 1 + local own = (palletFillType == fillType and x ~= nil) and segmentsDistance2(segments, x, z) or math.huge + if own <= radius2 then + if isNearestSpawner(rivals, placeable, own, x, z) then + total = total + level + count = count + 1 + else + lost = lost + 1 + end end end end + -- diagnose: paller inden for rækkevidde, men alle tildelt en nabo (højst én gang pr. minut pr. bygning) + local now = g_currentMission ~= nil and g_currentMission.time or nil + if count == 0 and lost > 0 and now ~= nil and (ADSources.lastPalletNote[placeable] or -math.huge) + 60000 < now then + ADSources.lastPalletNote[placeable] = now + Logging.info(ADSources.LOG_PREFIX .. "%s: %d paller inden for %d m tilhører nabo-spawnere (0 egne)", getPlaceableName(placeable, "?"), lost, ADSources.PALLET_RADIUS) + end return total, count end @@ -434,7 +479,7 @@ local function getHusbandryPalletProducts(source, farmId) local products = {} local spec = source.placeable.spec_husbandryPallets for fillType, spawner in pairs(getHusbandryPalletSpawners(source.placeable) or {}) do - local liters, count = getPalletsNearSpawner(source.placeable, fillType, farmId, firstSpawnerNode(spawner)) + local liters, count = getPalletsNearSpawner(source.placeable, fillType, farmId, spawner) local capacity = spec ~= nil and spec.capacities ~= nil and spec.capacities[fillType] or 0 local product = newProduct(fillType, liters, liters, math.max(capacity or 0, liters), true) product.palletLiters, product.palletCount = liters, count diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 56197db..b3b2ca3 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.28.1.0 + 1.28.2.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adSources.lua b/tests/test_adSources.lua index 94dfec0..9ec283c 100644 --- a/tests/test_adSources.lua +++ b/tests/test_adSources.lua @@ -438,6 +438,31 @@ check("S14f tomt hus 0 paller", eggsAt(emptyCoop).palletCount, 0) table.remove(g_currentMission.placeableSystem.placeables) table.remove(g_currentMission.placeableSystem.placeables) +-- S14g: spawnerens område er ALLE rækker fra start til slut — ikke kun første rækkes start. Hus A's +-- række 2 slutter tæt på hus B's start; en palle for enden af A's række 2 er stadig A's. +local rowA = {spawnPlaces = {{startNode = {x = 9000, z = 0}, endNode = {x = 9008, z = 0}}, {startNode = {x = 9000, z = 3}, endNode = {x = 9008, z = 3}}}} +local rowB = {spawnPlaces = {{startNode = {x = 9014, z = 3}, endNode = {x = 9022, z = 3}}}} +local function rowCoop(spawner, x) + return {rootNode = {x = x, z = -10}, ownerFarmId = 1, getOwnerFarmId = function() return 1 end, getName = function() return "Chicken Coop" end, + getHusbandryFillLevel = function() return 0 end, + spec_husbandryPallets = {fillTypeIndexToPalletSpawner = {[EGGS] = spawner}, activeFillTypes = {EGGS}, fillLevels = {[EGGS] = 0}, capacities = {[EGGS] = 5000}}} +end +local coopA, coopB = rowCoop(rowA, 9004), rowCoop(rowB, 9018) +table.insert(g_currentMission.placeableSystem.placeables, coopA) +table.insert(g_currentMission.placeableSystem.placeables, coopB) +addPallet(EGGS, 1000, 9008, 3) -- enden af A's række 2: 6 m til B's start, 8 m til A's første start +check("S14g palle for enden af egen række 2 er husets egen", eggsAt(coopA).available, 1000) +check("S14g naboen får den ikke", eggsAt(coopB).available, 0) +-- PlacementUtil-form (startX/startZ + dirX/dirZ*width) i stedet for noder +local placeC = {spawnPlaces = {{startX = 9000, startZ = 50, dirX = 1, dirZ = 0, width = 8}}} +local coopC = rowCoop(placeC, 9004) +table.insert(g_currentMission.placeableSystem.placeables, coopC) +addPallet(EGGS, 700, 9007, 51) +check("S14g PlacementUtil-plads tæller", eggsAt(coopC).available, 700) +table.remove(g_currentMission.placeableSystem.placeables) +table.remove(g_currentMission.placeableSystem.placeables) +table.remove(g_currentMission.placeableSystem.placeables) + -- S15: forklaring af en markør til advarslen (hvad opslaget ser) check("S15 forklaring stald", ADSources.explainMarker(adEnv, 10, 1), "læssestation ved markøren hører til 'Kostald' (stald)") check("S15 forklaring ingen station", ADSources.explainMarker(adEnv, 4, 1), "ingen læssestation ved markøren, og ingen fabrik/objektlager inden for 40 m") @@ -457,6 +482,19 @@ looseAt[13] = siloStation check("S16 silo aldrig via løst opslag", ADSources.identify(adEnv, 13, 1), nil) check("S16 forklaring nævner den løse station", ADSources.explainMarker(adEnv, 13, 1):find("silo", 1, true) ~= nil, true) +-- S16b: "Æg Pickup 1" står 12 m fra hønsehusets palleområde og 23 m fra svinestaldens gyllestation; +-- det nærmeste vinder (in-game 26/9: Pickup 1+2 blev genkendt som Pigsty og æggene aldrig hentet) +local looseDistance = {} +ADSmartPickup.getLoadStationNearWayPointId = function(_, id, farmId, maxDistance) return looseAt[id], looseDistance[id] end +local pigStation = {owningPlaceable = cowBarn} +addMarker(14, "Æg Pickup 1", 8000, 12) +looseAt[14], looseDistance[14] = pigStation, 23 +local eggSource = ADSources.identify(adEnv, 14, 1) +check("S16b hønsehuset vinder over fjernere gyllestation", eggSource and eggSource.placeable, coop) +addMarker(15, "Slurry ved hønsehus", 8000, 30) +looseAt[15], looseDistance[15] = pigStation, 5 +check("S16b nærmere gyllestation vinder stadig", ADSources.identify(adEnv, 15, 1).placeable, cowBarn) + -- S17: stald med ÉN station og TO læssepunkter (mælk i den ene ende, gylle i den anden): hver markør -- giver kun de varer dens nærmeste læssepunkt udleverer (in-game 24/9, LargerAnimalBarns cowBarnBig) local SLURRY = 13