mirror of
https://github.com/Xevion/project-cybersyn.git
synced 2025-12-06 13:16:04 -06:00
473 lines
15 KiB
Lua
473 lines
15 KiB
Lua
--By Mami
|
|
local get_distance = require("__flib__.misc").get_distance
|
|
local abs = math.abs
|
|
local floor = math.floor
|
|
|
|
|
|
---@param map_data MapData
|
|
---@param item_name string
|
|
function get_stack_size(map_data, item_name)
|
|
return game.item_prototypes[item_name].stack_size
|
|
end
|
|
|
|
|
|
---@param entity0 LuaEntity
|
|
---@param entity1 LuaEntity
|
|
function get_stop_dist(entity0, entity1)
|
|
local surface0 = entity0.surface.index
|
|
local surface1 = entity1.surface.index
|
|
return (surface0 == surface1 and get_distance(entity0.position, entity1.position) or DIFFERENT_SURFACE_DISTANCE)
|
|
end
|
|
|
|
|
|
---@param surface LuaSurface
|
|
local function se_get_space_elevator_name(surface)
|
|
--TODO: check how expensive the following is and potentially cache it's results
|
|
local entity = surface.find_entities_filtered({
|
|
name = SE_ELEVATOR_STOP_PROTO_NAME,
|
|
type = "train-stop",
|
|
limit = 1,
|
|
})[1]
|
|
if entity and entity.valid then
|
|
return string.sub(entity.backer_name, 1, string.len(entity.backer_name) - SE_ELEVATOR_SUFFIX_LENGTH)
|
|
end
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
--[[train schedules]]--
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
local create_loading_order_condition = {type = "inactivity", compare_type = "and", ticks = INACTIVITY_TIME}
|
|
---@param stop LuaEntity
|
|
---@param manifest Manifest
|
|
function create_loading_order(stop, manifest)
|
|
local condition = {}
|
|
for _, item in ipairs(manifest) do
|
|
local cond_type
|
|
if item.type == "fluid" then
|
|
cond_type = "fluid_count"
|
|
else
|
|
cond_type = "item_count"
|
|
end
|
|
|
|
condition[#condition + 1] = {
|
|
type = cond_type,
|
|
compare_type = "and",
|
|
condition = {comparator = "≥", first_signal = {type = item.type, name = item.name}, constant = item.count}
|
|
}
|
|
end
|
|
condition[#condition + 1] = create_loading_order_condition
|
|
return {station = stop.backer_name, wait_conditions = condition}
|
|
end
|
|
|
|
local create_unloading_order_condition = {{type = "empty", compare_type = "and"}}
|
|
---@param stop LuaEntity
|
|
function create_unloading_order(stop)
|
|
return {station = stop.backer_name, wait_conditions = create_unloading_order_condition}
|
|
end
|
|
|
|
local create_inactivity_order_condition = {{type = "inactivity", compare_type = "and", ticks = INACTIVITY_TIME}}
|
|
---@param depot_name string
|
|
function create_inactivity_order(depot_name)
|
|
return {station = depot_name, wait_conditions = create_inactivity_order_condition}
|
|
end
|
|
|
|
local create_direct_to_station_order_condition = {{type = "time", compare_type = "and", ticks = 1}}
|
|
---@param stop LuaEntity
|
|
function create_direct_to_station_order(stop)
|
|
return {rail = stop.connected_rail, rail_direction = stop.connected_rail_direction, wait_conditions = create_direct_to_station_order_condition}
|
|
end
|
|
|
|
---@param train LuaTrain
|
|
---@param depot_name string
|
|
function set_depot_schedule(train, depot_name)
|
|
train.schedule = {current = 1, records = {create_inactivity_order(depot_name)}}
|
|
end
|
|
|
|
---@param train LuaTrain
|
|
function lock_train(train)
|
|
train.manual_mode = true
|
|
end
|
|
|
|
---@param train LuaTrain
|
|
---@param stop LuaEntity
|
|
---@param old_name string
|
|
function rename_manifest_schedule(train, stop, old_name)
|
|
local new_name = stop.backer_name
|
|
local schedule = train.schedule
|
|
if not schedule then return end
|
|
for i, record in ipairs(schedule.records) do
|
|
if record.station == old_name then
|
|
record.station = new_name
|
|
end
|
|
end
|
|
train.schedule = schedule
|
|
end
|
|
|
|
---@param elevator_name string
|
|
---@param is_train_in_orbit boolean
|
|
local function se_create_elevator_order(elevator_name, is_train_in_orbit)
|
|
return {station = elevator_name..(is_train_in_orbit and SE_ELEVATOR_ORBIT_SUFFIX or SE_ELEVATOR_PLANET_SUFFIX)}
|
|
end
|
|
---@param train LuaTrain
|
|
---@param depot_name string
|
|
---@param d_surface_i int
|
|
---@param p_stop LuaEntity
|
|
---@param r_stop LuaEntity
|
|
---@param manifest Manifest
|
|
---@param start_at_depot boolean?
|
|
function set_manifest_schedule(train, depot_name, d_surface_i, p_stop, r_stop, manifest, start_at_depot)
|
|
--NOTE: can only return false if start_at_depot is false, it should be incredibly rare that this function returns false
|
|
local old_schedule
|
|
if not start_at_depot then
|
|
old_schedule = train.schedule
|
|
end
|
|
local t_surface = train.front_stock.surface
|
|
local p_surface = p_stop.surface
|
|
local r_surface = r_stop.surface
|
|
local t_surface_i = t_surface.index
|
|
local p_surface_i = p_surface.index
|
|
local r_surface_i = r_surface.index
|
|
if t_surface_i == p_surface_i and p_surface_i == r_surface_i then
|
|
train.schedule = {current = start_at_depot and 1 or 2, records = {
|
|
create_inactivity_order(depot_name),
|
|
create_direct_to_station_order(p_stop),
|
|
create_loading_order(p_stop, manifest),
|
|
create_direct_to_station_order(r_stop),
|
|
create_unloading_order(r_stop),
|
|
}}
|
|
if old_schedule and not train.has_path then
|
|
train.schedule = old_schedule
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
elseif IS_SE_PRESENT and (t_surface_i == p_surface_i or p_surface_i == r_surface_i or r_surface_i == t_surface_i) then
|
|
local t_zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = t_surface_i})
|
|
local other_zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = (t_surface_i == p_surface_i) and r_surface_i or p_surface_i})
|
|
local is_train_in_orbit = other_zone.orbit_index == t_zone.index
|
|
if is_train_in_orbit or t_zone.orbit_index == other_zone.index then
|
|
local elevator_name = se_get_space_elevator_name(t_surface)
|
|
if elevator_name then
|
|
local records = {create_inactivity_order(depot_name)}
|
|
if t_surface_i == p_surface_i then
|
|
records[#records + 1] = create_direct_to_station_order(p_stop)
|
|
else
|
|
records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit)
|
|
is_train_in_orbit = not is_train_in_orbit
|
|
end
|
|
records[#records + 1] = create_loading_order(p_stop, manifest)
|
|
if p_surface_i ~= r_surface_i then
|
|
records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit)
|
|
is_train_in_orbit = not is_train_in_orbit
|
|
end
|
|
records[#records + 1] = create_unloading_order(r_stop)
|
|
if r_surface_i ~= d_surface_i then
|
|
records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit)
|
|
is_train_in_orbit = not is_train_in_orbit
|
|
end
|
|
|
|
train.schedule = {current = start_at_depot and 1 or 2, records = records}
|
|
if old_schedule and not train.has_path then
|
|
train.schedule = old_schedule
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--NOTE: create a schedule that cannot be fulfilled, the train will be stuck but it will give the player information what went wrong
|
|
train.schedule = {current = 1, records = {
|
|
create_inactivity_order(depot_name),
|
|
create_loading_order(p_stop, manifest),
|
|
create_unloading_order(r_stop),
|
|
}}
|
|
lock_train(train)
|
|
send_lost_train_alert(train, depot_name)
|
|
return true
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
--[[combinators]]--
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
---@param comb LuaEntity
|
|
function get_comb_control(comb)
|
|
--NOTE: using this as opposed to get_comb_params gives you R/W access
|
|
return comb.get_or_create_control_behavior()--[[@as LuaArithmeticCombinatorControlBehavior]]
|
|
end
|
|
---@param comb LuaEntity
|
|
function get_comb_params(comb)
|
|
return comb.get_or_create_control_behavior().parameters--[[@as ArithmeticCombinatorParameters]]
|
|
end
|
|
---@param comb LuaEntity
|
|
function get_comb_gui_settings(comb)
|
|
local params = get_comb_params(comb)
|
|
local op = params.operation
|
|
|
|
local selected_index = 0
|
|
local switch_state = "none"
|
|
local bits = params.second_constant or 0
|
|
local allows_all_trains = bits%2 == 1
|
|
local is_pr_state = floor(bits/2)%3
|
|
if is_pr_state == 0 then
|
|
switch_state = "none"
|
|
elseif is_pr_state == 1 then
|
|
switch_state = "left"
|
|
elseif is_pr_state == 2 then
|
|
switch_state = "right"
|
|
end
|
|
|
|
if op == OPERATION_PRIMARY_IO or op == OPERATION_PRIMARY_IO_ACTIVE or op == OPERATION_PRIMARY_IO_FAILED_REQUEST then
|
|
selected_index = 1
|
|
elseif op == OPERATION_SECONDARY_IO then
|
|
selected_index = 2
|
|
elseif op == OPERATION_DEPOT then
|
|
selected_index = 3
|
|
elseif op == OPERATION_WAGON_MANIFEST then
|
|
selected_index = 4
|
|
end
|
|
return selected_index, params.first_signal, not allows_all_trains, switch_state
|
|
end
|
|
---@param comb LuaEntity
|
|
function get_comb_network_name(comb)
|
|
local params = get_comb_params(comb)
|
|
local signal = params.first_signal
|
|
|
|
return signal and signal.name or nil
|
|
end
|
|
---@param station Station
|
|
function set_station_from_comb_state(station)
|
|
--NOTE: this does nothing to update currently active deliveries
|
|
local params = get_comb_params(station.entity_comb1)
|
|
local bits = params.second_constant or 0
|
|
local is_pr_state = floor(bits/2)%3
|
|
local signal = params.first_signal
|
|
station.network_name = signal and signal.name or nil
|
|
station.allows_all_trains = bits%2 == 1
|
|
station.is_p = is_pr_state == 0 or is_pr_state == 1
|
|
station.is_r = is_pr_state == 0 or is_pr_state == 2
|
|
end
|
|
---@param map_data MapData
|
|
---@param unit_number uint
|
|
---@param params ArithmeticCombinatorParameters
|
|
local function has_comb_params_changed(map_data, unit_number, params)
|
|
local old_params = map_data.to_comb_params[unit_number]
|
|
|
|
if params.operation ~= old_params.operation then
|
|
if (old_params.operation == OPERATION_PRIMARY_IO) and (params.operation == OPERATION_PRIMARY_IO_ACTIVE or params.operation == OPERATION_PRIMARY_IO_FAILED_REQUEST) then
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
local new_signal = params.first_signal
|
|
local old_signal = old_params.first_signal
|
|
local new_network = new_signal and new_signal.name or nil
|
|
local old_network = old_signal and old_signal.name or nil
|
|
if new_network ~= old_network then
|
|
return true
|
|
end
|
|
if params.second_constant ~= old_params.second_constant then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
---@param map_data MapData
|
|
---@param station Station
|
|
function update_display(map_data, station)
|
|
local comb = station.entity_comb1
|
|
if comb.valid then
|
|
local unit_number = comb.unit_number--[[@as uint]]
|
|
local control = get_comb_control(comb)
|
|
local params = control.parameters
|
|
if not has_comb_params_changed(map_data, unit_number, params) then
|
|
if station.display_state >= 2 then
|
|
params.operation = OPERATION_PRIMARY_IO_ACTIVE
|
|
elseif station.display_state == 1 then
|
|
params.operation = OPERATION_PRIMARY_IO_FAILED_REQUEST
|
|
else
|
|
params.operation = OPERATION_PRIMARY_IO
|
|
end
|
|
control.parameters = params
|
|
end
|
|
end
|
|
end
|
|
---@param comb LuaEntity
|
|
---@param allows_all_trains boolean
|
|
function set_comb_allows_all_trains(comb, allows_all_trains)
|
|
local control = get_comb_control(comb)
|
|
local param = control.parameters
|
|
local bits = param.second_constant or 0
|
|
param.second_constant = (bits - bits%2) + (allows_all_trains and 1 or 0)
|
|
control.parameters = param
|
|
end
|
|
---@param comb LuaEntity
|
|
---@param is_pr_state 0|1|2
|
|
function set_comb_is_pr_state(comb, is_pr_state)
|
|
local control = get_comb_control(comb)
|
|
local param = control.parameters
|
|
local bits = param.second_constant or 0
|
|
param.second_constant = (bits%2) + (2*is_pr_state)
|
|
control.parameters = param
|
|
end
|
|
---@param comb LuaEntity
|
|
---@param signal SignalID?
|
|
function set_comb_network_name(comb, signal)
|
|
local control = get_comb_control(comb)
|
|
local param = control.parameters
|
|
|
|
param.first_signal = signal
|
|
control.parameters = param
|
|
end
|
|
---@param comb LuaEntity
|
|
---@param op string
|
|
function set_comb_operation(comb, op)
|
|
local control = get_comb_control(comb)
|
|
local params = control.parameters
|
|
params.operation = op
|
|
control.parameters = params
|
|
end
|
|
|
|
|
|
---@param map_data MapData
|
|
---@param comb LuaEntity
|
|
---@param signals ConstantCombinatorParameters[]?
|
|
function set_combinator_output(map_data, comb, signals)
|
|
local out = map_data.to_output[comb.unit_number]
|
|
if out.valid then
|
|
out.get_or_create_control_behavior().parameters = signals
|
|
end
|
|
end
|
|
|
|
local DEFINES_WORKING = defines.entity_status.working
|
|
local DEFINES_LOW_POWER = defines.entity_status.low_power
|
|
local DEFINES_COMBINATOR_INPUT = defines.circuit_connector_id.combinator_input
|
|
---@param station Station
|
|
function get_signals(station)
|
|
--NOTE: the combinator must be valid, but checking for valid every time is too slow
|
|
local comb = station.entity_comb1
|
|
local status = comb.status
|
|
if status == DEFINES_WORKING or status == DEFINES_LOW_POWER then
|
|
return comb.get_merged_signals(DEFINES_COMBINATOR_INPUT)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
---@param map_data MapData
|
|
---@param station Station
|
|
function set_comb2(map_data, station)
|
|
if station.entity_comb2 then
|
|
local deliveries = station.deliveries
|
|
local signals = {}
|
|
for item_name, count in pairs(deliveries) do
|
|
local i = #signals + 1
|
|
local is_fluid = game.item_prototypes[item_name] == nil--NOTE: this is expensive
|
|
signals[i] = {index = i, signal = {type = is_fluid and "fluid" or "item", name = item_name}, count = -count}
|
|
end
|
|
set_combinator_output(map_data, station.entity_comb2, signals)
|
|
end
|
|
end
|
|
|
|
|
|
---@param map_data MapData
|
|
---@param station Station
|
|
---@param signal SignalID
|
|
function get_threshold(map_data, station, signal)
|
|
local comb2 = station.entity_comb2
|
|
if comb2 then
|
|
local count = comb2.get_merged_signal(signal, defines.circuit_connector_id.combinator_input)
|
|
if count ~= 0 then
|
|
return abs(count)
|
|
end
|
|
end
|
|
return station.r_threshold
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
--[[alerts]]--
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
local send_missing_train_alert_for_stop_icon = {name = MISSING_TRAIN_NAME, type = "fluid"}
|
|
---@param r_stop LuaEntity
|
|
---@param p_stop LuaEntity
|
|
function send_missing_train_alert_for_stops(r_stop, p_stop)
|
|
for _, player in pairs(r_stop.force.players) do
|
|
player.add_custom_alert(
|
|
r_stop,
|
|
send_missing_train_alert_for_stop_icon,
|
|
{"cybersyn-messages.missing-trains", r_stop.backer_name, p_stop.backer_name},
|
|
true)
|
|
end
|
|
end
|
|
|
|
local send_lost_train_alert_icon = {name = LOST_TRAIN_NAME, type = "fluid"}
|
|
---@param train LuaTrain
|
|
---@param depot_name string
|
|
function send_lost_train_alert(train, depot_name)
|
|
local loco = train.front_stock or train.back_stock
|
|
if loco then
|
|
for _, player in pairs(loco.force.players) do
|
|
player.add_custom_alert(
|
|
loco,
|
|
send_lost_train_alert_icon,
|
|
{"cybersyn-messages.lost-train", depot_name},
|
|
true)
|
|
player.play_sound({path = ALERT_SOUND})
|
|
end
|
|
end
|
|
end
|
|
---@param train LuaTrain
|
|
function send_unexpected_train_alert(train)
|
|
local loco = train.front_stock or train.back_stock
|
|
if loco then
|
|
for _, player in pairs(loco.force.players) do
|
|
player.add_custom_alert(
|
|
loco,
|
|
send_lost_train_alert_icon,
|
|
{"cybersyn-messages.unexpected-train"},
|
|
true)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local send_nonempty_train_in_depot_alert_icon = {name = NONEMPTY_TRAIN_NAME, type = "fluid"}
|
|
---@param train LuaTrain
|
|
function send_nonempty_train_in_depot_alert(train)
|
|
local loco = train.front_stock or train.back_stock
|
|
if loco then
|
|
for _, player in pairs(loco.force.players) do
|
|
player.add_custom_alert(
|
|
loco,
|
|
send_nonempty_train_in_depot_alert_icon,
|
|
{"cybersyn-messages.nonempty-train"},
|
|
true)
|
|
player.play_sound({path = ALERT_SOUND})
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local send_stuck_train_alert_icon = {name = LOST_TRAIN_NAME, type = "fluid"}
|
|
---@param train LuaTrain
|
|
---@param depot_name string
|
|
function send_stuck_train_alert(train, depot_name)
|
|
local loco = train.front_stock or train.back_stock
|
|
if loco then
|
|
for _, player in pairs(loco.force.players) do
|
|
player.add_custom_alert(
|
|
loco,
|
|
send_stuck_train_alert_icon,
|
|
{"cybersyn-messages.stuck-train", depot_name},
|
|
true)
|
|
end
|
|
end
|
|
end
|