314 lines
8.3 KiB
Lua
314 lines
8.3 KiB
Lua
|
local Entity = require("__stdlib__/stdlib/entity/entity")
|
||
|
local Direction = require("__stdlib__/stdlib/area/direction")
|
||
|
local Position = require("__stdlib__/stdlib/area/position")
|
||
|
local Area = require("__stdlib__/stdlib/area/area")
|
||
|
|
||
|
local H = require("scripts.helpers")
|
||
|
local C = require("common.constants")
|
||
|
|
||
|
-- local settings = require("scripts.settings")
|
||
|
|
||
|
local M = {}
|
||
|
|
||
|
---@class DispatcherTrainStopData
|
||
|
---@field entity LuaEntity
|
||
|
---@field input LuaEntity
|
||
|
|
||
|
---Gets the data attached to a dispatcher
|
||
|
---@param entity LuaEntity
|
||
|
---@return DispatcherTrainStopData|nil
|
||
|
---@nodiscard
|
||
|
local function get_dispatcher_data(entity)
|
||
|
if not global.tws_dispatchers then
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
return global.tws_dispatchers[entity.unit_number]
|
||
|
end
|
||
|
|
||
|
---Attaches some data to a dispatcher
|
||
|
---@param entity LuaEntity
|
||
|
---@param value DispatcherTrainStopData|nil
|
||
|
local function set_dispatcher_data(entity, value)
|
||
|
if not global.tws_dispatchers then
|
||
|
global.tws_dispatchers = {}
|
||
|
end
|
||
|
|
||
|
global.tws_dispatchers[entity.unit_number] = value
|
||
|
end
|
||
|
|
||
|
-- {{{ CreateStop
|
||
|
---Runs when a stop gets created
|
||
|
---@param entity LuaEntity
|
||
|
local function CreateStop(entity)
|
||
|
if get_dispatcher_data(entity) then
|
||
|
-- TODO: better logs
|
||
|
game.print("Duplicate unit number, wtf")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local stop_offset = C.stop_offsets[entity.name]
|
||
|
local offset_adjustment = (not stop_offset) and stop_offset
|
||
|
or Direction.to_vector(Direction.next(entity.direction), stop_offset)
|
||
|
-- local offset_adjustment = 0
|
||
|
---@type MapPosition
|
||
|
local input_position = Position(entity.position)
|
||
|
+ offset_adjustment
|
||
|
-- Moves the center we rotate around to the
|
||
|
-- center of the top-left block,
|
||
|
-- Position(0.5, 0.5) +
|
||
|
-- then rotates counterclockwise once,
|
||
|
-- and go 0.5 more in that direction
|
||
|
-- Direction.to_vector(
|
||
|
-- Direction.next(entity.direction, true), 0.5)
|
||
|
+ Direction.to_vector(entity.direction, 0.5)
|
||
|
|
||
|
local search_area = Area.shrink(Area.new({ input_position }), 0.001)
|
||
|
|
||
|
local input
|
||
|
|
||
|
-- {{{ 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 == C.input_item_name then
|
||
|
_, input = ghost.revive()
|
||
|
end
|
||
|
|
||
|
-- something has built I/O already (e.g.) Creative Mode Instant Blueprint
|
||
|
elseif ghost.name == C.input_item_name then
|
||
|
input = ghost
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
-- }}}
|
||
|
|
||
|
if input == nil then -- create new
|
||
|
input = entity.surface.create_entity({
|
||
|
name = C.input_item_name,
|
||
|
position = input_position,
|
||
|
force = entity.force,
|
||
|
})
|
||
|
end
|
||
|
|
||
|
if input == nil then
|
||
|
-- TODO: logging
|
||
|
game.print("Something went wrong")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
input.operable = false -- disable gui
|
||
|
input.minable = false
|
||
|
input.destructible = false -- don't bother checking if alive
|
||
|
|
||
|
---@type DispatcherTrainStopData
|
||
|
local stop_data = { entity = entity, input = input }
|
||
|
|
||
|
set_dispatcher_data(entity, stop_data)
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
-- {{{ OnEntityCreated
|
||
|
local 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 entity.name == C.item_name then
|
||
|
CreateStop(entity)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
-- {{{ RemoveStop
|
||
|
---Runs once a train stop has been removed.
|
||
|
---@param entity LuaEntity
|
||
|
---@param create_ghosts boolean
|
||
|
function RemoveStop(entity, create_ghosts)
|
||
|
local stop = get_dispatcher_data(entity)
|
||
|
|
||
|
-- {{{ Destroy io entities
|
||
|
if stop then
|
||
|
---@type LuaEntity
|
||
|
local input = stop.input
|
||
|
|
||
|
if input and input.valid then
|
||
|
if create_ghosts then
|
||
|
input.destructable = true
|
||
|
input.die()
|
||
|
else
|
||
|
input.destroy()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
-- }}}
|
||
|
|
||
|
-- Delete entity data
|
||
|
set_dispatcher_data(entity, nil)
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
-- {{{ OnEntityRemoved
|
||
|
---Runs when any kind of entity has been removed.
|
||
|
---@param event EventData.on_entity_died|EventData.script_raised_destroy|EventData.on_robot_pre_mined|EventData.on_pre_player_mined_item
|
||
|
---@param create_ghosts any
|
||
|
function OnEntityRemoved(event, create_ghosts)
|
||
|
local entity = event.entity
|
||
|
|
||
|
if not entity or not entity.valid then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if entity.name == C.item_name then
|
||
|
RemoveStop(entity, create_ghosts)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
-- {{{ Remove entity data when surfaces get removed.
|
||
|
---@param event EventData.on_pre_surface_deleted|EventData.on_pre_surface_cleared
|
||
|
function OnSurfaceRemoved(event)
|
||
|
-- stop references
|
||
|
local surfaceID = event.surface_index
|
||
|
local surface = game.surfaces[surfaceID]
|
||
|
|
||
|
if surface then
|
||
|
local train_stops = surface.find_entities_filtered({ type = "train-stop" })
|
||
|
|
||
|
for _, entity in pairs(train_stops) do
|
||
|
if entity.name == C.item_name then
|
||
|
RemoveStop(entity, false)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
-- {{{ UpdateSchedule
|
||
|
---Runs every tick to update train schedules
|
||
|
---@param stop DispatcherTrainStopData
|
||
|
function UpdateSchedule(stop)
|
||
|
local control = stop.input.get_or_create_control_behavior()
|
||
|
|
||
|
if not control or not control.valid then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local signals = {
|
||
|
action_create = { type = "virtual", name = "signal-C" },
|
||
|
action_jump = { type = "virtual", name = "signal-J" },
|
||
|
target_after = { type = "virtual", name = "signal-A" },
|
||
|
target_before = { type = "virtual", name = "signal-B" },
|
||
|
template_past = { type = "virtual", name = "signal-P" },
|
||
|
template_future = { type = "virtual", name = "signal-F" },
|
||
|
option_infinite = { type = "virtual", name = "signal-O" },
|
||
|
option_extend = { type = "virtual", name = "signal-E" },
|
||
|
}
|
||
|
|
||
|
local create = H.get_signal_count(control, signals.action_create)
|
||
|
local jump = H.get_signal_count(control, signals.action_jump)
|
||
|
|
||
|
local train = stop.entity.get_stopped_train()
|
||
|
|
||
|
if not train or not train.valid then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local schedule = train.schedule
|
||
|
|
||
|
if not schedule then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
schedule = H.copy(schedule)
|
||
|
|
||
|
if create > 0 then
|
||
|
local target_after = H.get_signal_count(control, signals.target_after)
|
||
|
local target_before = H.get_signal_count(control, signals.target_before)
|
||
|
local target_index = schedule.current + target_after - target_before
|
||
|
|
||
|
local template_future = H.get_signal_count(control, signals.template_future)
|
||
|
local template_past = H.get_signal_count(control, signals.template_past)
|
||
|
local template_index = schedule.current + template_future - template_past
|
||
|
|
||
|
local template = H.get_cycled(schedule.records, template_index)
|
||
|
local copy = H.copy(template)
|
||
|
|
||
|
local is_temporary = 0 == H.get_signal_count(control, signals.option_infinite)
|
||
|
copy.temporary = is_temporary
|
||
|
|
||
|
local extend = H.get_signal_count(control, signals.option_extend)
|
||
|
|
||
|
if extend > 0 and copy.station then
|
||
|
copy.station = copy.station .. " " .. tostring(extend)
|
||
|
end
|
||
|
|
||
|
H.list_insert(schedule.records, target_index, copy)
|
||
|
|
||
|
train.schedule = schedule
|
||
|
end
|
||
|
|
||
|
if jump > 0 and #schedule.records > 0 then
|
||
|
schedule.current = (schedule.current + jump - 1) % #schedule.records + 1
|
||
|
|
||
|
train.schedule = schedule
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
-- {{{ OnTick
|
||
|
function OnTick()
|
||
|
for _, v in pairs(global.tws_dispatchers or {}) do
|
||
|
UpdateSchedule(v)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- }}}
|
||
|
|
||
|
function M.setup()
|
||
|
local filters_on_built = { { filter = "type", type = "train-stop" } }
|
||
|
local filters_on_mined = { { filter = "type", type = "train-stop" } }
|
||
|
|
||
|
-- {{{ On create events
|
||
|
local on_create_events = {
|
||
|
defines.events.on_built_entity,
|
||
|
defines.events.on_robot_built_entity,
|
||
|
defines.events.script_raised_built,
|
||
|
defines.events.script_raised_revive,
|
||
|
defines.events.on_entity_cloned,
|
||
|
}
|
||
|
|
||
|
for _, event in pairs(on_create_events) do
|
||
|
script.on_event(event, OnEntityCreated, filters_on_built)
|
||
|
end
|
||
|
-- }}}
|
||
|
-- {{{ On remove events
|
||
|
local on_remove_events = {
|
||
|
defines.events.on_pre_player_mined_item,
|
||
|
defines.events.on_robot_pre_mined,
|
||
|
defines.events.script_raised_destroy,
|
||
|
}
|
||
|
|
||
|
for _, event in pairs(on_remove_events) do
|
||
|
script.on_event(event, OnEntityRemoved, filters_on_mined)
|
||
|
end
|
||
|
|
||
|
script.on_event(defines.events.on_entity_died, function(event)
|
||
|
OnEntityRemoved(event, true)
|
||
|
end, filters_on_mined)
|
||
|
-- }}}
|
||
|
-- {{{ On surface removed
|
||
|
script.on_event({
|
||
|
defines.events.on_pre_surface_deleted,
|
||
|
defines.events.on_pre_surface_cleared,
|
||
|
}, OnSurfaceRemoved)
|
||
|
-- }}}
|
||
|
|
||
|
script.on_event(defines.events.on_tick, OnTick)
|
||
|
end
|
||
|
|
||
|
return M
|