296 lines
11 KiB
Lua

--[[ Copyright (c) 2017 Optera
* Part of Logistics Train Network
*
* See LICENSE.md in the project directory for license information.
--]]
--create stop
function CreateStop(entity)
if global.LogisticTrainStops[entity.unit_number] then
if message_level >= 1 then printmsg({"ltn-message.error-duplicated-unit_number", entity.unit_number}, entity.force) end
if debug_log then log("(CreateStop) duplicate stop unit number "..entity.unit_number) end
return
end
local stop_offset = ltn_stop_entity_names[entity.name]
local posIn, posOut, rotOut, search_area
--log("Stop created at "..entity.position.x.."/"..entity.position.y..", orientation "..entity.direction)
if entity.direction == 0 then --SN
posIn = {entity.position.x + stop_offset, entity.position.y - 1}
posOut = {entity.position.x - 1 + stop_offset, entity.position.y - 1}
rotOut = 0
search_area = {
{entity.position.x + 0.001 - 1 + stop_offset, entity.position.y + 0.001 - 1},
{entity.position.x - 0.001 + 1 + stop_offset, entity.position.y - 0.001}
}
elseif entity.direction == 2 then --WE
posIn = {entity.position.x, entity.position.y + stop_offset}
posOut = {entity.position.x, entity.position.y - 1 + stop_offset}
rotOut = 2
search_area = {
{entity.position.x + 0.001, entity.position.y + 0.001 - 1 + stop_offset},
{entity.position.x - 0.001 + 1, entity.position.y - 0.001 + 1 + stop_offset}
}
elseif entity.direction == 4 then --NS
posIn = {entity.position.x - 1 - stop_offset, entity.position.y}
posOut = {entity.position.x - stop_offset, entity.position.y}
rotOut = 4
search_area = {
{entity.position.x + 0.001 - 1 - stop_offset, entity.position.y + 0.001},
{entity.position.x - 0.001 + 1 - stop_offset, entity.position.y - 0.001 + 1}
}
elseif entity.direction == 6 then --EW
posIn = {entity.position.x - 1, entity.position.y - 1 - stop_offset}
posOut = {entity.position.x - 1, entity.position.y - stop_offset}
rotOut = 6
search_area = {
{entity.position.x + 0.001 - 1, entity.position.y + 0.001 - 1 - stop_offset},
{entity.position.x - 0.001, entity.position.y - 0.001 + 1 - stop_offset}
}
else --invalid orientation
if message_level >= 1 then printmsg({"ltn-message.error-stop-orientation", tostring(entity.direction)}, entity.force) end
if debug_log then log("(CreateStop) invalid train stop orientation "..tostring(entity.direction) ) end
entity.destroy()
return
end
local input, output, lampctrl
-- handle blueprint ghosts and existing IO entities preserving circuit connections
local ghosts = entity.surface.find_entities(search_area)
for _,ghost in pairs (ghosts) do
if ghost.valid then
if ghost.name == "entity-ghost" then
if ghost.ghost_name == ltn_stop_input then
-- log("reviving ghost input at "..ghost.position.x..", "..ghost.position.y)
_, input = ghost.revive()
elseif ghost.ghost_name == ltn_stop_output then
-- log("reviving ghost output at "..ghost.position.x..", "..ghost.position.y)
_, output = ghost.revive()
elseif ghost.ghost_name == ltn_stop_output_controller then
-- log("reviving ghost lamp-control at "..ghost.position.x..", "..ghost.position.y)
_, lampctrl = ghost.revive()
end
-- something has built I/O already (e.g.) Creative Mode Instant Blueprint
elseif ghost.name == ltn_stop_input then
input = ghost
-- log("Found existing input at "..ghost.position.x..", "..ghost.position.y)
elseif ghost.name == ltn_stop_output then
output = ghost
-- log("Found existing output at "..ghost.position.x..", "..ghost.position.y)
elseif ghost.name == ltn_stop_output_controller then
lampctrl = ghost
-- log("Found existing lamp-control at "..ghost.position.x..", "..ghost.position.y)
end
end
end
if input == nil then -- create new
input = entity.surface.create_entity
{
name = ltn_stop_input,
position = posIn,
force = entity.force
}
end
input.operable = false -- disable gui
input.minable = false
input.destructible = false -- don't bother checking if alive
if lampctrl == nil then
lampctrl = entity.surface.create_entity
{
name = ltn_stop_output_controller,
position = {input.position.x + 0.45, input.position.y + 0.45}, -- slight offset so adjacent lamps won't connect
force = entity.force
}
-- log("building lamp-control at "..lampctrl.position.x..", "..lampctrl.position.y)
end
lampctrl.operable = false -- disable gui
lampctrl.minable = false
lampctrl.destructible = false -- don't bother checking if alive
-- connect lamp and control
lampctrl.get_control_behavior().parameters = {{index = 1, signal = {type="virtual",name="signal-white"}, count = 1 }}
input.connect_neighbour({target_entity=lampctrl, wire=defines.wire_type.green})
input.connect_neighbour({target_entity=lampctrl, wire=defines.wire_type.red})
input.get_or_create_control_behavior().use_colors = true
input.get_or_create_control_behavior().circuit_condition = {condition = {comparator=">",first_signal={type="virtual",name="signal-anything"}}}
if output == nil then -- create new
output = entity.surface.create_entity
{
name = ltn_stop_output,
position = posOut,
direction = rotOut,
force = entity.force
}
end
output.operable = false -- disable gui
output.minable = false
output.destructible = false -- don't bother checking if alive
-- enable reading contents and sending signals to trains
entity.get_or_create_control_behavior().send_to_train = true
entity.get_or_create_control_behavior().read_from_train = true
global.LogisticTrainStops[entity.unit_number] = {
entity = entity,
input = input,
output = output,
lamp_control = lampctrl,
parked_train = nil,
parked_train_id = nil,
active_deliveries = {}, --delivery IDs to/from stop
error_code = -1, --key to error_codes table
is_depot = false,
depot_priority = 0,
network_id = default_network,
min_carriages = 0,
max_carriages = 0,
max_trains = 0,
requesting_threshold = min_requested,
requesting_threshold_stacks = 0,
requester_priority = 0,
no_warnings = false,
providing_threshold = min_provided,
providing_threshold_stacks = 0,
provider_priority = 0,
locked_slots = 0,
}
UpdateStopOutput(global.LogisticTrainStops[entity.unit_number])
-- register events
-- script.on_event(defines.events.on_tick, OnTick)
script.on_nth_tick(nil)
script.on_nth_tick(dispatcher_nth_tick, OnTick)
script.on_event(defines.events.on_train_changed_state, OnTrainStateChanged)
script.on_event(defines.events.on_train_created, OnTrainCreated)
if debug_log then log("(OnEntityCreated) on_nth_tick("..dispatcher_nth_tick.."), on_train_changed_state, on_train_created registered") end
end
function OnEntityCreated(event)
local entity = event.created_entity or event.entity or event.destination
if not entity or not entity.valid then return end
if ltn_stop_entity_names[entity.name] then
CreateStop(entity)
end
end
-- stop removed
function RemoveStop(stopID, create_ghosts)
local stop = global.LogisticTrainStops[stopID]
-- clean lookup tables
for k,v in pairs(global.StopDistances) do
if k:find(stopID) then
global.StopDistances[k] = nil
end
end
-- remove available train
if stop and stop.is_depot and stop.parked_train_id and global.Dispatcher.availableTrains[stop.parked_train_id] then
global.Dispatcher.availableTrains_total_capacity = global.Dispatcher.availableTrains_total_capacity - global.Dispatcher.availableTrains[stop.parked_train_id].capacity
global.Dispatcher.availableTrains_total_fluid_capacity = global.Dispatcher.availableTrains_total_fluid_capacity - global.Dispatcher.availableTrains[stop.parked_train_id].fluid_capacity
global.Dispatcher.availableTrains[stop.parked_train_id] = nil
end
-- destroy IO entities, broken IO entities should be sufficiently handled in initializeTrainStops()
if stop then
if stop.input and stop.input.valid then
if create_ghosts then
stop.input.destructible = true
stop.input.die()
else
stop.input.destroy()
end
end
if stop.output and stop.output.valid then
if create_ghosts then
stop.output.destructible = true
stop.output.die()
else
stop.output.destroy()
end
end
if stop.lamp_control and stop.lamp_control.valid then stop.lamp_control.destroy() end
end
global.LogisticTrainStops[stopID] = nil
if not next(global.LogisticTrainStops) then
-- reset tick indexes
global.tick_state = 0
global.tick_stop_index = nil
global.tick_request_index = nil
-- unregister events
script.on_nth_tick(nil)
script.on_event(defines.events.on_train_changed_state, nil)
script.on_event(defines.events.on_train_created, nil)
if debug_log then log("(OnEntityRemoved) Removed last LTN Stop: on_nth_tick, on_train_changed_state, on_train_created unregistered") end
end
end
function OnEntityRemoved(event, create_ghosts)
local entity = event.entity
if not entity or not entity.valid then return end
if entity.train then
local trainID = entity.train.id
-- remove from stop if parked
if global.StoppedTrains[trainID] then
TrainLeaves(trainID)
end
-- removing any carriage fails a delivery
-- otherwise I'd have to handle splitting and merging a delivery across train parts
local delivery = global.Dispatcher.Deliveries[trainID]
if delivery then
script.raise_event(on_delivery_failed_event, {train_id = trainID, shipment = delivery.shipment})
RemoveDelivery(trainID)
end
elseif ltn_stop_entity_names[entity.name] then
RemoveStop(entity.unit_number, create_ghosts)
end
end
--rename stop
local function renamedStop(targetID, old_name, new_name)
-- find identical stop names
local duplicateName = false
local renameDeliveries = true
for stopID, stop in pairs(global.LogisticTrainStops) do
if not stop.entity.valid or not stop.input.valid or not stop.output.valid or not stop.lamp_control.valid then
RemoveStop(stopID)
elseif stop.entity.backer_name == old_name then
renameDeliveries = false
end
end
-- rename deliveries only if no other LTN stop old_name exists
if renameDeliveries then
if debug_log then log("(OnEntityRenamed) last LTN stop "..old_name.." renamed, updating deliveries to "..new_name..".") end
for trainID, delivery in pairs(global.Dispatcher.Deliveries) do
if delivery.to == old_name then
delivery.to = new_name
end
if delivery.from == old_name then
delivery.from = new_name
end
end
end
end
script.on_event(defines.events.on_entity_renamed, function(event)
local uid = event.entity.unit_number
local oldName = event.old_name
local newName = event.entity.backer_name
if ltn_stop_entity_names[event.entity.name] then
renamedStop(uid, oldName, newName)
end
end)