506 lines
23 KiB
Lua

--[[ Copyright (c) 2017 Optera
* Part of Logistics Train Network
*
* See LICENSE.md in the project directory for license information.
--]]
-- return true if stop, output, lamp are on same logic network
local function detectShortCircuit(checkStop)
local scdetected = false
local networks = {}
local entities = {checkStop.entity, checkStop.output, checkStop.input}
for k, entity in pairs(entities) do
local greenWire = entity.get_circuit_network(defines.wire_type.green)
if greenWire then
if networks[greenWire.network_id] then
scdetected = true
else
networks[greenWire.network_id] = entity.unit_number
end
end
local redWire = entity.get_circuit_network(defines.wire_type.red)
if redWire then
if networks[redWire.network_id] then
scdetected = true
else
networks[redWire.network_id] = entity.unit_number
end
end
end
return scdetected
end
local function remove_available_train(trainID)
if debug_log then log("(UpdateStop) removing available train "..tostring(trainID).." from depot." ) end
global.Dispatcher.availableTrains_total_capacity = global.Dispatcher.availableTrains_total_capacity - global.Dispatcher.availableTrains[trainID].capacity
global.Dispatcher.availableTrains_total_fluid_capacity = global.Dispatcher.availableTrains_total_fluid_capacity - global.Dispatcher.availableTrains[trainID].fluid_capacity
global.Dispatcher.availableTrains[trainID] = nil
end
-- update stop input signals
function UpdateStop(stopID, stop)
global.Dispatcher.Requests_by_Stop[stopID] = nil
-- remove invalid stops
if not stop or not stop.entity.valid or not stop.input.valid or not stop.output.valid or not stop.lamp_control.valid then
if message_level >= 1 then printmsg({"ltn-message.error-invalid-stop", stopID}) end
if debug_log then log(format("(UpdateStop) Removing invalid stop: [%d]", stopID )) end
RemoveStop(stopID)
return
end
-- remove invalid trains
if stop.parked_train and not stop.parked_train.valid then
global.LogisticTrainStops[stopID].parked_train = nil
global.LogisticTrainStops[stopID].parked_train_id = nil
end
-- remove invalid active_deliveries -- shouldn't be necessary
for i=#stop.active_deliveries, 1, -1 do
if not global.Dispatcher.Deliveries[stop.active_deliveries[i]] then
table.remove(stop.active_deliveries, i)
if message_level >= 1 then printmsg({"ltn-message.error-invalid-delivery", stop.entity.backer_name}) end
if debug_log then log("(UpdateStop) Removing invalid delivery from stop '"..tostring(stop.entity.backer_name).."': "..tostring(stop.active_deliveries[i])) end
end
end
-- reset stop parameters in case something goes wrong
stop.min_carriages = 0
stop.max_carriages = 0
stop.max_trains = 0
stop.requesting_threshold = min_requested
stop.requester_priority = 0
stop.no_warnings = false
stop.providing_threshold = min_provided
stop.provider_priority = 0
stop.locked_slots = 0
stop.depot_priority = 0
-- skip short circuited stops
if detectShortCircuit(stop) then
stop.error_code = 1
if stop.parked_train_id and global.Dispatcher.availableTrains[stop.parked_train_id] then
remove_available_train(stop.parked_train_id)
end
setLamp(stop, ErrorCodes[stop.error_code], 1)
if debug_log then log("(UpdateStop) Short circuit error: "..stop.entity.backer_name) end
return
end
-- skip deactivated stops
local stopCB = stop.entity.get_control_behavior()
if stopCB and stopCB.disabled then
stop.error_code = 1
if stop.parked_train_id and global.Dispatcher.availableTrains[stop.parked_train_id] then
remove_available_train(stop.parked_train_id)
end
setLamp(stop, ErrorCodes[stop.error_code], 2)
if debug_log then log("(UpdateStop) Circuit deactivated stop: "..stop.entity.backer_name) end
return
end
-- initialize control signal values to defaults
local is_depot = false
local depot_priority = 0
local network_id = default_network
local min_carriages = 0
local max_carriages = 0
local max_trains = 0
local requesting_threshold = min_requested
local requesting_threshold_stacks = 0
local requester_priority = 0
local no_warnings = false
local providing_threshold = min_provided
local providing_threshold_stacks = 0
local provider_priority = 0
local locked_slots = 0
-- get circuit values 0.16.24
local signals = stop.input.get_merged_signals()
if not signals then return end -- either lamp and lampctrl are not connected or lampctrl has no output signal
local signals_filtered = {}
local signal_type_virtual = "virtual"
local abs = math.abs
for _,v in pairs(signals) do
if v.signal.name and v.signal.type then
if v.signal.type ~= signal_type_virtual then
-- add item and fluid signals to new array
signals_filtered[v.signal] = v.count
elseif ControlSignals[v.signal.name] then
-- read out control signals
if v.signal.name == ISDEPOT and v.count > 0 then
is_depot = true
elseif v.signal.name == DEPOT_PRIORITY then
depot_priority = v.count
elseif v.signal.name == NETWORKID then
network_id = v.count
elseif v.signal.name == MINTRAINLENGTH and v.count > 0 then
min_carriages = v.count
elseif v.signal.name == MAXTRAINLENGTH and v.count > 0 then
max_carriages = v.count
elseif v.signal.name == MAXTRAINS and v.count > 0 then
max_trains = v.count
elseif v.signal.name == REQUESTED_THRESHOLD then
requesting_threshold = abs(v.count)
elseif v.signal.name == REQUESTED_STACK_THRESHOLD then
requesting_threshold_stacks = abs(v.count)
elseif v.signal.name == REQUESTED_PRIORITY then
requester_priority = v.count
elseif v.signal.name == NOWARN and v.count > 0 then
no_warnings = true
elseif v.signal.name == PROVIDED_THRESHOLD then
providing_threshold = abs(v.count)
elseif v.signal.name == PROVIDED_STACK_THRESHOLD then
providing_threshold_stacks = abs(v.count)
elseif v.signal.name == PROVIDED_PRIORITY then
provider_priority = v.count
elseif v.signal.name == LOCKEDSLOTS and v.count > 0 then
locked_slots = v.count
end
end
end
end
local network_id_string = format("0x%x", band(network_id))
--update lamp colors when error_code or is_depot changed state
if stop.error_code ~=0 or stop.is_depot ~= is_depot then
stop.error_code = 0 -- we are error free here
if is_depot then
if stop.parked_train_id and stop.parked_train.valid then
if global.Dispatcher.Deliveries[stop.parked_train_id] then
setLamp(stop, "yellow", 1)
else
setLamp(stop, "blue", 1)
end
else
setLamp(stop, "green", 1)
end
else
if #stop.active_deliveries > 0 then
if stop.parked_train_id and stop.parked_train.valid then
setLamp(stop, "blue", #stop.active_deliveries)
else
setLamp(stop, "yellow", #stop.active_deliveries)
end
else
setLamp(stop, "green", 1)
end
end
end
-- check if it's a depot
if is_depot then
stop.is_depot = true
stop.depot_priority = depot_priority
stop.network_id = network_id
-- add parked train to available trains
if stop.parked_train_id and stop.parked_train.valid then
if global.Dispatcher.Deliveries[stop.parked_train_id] then
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."}"..
", depot priority: ".. depot_priority..
", assigned train.id: "..stop.parked_train_id )
end
else
if not global.Dispatcher.availableTrains[stop.parked_train_id] then
-- full arrival handling in case ltn-depot signal was turned on with an already parked train
TrainArrives(stop.parked_train)
else
-- update properties from depot
global.Dispatcher.availableTrains[stop.parked_train_id].network_id = network_id
global.Dispatcher.availableTrains[stop.parked_train_id].depot_priority = depot_priority
end
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."}"..
", depot priority: ".. depot_priority..
", available train.id: "..stop.parked_train_id )
end
end
else
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."}"..
", depot priority: ".. depot_priority..
", no available train" )
end
end
-- not a depot > check if the name is unique
else
stop.is_depot = false
if stop.parked_train_id and global.Dispatcher.availableTrains[stop.parked_train_id] then
remove_available_train(stop.parked_train_id)
end
for signal, count in pairs(signals_filtered) do
local signal_type = signal.type
local signal_name = signal.name
local item = signal_type..","..signal_name
for trainID, delivery in pairs (global.Dispatcher.Deliveries) do
local deliverycount = delivery.shipment[item]
if deliverycount then
if stop.parked_train and stop.parked_train_id == trainID then
-- calculate items +- train inventory
local traincount = 0
if signal_type == "fluid" then
traincount = stop.parked_train.get_fluid_count(signal_name)
else
traincount = stop.parked_train.get_item_count(signal_name)
end
if delivery.to_id == stop.entity.unit_number then
local newcount = count + traincount
if newcount > 0 then newcount = 0 end --make sure we don't turn it into a provider
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} updating requested count with train "..trainID.." inventory: "..item.." "..count.."+"..traincount.."="..newcount) end
count = newcount
elseif delivery.from_id == stop.entity.unit_number then
if traincount <= deliverycount then
local newcount = count - (deliverycount - traincount)
if newcount < 0 then newcount = 0 end --make sure we don't turn it into a request
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} updating provided count with train "..trainID.." inventory: "..item.." "..count.."-"..deliverycount - traincount.."="..newcount) end
count = newcount
else --train loaded more than delivery
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} updating delivery count with overloaded train "..trainID.." inventory: "..item.." "..traincount) end
-- update delivery to new size
global.Dispatcher.Deliveries[trainID].shipment[item] = traincount
end
end
else
-- calculate items +- deliveries
if delivery.to_id == stop.entity.unit_number then
local newcount = count + deliverycount
if newcount > 0 then newcount = 0 end --make sure we don't turn it into a provider
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} updating requested count with delivery: "..item.." "..count.."+"..deliverycount.."="..newcount) end
count = newcount
elseif delivery.from_id == stop.entity.unit_number and not delivery.pickupDone then
local newcount = count - deliverycount
if newcount < 0 then newcount = 0 end --make sure we don't turn it into a request
if debug_log then log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} updating provided count with delivery: "..item.." "..count.."-"..deliverycount.."="..newcount) end
count = newcount
end
end
end
end -- for delivery
local useProvideStackThreshold = false
local useRequestStackThreshold = false
local stack_count = 0
if signal_type == "item" then
useProvideStackThreshold = providing_threshold_stacks > 0
useRequestStackThreshold = requesting_threshold_stacks > 0
if game.item_prototypes[signal_name] then
stack_count = count / game.item_prototypes[signal_name].stack_size
end
end
-- update Dispatcher Storage
-- Providers are used when above Provider Threshold
-- Requests are handled when above Requester Threshold
if (useProvideStackThreshold and stack_count >= providing_threshold_stacks) or
(not useProvideStackThreshold and count >= providing_threshold) then
global.Dispatcher.Provided[item] = global.Dispatcher.Provided[item] or {}
global.Dispatcher.Provided[item][stopID] = count
global.Dispatcher.Provided_by_Stop[stopID] = global.Dispatcher.Provided_by_Stop[stopID] or {}
global.Dispatcher.Provided_by_Stop[stopID][item] = count
if debug_log then
local trainsEnRoute = "";
for k,v in pairs(stop.active_deliveries) do
trainsEnRoute=trainsEnRoute.." "..v
end
log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} provides "..item.." "..count.."("..providing_threshold..")".." stacks: "..stack_count.."("..providing_threshold_stacks..")"..", priority: "..provider_priority..", min length: "..min_carriages..", max length: "..max_carriages..", trains en route: "..trainsEnRoute)
end
elseif (useRequestStackThreshold and stack_count*-1 >= requesting_threshold_stacks) or
(not useRequestStackThreshold and count*-1 >= requesting_threshold) then
count = count * -1
local ageIndex = item..","..stopID
global.Dispatcher.RequestAge[ageIndex] = global.Dispatcher.RequestAge[ageIndex] or game.tick
global.Dispatcher.Requests[#global.Dispatcher.Requests+1] = {age = global.Dispatcher.RequestAge[ageIndex], stopID = stopID, priority = requester_priority, item = item, count = count}
global.Dispatcher.Requests_by_Stop[stopID] = global.Dispatcher.Requests_by_Stop[stopID] or {}
global.Dispatcher.Requests_by_Stop[stopID][item] = count
if debug_log then
local trainsEnRoute = "";
for k,v in pairs(stop.active_deliveries) do
trainsEnRoute=trainsEnRoute.." "..v
end
log("(UpdateStop) "..stop.entity.backer_name.." {"..network_id_string.."} requests "..item.." "..count.."("..requesting_threshold..")".." stacks: "..tostring(stack_count*-1).."("..requesting_threshold_stacks..")"..", priority: "..requester_priority..", min length: "..min_carriages..", max length: "..max_carriages..", age: "..global.Dispatcher.RequestAge[ageIndex].."/"..game.tick..", trains en route: "..trainsEnRoute)
end
end
end -- for circuitValues
stop.network_id = network_id
stop.providing_threshold = providing_threshold
stop.providing_threshold_stacks = providing_threshold_stacks
stop.provider_priority = provider_priority
stop.requesting_threshold = requesting_threshold
stop.requesting_threshold_stacks = requesting_threshold_stacks
stop.requester_priority = requester_priority
stop.min_carriages = min_carriages
stop.max_carriages = max_carriages
stop.max_trains = max_trains
stop.locked_slots = locked_slots
stop.no_warnings = no_warnings
end
end
function setLamp(trainStop, color, count)
-- skip invalid stops and colors
if trainStop and trainStop.lamp_control.valid and ColorLookup[color] then
trainStop.lamp_control.get_control_behavior().parameters = {{index = 1, signal = {type="virtual",name=ColorLookup[color]}, count = count }}
return true
end
return false
end
function UpdateStopOutput(trainStop, ignore_existing_cargo)
-- skip invalid stop outputs
if not trainStop.output.valid then
return
end
local signals = {}
local index = 0
if trainStop.parked_train and trainStop.parked_train.valid then
-- get train composition
local carriages = trainStop.parked_train.carriages
local encoded_positions_by_name = {}
local encoded_positions_by_type = {}
local inventory = not(ignore_existing_cargo) and trainStop.parked_train.get_contents() or {}
local fluidInventory = not(ignore_existing_cargo) and trainStop.parked_train.get_fluid_contents() or {}
if #carriages < 32 then --prevent circuit network integer overflow error
if trainStop.parked_train_faces_stop then --train faces forwards >> iterate normal
for i=1, #carriages do
local signal_type = format("ltn-position-any-%s", carriages[i].type)
if game.virtual_signal_prototypes[signal_type] then
if encoded_positions_by_type[signal_type] then
encoded_positions_by_type[signal_type] = encoded_positions_by_type[signal_type] + 2^(i-1)
else
encoded_positions_by_type[signal_type] = 2^(i-1)
end
else
if message_level >= 1 then printmsg({"ltn-message.error-invalid-position-signal", signal_type}) end
log(format("Error: signal \"%s\" not found!", signal_type))
end
local signal_name = format("ltn-position-%s", carriages[i].name)
if game.virtual_signal_prototypes[signal_name] then
if encoded_positions_by_name[signal_name] then
encoded_positions_by_name[signal_name] = encoded_positions_by_name[signal_name] + 2^(i-1)
else
encoded_positions_by_name[signal_name] = 2^(i-1)
end
else
if message_level >= 1 then printmsg({"ltn-message.error-invalid-position-signal", signal_name}) end
log(format("Error: signal \"%s\" not found!", signal_name))
end
end
else --train faces backwards >> iterate backwards
n = 0
for i=#carriages, 1, -1 do
local signal_type = format("ltn-position-any-%s", carriages[i].type)
if game.virtual_signal_prototypes[signal_type] then
if encoded_positions_by_type[signal_type] then
encoded_positions_by_type[signal_type] = encoded_positions_by_type[signal_type] + 2^n
else
encoded_positions_by_type[signal_type] = 2^n
end
else
if message_level >= 1 then printmsg({"ltn-message.error-invalid-position-signal", signal_type}) end
log(format("Error: signal \"%s\" not found!", signal_type))
end
local signal_name = format("ltn-position-%s", carriages[i].name)
if game.virtual_signal_prototypes[signal_name] then
if encoded_positions_by_name[signal_name] then
encoded_positions_by_name[signal_name] = encoded_positions_by_name[signal_name] + 2^n
else
encoded_positions_by_name[signal_name] = 2^n
end
else
if message_level >= 1 then printmsg({"ltn-message.error-invalid-position-signal", signal_name}) end
log(format("Error: signal \"%s\" not found!", signal_name))
end
n=n+1
end
end
for k ,v in pairs (encoded_positions_by_type) do
index = index+1
table.insert(signals, {index = index, signal = {type="virtual",name=k}, count = v })
end
for k ,v in pairs (encoded_positions_by_name) do
index = index+1
table.insert(signals, {index = index, signal = {type="virtual",name=k}, count = v })
end
end
if not trainStop.is_depot then
-- Update normal stations
local conditions = trainStop.parked_train.schedule.records[trainStop.parked_train.schedule.current].wait_conditions
if conditions ~= nil then
for _, c in pairs(conditions) do
if c.condition and c.condition.first_signal then -- loading without mods can make first signal nil?
if c.type == "item_count" then
if (c.condition.comparator == "=" and c.condition.constant == 0) then
--train expects to be unloaded of each of this item
inventory[c.condition.first_signal.name] = nil
elseif c.condition.comparator == "" then
--train expects to be loaded to x of this item
inventory[c.condition.first_signal.name] = c.condition.constant
end
elseif c.type == "fluid_count" then
if (c.condition.comparator == "=" and c.condition.constant == 0) then
--train expects to be unloaded of each of this fluid
fluidInventory[c.condition.first_signal.name] = -1
elseif c.condition.comparator == "" then
--train expects to be loaded to x of this fluid
fluidInventory[c.condition.first_signal.name] = c.condition.constant
end
end
end
end
end
-- output expected inventory contents
for k,v in pairs(inventory) do
index = index+1
table.insert(signals, {index = index, signal = {type="item", name=k}, count = v})
end
for k,v in pairs(fluidInventory) do
index = index+1
table.insert(signals, {index = index, signal = {type="fluid", name=k}, count = v})
end
end -- not trainStop.is_depot
end
-- will reset if called with no parked train
if index > 0 then
-- log("[LTN] "..tostring(trainStop.entity.backer_name).. " displaying "..#signals.."/"..tostring(trainStop.output.get_control_behavior().signals_count).." signals.")
while #signals > trainStop.output.get_control_behavior().signals_count do
-- log("[LTN] removing signal "..tostring(signals[#signals].signal.name))
table.remove(signals)
end
if index ~= #signals then
if message_level >= 1 then printmsg({"ltn-message.error-stop-output-truncated", tostring(trainStop.entity.backer_name), tostring(trainStop.parked_train), trainStop.output.get_control_behavior().signals_count, index-#signals}, trainStop.entity.force) end
if debug_log then log("(UpdateStopOutput) Inventory of train "..tostring(trainStop.parked_train.id).." at stop "..tostring(trainStop.entity.backer_name).." exceeds stop output limit of "..trainStop.output.get_control_behavior().signals_count.." by "..index-#signals.." signals.") end
end
trainStop.output.get_control_behavior().parameters = signals
if debug_log then log("(UpdateStopOutput) Updating signals for "..tostring(trainStop.entity.backer_name)..": train "..tostring(trainStop.parked_train.id)..": "..index.." signals") end
else
trainStop.output.get_control_behavior().parameters = nil
if debug_log then log("(UpdateStopOutput) Resetting signals for "..tostring(trainStop.entity.backer_name)..".") end
end
end