--[[ 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)