503 lines
20 KiB
Lua
503 lines
20 KiB
Lua
-- AD Runs
|
|
-- Rene funktioner (ingen spil-API) for "runs": navn, aflæsningsmål, varer, Wait-pulje,
|
|
-- loop-tæller og tildelte traktorer. Alle funktioner returnerer en NY store —
|
|
-- input røres aldrig, så GUI og controller kan holde referencer uden overraskelser.
|
|
|
|
ADRuns = {}
|
|
|
|
ADRuns.MAX_LOOPS = 99
|
|
-- "Kør hjem fra Wait når målet er under X %": 10..90, standard 50 (= den gamle 50 %-regel).
|
|
ADRuns.DEFAULT_RETURN_BELOW_PERCENT = 50
|
|
ADRuns.MIN_RETURN_BELOW_PERCENT, ADRuns.MAX_RETURN_BELOW_PERCENT = 10, 90
|
|
ADRuns.XML_VERSION = 3
|
|
ADRuns.INGREDIENT_MODES = {auto = true, manual = true}
|
|
-- Retning: Forsyning (ind til stald/fabrik) eller Udkørsel (fabrik/lager -> silo/salg).
|
|
ADRuns.KINDS = {supply = true, outbound = true}
|
|
-- Politik pr. vare i et udkørsels-run; "off" = varen er ikke med (fjernes fra listen).
|
|
ADRuns.POLICIES = {store = true, sell = true, sellElseStore = true}
|
|
ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT = 90, 50, 100
|
|
ADRuns.DEFAULT_STORE_ABOVE_PERCENT, ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT = 80, 50, 95
|
|
-- "Sælg altid over X %": kildens lager over grænsen sælges uanset pris (0 = fra). Så hober f.eks. gylle
|
|
-- ved BGA'en sig ikke op, når prisen er lav.
|
|
ADRuns.DEFAULT_SELL_ABOVE_PERCENT, ADRuns.MIN_SELL_ABOVE_PERCENT, ADRuns.MAX_SELL_ABOVE_PERCENT = 0, 0, 100
|
|
|
|
local function copyList(list)
|
|
local copy = {}
|
|
for index, value in ipairs(list or {}) do
|
|
copy[index] = value
|
|
end
|
|
return copy
|
|
end
|
|
|
|
local function copyProducts(products)
|
|
local copy = {}
|
|
for index, product in ipairs(products or {}) do
|
|
copy[index] = {fillType = product.fillType, policy = product.policy}
|
|
end
|
|
return copy
|
|
end
|
|
|
|
local function clampRange(value, default, min, max)
|
|
local number = tonumber(value) or default
|
|
return math.max(min, math.min(max, math.floor(number)))
|
|
end
|
|
|
|
-- Flow-vogne: {[traktorId] = vognId} (maskinpulje — flowet henter sin egen vogn).
|
|
function ADRuns.copyWagons(wagons)
|
|
local copy = {}
|
|
for tractorId, wagonId in pairs(wagons or {}) do copy[tractorId] = wagonId end
|
|
return copy
|
|
end
|
|
|
|
function ADRuns.serializeWagons(wagons)
|
|
local keys = {}
|
|
for tractorId in pairs(wagons or {}) do table.insert(keys, tractorId) end
|
|
table.sort(keys)
|
|
local parts = {}
|
|
for _, tractorId in ipairs(keys) do table.insert(parts, tractorId .. "=" .. wagons[tractorId]) end
|
|
return table.concat(parts, ";")
|
|
end
|
|
|
|
function ADRuns.parseWagons(text)
|
|
local wagons = {}
|
|
for tractorId, wagonId in string.gmatch(tostring(text or ""), "([^=;]+)=([^=;]+)") do wagons[tractorId] = wagonId end
|
|
return wagons
|
|
end
|
|
|
|
local function copyRun(run)
|
|
return {
|
|
id = run.id,
|
|
name = run.name,
|
|
kind = run.kind,
|
|
sourceWayPointIds = copyList(run.sourceWayPointIds),
|
|
sourceBuildingIds = copyList(run.sourceBuildingIds),
|
|
targetBuildingIds = copyList(run.targetBuildingIds),
|
|
storeBuildingIds = copyList(run.storeBuildingIds),
|
|
products = copyProducts(run.products),
|
|
sellAtPercent = run.sellAtPercent,
|
|
storeAbovePercent = run.storeAbovePercent,
|
|
sellAbovePercent = run.sellAbovePercent,
|
|
collectRound = run.collectRound,
|
|
targetWayPointId = run.targetWayPointId,
|
|
targetWayPointIds = copyList(run.targetWayPointIds),
|
|
ingredientMode = run.ingredientMode,
|
|
ingredients = copyList(run.ingredients),
|
|
waitPoolGroup = run.waitPoolGroup,
|
|
loops = run.loops,
|
|
returnBelowPercent = run.returnBelowPercent,
|
|
vehicleIds = copyList(run.vehicleIds),
|
|
stopped = run.stopped == true,
|
|
wagons = ADRuns.copyWagons(run.wagons),
|
|
}
|
|
end
|
|
|
|
local function copyStore(store)
|
|
local runs = {}
|
|
for index, run in ipairs(store.runs) do
|
|
runs[index] = copyRun(run)
|
|
end
|
|
return {nextId = store.nextId, runs = runs}
|
|
end
|
|
|
|
local function clampPercent(percent)
|
|
local value = tonumber(percent) or ADRuns.DEFAULT_RETURN_BELOW_PERCENT
|
|
return math.max(ADRuns.MIN_RETURN_BELOW_PERCENT, math.min(ADRuns.MAX_RETURN_BELOW_PERCENT, math.floor(value)))
|
|
end
|
|
|
|
local function clampLoops(loops)
|
|
local value = tonumber(loops) or 0
|
|
return math.max(0, math.min(ADRuns.MAX_LOOPS, math.floor(value)))
|
|
end
|
|
|
|
function ADRuns.newStore()
|
|
return {nextId = 1, runs = {}}
|
|
end
|
|
|
|
function ADRuns.createRun(store, name, kind)
|
|
local new = copyStore(store)
|
|
local run = {
|
|
id = new.nextId,
|
|
name = name,
|
|
kind = ADRuns.KINDS[kind] and kind or "supply",
|
|
sourceWayPointIds = {},
|
|
sourceBuildingIds = {},
|
|
targetBuildingIds = {},
|
|
storeBuildingIds = {},
|
|
products = {},
|
|
sellAtPercent = ADRuns.DEFAULT_SELL_AT_PERCENT,
|
|
storeAbovePercent = ADRuns.DEFAULT_STORE_ABOVE_PERCENT,
|
|
sellAbovePercent = ADRuns.DEFAULT_SELL_ABOVE_PERCENT,
|
|
collectRound = false,
|
|
targetWayPointId = nil,
|
|
targetWayPointIds = {},
|
|
ingredientMode = "auto",
|
|
ingredients = {},
|
|
waitPoolGroup = "",
|
|
loops = 0,
|
|
returnBelowPercent = ADRuns.DEFAULT_RETURN_BELOW_PERCENT,
|
|
vehicleIds = {},
|
|
}
|
|
new.nextId = new.nextId + 1
|
|
table.insert(new.runs, run)
|
|
return new, run
|
|
end
|
|
|
|
function ADRuns.getRun(store, runId)
|
|
for _, run in ipairs(store.runs) do
|
|
if run.id == runId then
|
|
return run
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function ADRuns.deleteRun(store, runId)
|
|
local new = copyStore(store)
|
|
for index, run in ipairs(new.runs) do
|
|
if run.id == runId then
|
|
table.remove(new.runs, index)
|
|
break
|
|
end
|
|
end
|
|
return new
|
|
end
|
|
|
|
-- changes: delmængde af {name, targetWayPointId, ingredientMode, ingredients, waitPoolGroup, loops}.
|
|
function ADRuns.updateRun(store, runId, changes)
|
|
local new = copyStore(store)
|
|
local run = ADRuns.getRun(new, runId)
|
|
if run == nil then
|
|
return new
|
|
end
|
|
if changes.name ~= nil then run.name = changes.name end
|
|
-- Mål: liste (flere stalde pr. run); targetWayPointId = første = "primært" (bagudkompatibelt).
|
|
-- false = ryd målene (nil i changes betyder "rør ikke")
|
|
if changes.targetWayPointIds ~= nil then
|
|
run.targetWayPointIds = copyList(changes.targetWayPointIds)
|
|
elseif changes.targetWayPointId == false then
|
|
run.targetWayPointIds = {}
|
|
elseif changes.targetWayPointId ~= nil then
|
|
run.targetWayPointIds = {changes.targetWayPointId}
|
|
end
|
|
run.targetWayPointId = run.targetWayPointIds[1]
|
|
if ADRuns.INGREDIENT_MODES[changes.ingredientMode] then run.ingredientMode = changes.ingredientMode end
|
|
if changes.ingredients ~= nil then run.ingredients = copyList(changes.ingredients) end
|
|
if changes.waitPoolGroup ~= nil then run.waitPoolGroup = changes.waitPoolGroup end
|
|
if changes.loops ~= nil then run.loops = clampLoops(changes.loops) end
|
|
if changes.returnBelowPercent ~= nil then run.returnBelowPercent = clampPercent(changes.returnBelowPercent) end
|
|
if ADRuns.KINDS[changes.kind] then run.kind = changes.kind end
|
|
if changes.sourceWayPointIds ~= nil then run.sourceWayPointIds = copyList(changes.sourceWayPointIds) end
|
|
if changes.sourceBuildingIds ~= nil then run.sourceBuildingIds = copyList(changes.sourceBuildingIds) end
|
|
if changes.targetBuildingIds ~= nil then run.targetBuildingIds = copyList(changes.targetBuildingIds) end
|
|
if changes.storeBuildingIds ~= nil then run.storeBuildingIds = copyList(changes.storeBuildingIds) end
|
|
if changes.products ~= nil then run.products = copyProducts(changes.products) end
|
|
if changes.sellAtPercent ~= nil then run.sellAtPercent = clampRange(changes.sellAtPercent, ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT) end
|
|
if changes.storeAbovePercent ~= nil then run.storeAbovePercent = clampRange(changes.storeAbovePercent, ADRuns.DEFAULT_STORE_ABOVE_PERCENT, ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT) end
|
|
if changes.sellAbovePercent ~= nil then run.sellAbovePercent = clampRange(changes.sellAbovePercent, ADRuns.DEFAULT_SELL_ABOVE_PERCENT, ADRuns.MIN_SELL_ABOVE_PERCENT, ADRuns.MAX_SELL_ABOVE_PERCENT) end
|
|
if changes.collectRound ~= nil then run.collectRound = changes.collectRound == true end
|
|
return new
|
|
end
|
|
|
|
-- ---- bygninger som kilder/mål (flow-tavlen) ----
|
|
|
|
local function toggleInList(list, value)
|
|
local result, found = {}, false
|
|
for _, item in ipairs(list or {}) do
|
|
if item == value then found = true else table.insert(result, item) end
|
|
end
|
|
if not found then table.insert(result, value) end
|
|
return result
|
|
end
|
|
|
|
function ADRuns.toggleSourceBuilding(store, runId, buildingId)
|
|
local run = ADRuns.getRun(store, runId)
|
|
if run == nil then return copyStore(store) end
|
|
return ADRuns.updateRun(store, runId, {sourceBuildingIds = toggleInList(run.sourceBuildingIds, buildingId)})
|
|
end
|
|
|
|
-- Tvunget lagermål for udkørsel: lagerture kører KUN til disse bygninger (tom = moden vælger selv).
|
|
function ADRuns.toggleStoreBuilding(store, runId, buildingId)
|
|
local run = ADRuns.getRun(store, runId)
|
|
if run == nil then return copyStore(store) end
|
|
return ADRuns.updateRun(store, runId, {storeBuildingIds = toggleInList(run.storeBuildingIds, buildingId)})
|
|
end
|
|
|
|
function ADRuns.toggleTargetBuilding(store, runId, buildingId)
|
|
local run = ADRuns.getRun(store, runId)
|
|
if run == nil then return copyStore(store) end
|
|
return ADRuns.updateRun(store, runId, {targetBuildingIds = toggleInList(run.targetBuildingIds, buildingId)})
|
|
end
|
|
|
|
-- ---- udkørsel: kilder og varer ----
|
|
|
|
-- Tilføj/fjern en kilde-markør (afkrydsning i fanen Udkørsel).
|
|
function ADRuns.toggleSource(store, runId, wayPointId)
|
|
local run = ADRuns.getRun(store, runId)
|
|
if run == nil then
|
|
return copyStore(store)
|
|
end
|
|
local sources, found = {}, false
|
|
for _, id in ipairs(run.sourceWayPointIds) do
|
|
if id == wayPointId then
|
|
found = true
|
|
else
|
|
table.insert(sources, id)
|
|
end
|
|
end
|
|
if not found then
|
|
table.insert(sources, wayPointId)
|
|
end
|
|
return ADRuns.updateRun(store, runId, {sourceWayPointIds = sources})
|
|
end
|
|
|
|
function ADRuns.getProductPolicy(run, fillTypeName)
|
|
for _, product in ipairs(run.products or {}) do
|
|
if product.fillType == fillTypeName then
|
|
return product.policy
|
|
end
|
|
end
|
|
return "off"
|
|
end
|
|
|
|
-- policy "off" fjerner varen; ukendt politik ignoreres (store uændret).
|
|
function ADRuns.setProductPolicy(store, runId, fillTypeName, policy)
|
|
local run = ADRuns.getRun(store, runId)
|
|
if run == nil or (policy ~= "off" and not ADRuns.POLICIES[policy]) then
|
|
return copyStore(store)
|
|
end
|
|
local products = {}
|
|
for _, product in ipairs(run.products) do
|
|
if product.fillType ~= fillTypeName then
|
|
table.insert(products, product)
|
|
end
|
|
end
|
|
if policy ~= "off" then
|
|
table.insert(products, {fillType = fillTypeName, policy = policy})
|
|
end
|
|
return ADRuns.updateRun(store, runId, {products = products})
|
|
end
|
|
|
|
-- "SUGAR:sell FORAGE:store" — navne er stabile på tværs af mods, fillType-indeks er det ikke.
|
|
function ADRuns.serializeProducts(products)
|
|
local parts = {}
|
|
for _, product in ipairs(products or {}) do
|
|
table.insert(parts, product.fillType .. ":" .. product.policy)
|
|
end
|
|
return table.concat(parts, " ")
|
|
end
|
|
|
|
function ADRuns.parseProducts(text)
|
|
local products = {}
|
|
for token in string.gmatch(tostring(text or ""), "%S+") do
|
|
local name, policy = string.match(token, "^([^:]+):(%a+)$")
|
|
if name ~= nil and ADRuns.POLICIES[policy] then
|
|
table.insert(products, {fillType = name, policy = policy})
|
|
end
|
|
end
|
|
return products
|
|
end
|
|
|
|
-- Tilføj/fjern et mål (afkrydsning i fanen).
|
|
function ADRuns.toggleTarget(store, runId, wayPointId)
|
|
local run = ADRuns.getRun(store, runId)
|
|
if run == nil then
|
|
return copyStore(store)
|
|
end
|
|
local targets = {}
|
|
local found = false
|
|
for _, id in ipairs(run.targetWayPointIds) do
|
|
if id == wayPointId then
|
|
found = true
|
|
else
|
|
table.insert(targets, id)
|
|
end
|
|
end
|
|
if not found then
|
|
table.insert(targets, wayPointId)
|
|
end
|
|
return ADRuns.updateRun(store, runId, {targetWayPointIds = targets})
|
|
end
|
|
|
|
function ADRuns.findRunByVehicle(store, vehicleId)
|
|
for _, run in ipairs(store.runs) do
|
|
for _, id in ipairs(run.vehicleIds) do
|
|
if id == vehicleId then
|
|
return run
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function ADRuns.unassignVehicle(store, vehicleId)
|
|
local new = copyStore(store)
|
|
for _, run in ipairs(new.runs) do
|
|
for index, id in ipairs(run.vehicleIds) do
|
|
if id == vehicleId then
|
|
table.remove(run.vehicleIds, index)
|
|
break
|
|
end
|
|
end
|
|
-- traktoren forlader flowet: dens huskede vogn hører ikke længere til flowet
|
|
if run.wagons ~= nil then run.wagons[vehicleId] = nil end
|
|
end
|
|
return new
|
|
end
|
|
|
|
-- En traktor er kun i ét run. Returnerer ny store + id på det run den blev flyttet FRA (nil = ingen).
|
|
function ADRuns.assignVehicle(store, runId, vehicleId)
|
|
local previous = ADRuns.findRunByVehicle(store, vehicleId)
|
|
if previous ~= nil and previous.id == runId then
|
|
return copyStore(store), nil
|
|
end
|
|
local new = ADRuns.unassignVehicle(store, vehicleId)
|
|
local run = ADRuns.getRun(new, runId)
|
|
if run == nil then
|
|
return new, nil
|
|
end
|
|
table.insert(run.vehicleIds, vehicleId)
|
|
return new, previous ~= nil and previous.id or nil
|
|
end
|
|
|
|
-- Varer gemmes som mellemrumsadskilte fillType-NAVNE ("STRAW FORAGE") — navne er
|
|
-- stabile på tværs af mods/kort, fillType-indeks er det ikke.
|
|
function ADRuns.serializeIngredients(list)
|
|
return table.concat(list or {}, " ")
|
|
end
|
|
|
|
function ADRuns.parseIngredients(text)
|
|
local list = {}
|
|
for name in string.gmatch(tostring(text or ""), "%S+") do
|
|
table.insert(list, name)
|
|
end
|
|
return list
|
|
end
|
|
|
|
-- Flade poster (kun strenge/tal) til XML-laget, i store'ens rækkefølge.
|
|
function ADRuns.toRecords(store)
|
|
local records = {}
|
|
for index, run in ipairs(store.runs) do
|
|
records[index] = {
|
|
id = run.id,
|
|
name = run.name,
|
|
target = run.targetWayPointId or 0,
|
|
targets = table.concat(run.targetWayPointIds, " "),
|
|
ingredientMode = run.ingredientMode,
|
|
ingredients = ADRuns.serializeIngredients(run.ingredients),
|
|
waitPool = run.waitPoolGroup,
|
|
loops = run.loops,
|
|
returnBelow = run.returnBelowPercent,
|
|
vehicleIds = copyList(run.vehicleIds),
|
|
kind = run.kind,
|
|
sources = table.concat(run.sourceWayPointIds or {}, " "),
|
|
sourceBuildings = table.concat(run.sourceBuildingIds or {}, " "),
|
|
targetBuildings = table.concat(run.targetBuildingIds or {}, " "),
|
|
storeBuildings = table.concat(run.storeBuildingIds or {}, " "),
|
|
products = ADRuns.serializeProducts(run.products),
|
|
sellAt = run.sellAtPercent,
|
|
storeAbove = run.storeAbovePercent,
|
|
sellAbove = run.sellAbovePercent or 0,
|
|
collectRound = run.collectRound and 1 or 0,
|
|
stopped = run.stopped and 1 or 0,
|
|
wagons = ADRuns.serializeWagons(run.wagons),
|
|
}
|
|
end
|
|
return records
|
|
end
|
|
|
|
-- Poster uden gyldigt id/navn eller med dublet-id springes over; nextId = højeste id + 1.
|
|
function ADRuns.fromRecords(records)
|
|
local store = ADRuns.newStore()
|
|
for _, record in ipairs(records or {}) do
|
|
local id = tonumber(record.id)
|
|
local isValid = id ~= nil and id >= 1 and record.name ~= nil and record.name ~= ""
|
|
if isValid and ADRuns.getRun(store, id) == nil then
|
|
local target = tonumber(record.target)
|
|
local targets = {}
|
|
for token in string.gmatch(tostring(record.targets or ""), "%S+") do
|
|
local wayPointId = tonumber(token)
|
|
if wayPointId ~= nil and wayPointId > 0 then table.insert(targets, wayPointId) end
|
|
end
|
|
if #targets == 0 and target ~= nil and target > 0 then
|
|
targets = {target}
|
|
end
|
|
local function parseIds(text)
|
|
local ids = {}
|
|
for token in string.gmatch(tostring(text or ""), "%S+") do table.insert(ids, token) end
|
|
return ids
|
|
end
|
|
local sources = {}
|
|
for token in string.gmatch(tostring(record.sources or ""), "%S+") do
|
|
local wayPointId = tonumber(token)
|
|
if wayPointId ~= nil and wayPointId > 0 then table.insert(sources, wayPointId) end
|
|
end
|
|
table.insert(store.runs, {
|
|
id = id,
|
|
name = record.name,
|
|
kind = ADRuns.KINDS[record.kind] and record.kind or "supply",
|
|
sourceWayPointIds = sources,
|
|
sourceBuildingIds = parseIds(record.sourceBuildings),
|
|
targetBuildingIds = parseIds(record.targetBuildings),
|
|
storeBuildingIds = parseIds(record.storeBuildings),
|
|
products = ADRuns.parseProducts(record.products),
|
|
sellAtPercent = clampRange(record.sellAt, ADRuns.DEFAULT_SELL_AT_PERCENT, ADRuns.MIN_SELL_AT_PERCENT, ADRuns.MAX_SELL_AT_PERCENT),
|
|
storeAbovePercent = clampRange(record.storeAbove, ADRuns.DEFAULT_STORE_ABOVE_PERCENT, ADRuns.MIN_STORE_ABOVE_PERCENT, ADRuns.MAX_STORE_ABOVE_PERCENT),
|
|
sellAbovePercent = clampRange(record.sellAbove, ADRuns.DEFAULT_SELL_ABOVE_PERCENT, ADRuns.MIN_SELL_ABOVE_PERCENT, ADRuns.MAX_SELL_ABOVE_PERCENT),
|
|
collectRound = tonumber(record.collectRound) == 1,
|
|
targetWayPointId = targets[1],
|
|
targetWayPointIds = targets,
|
|
ingredientMode = ADRuns.INGREDIENT_MODES[record.ingredientMode] and record.ingredientMode or "auto",
|
|
ingredients = ADRuns.parseIngredients(record.ingredients),
|
|
waitPoolGroup = record.waitPool or "",
|
|
loops = clampLoops(record.loops),
|
|
returnBelowPercent = clampPercent(record.returnBelow),
|
|
vehicleIds = copyList(record.vehicleIds),
|
|
stopped = tonumber(record.stopped) == 1,
|
|
wagons = ADRuns.parseWagons(record.wagons),
|
|
})
|
|
store.nextId = math.max(store.nextId, id + 1)
|
|
end
|
|
end
|
|
return store
|
|
end
|
|
|
|
-- ---- køretøjsnavne ----
|
|
|
|
-- Navn pr. køretøj efter det forløb det kører: "Milk Collect 1", "Milk Collect 2" … i medlemsrækkefølge;
|
|
-- er køretøjet alene i forløbet, bare forløbets navn. {[vehicleId] = navn}.
|
|
function ADRuns.vehicleNames(store)
|
|
local names = {}
|
|
for _, run in ipairs(store ~= nil and store.runs or {}) do
|
|
local count = #(run.vehicleIds or {})
|
|
for index, vehicleId in ipairs(run.vehicleIds or {}) do
|
|
names[vehicleId] = count > 1 and string.format("%s %d", tostring(run.name), index) or tostring(run.name)
|
|
end
|
|
end
|
|
return names
|
|
end
|
|
|
|
-- true når navnet er et forløbsnavn vi selv har sat ("Navn" eller "Navn 3").
|
|
function ADRuns.isRunVehicleName(store, name)
|
|
if name == nil then return false end
|
|
for _, run in ipairs(store ~= nil and store.runs or {}) do
|
|
local runName = tostring(run.name)
|
|
if name == runName then return true end
|
|
local prefix, number = string.match(name, "^(.-) (%d+)$")
|
|
if prefix == runName and number ~= nil then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Navnet i log og menu: AutoDrives førernavn (sat efter forløbet), ellers køretøjets modelnavn.
|
|
function ADRuns.vehicleLabel(vehicle)
|
|
local stateModule = vehicle ~= nil and vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
if stateModule ~= nil and stateModule.getName ~= nil then
|
|
local ok, name = pcall(stateModule.getName, stateModule)
|
|
if ok and name ~= nil and name ~= "" then return tostring(name) end
|
|
end
|
|
if vehicle ~= nil and vehicle.getName ~= nil then
|
|
return tostring(vehicle:getName())
|
|
end
|
|
return "?"
|
|
end
|