Base functionality has been implemented
This commit is contained in:
commit
62e12a5204
33
lua/prescient-trains/.neoconf.json
Normal file
33
lua/prescient-trains/.neoconf.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"lspconfig": {
|
||||
"sumneko_lua": {
|
||||
"Lua.workspace.library": [
|
||||
"/home/adrielus/Projects/Foreign/vscode-factoriomod-debug/sumneko-3rd/factorio/library",
|
||||
"/home/adrielus/.steam/steam/steamapps/common/Factorio/data"
|
||||
],
|
||||
"Lua.runtime.plugin": "/home/adrielus/Projects/Foreign/vscode-factoriomod-debug/sumneko-3rd/factorio/plugin.lua",
|
||||
"Lua.workspace.checkThirdParty": false,
|
||||
"Lua.runtime.version": "Lua 5.2",
|
||||
"Lua.runtime.special": {
|
||||
"__object_name": "type"
|
||||
},
|
||||
"Lua.runtime.builtin": {
|
||||
"io": "disable",
|
||||
"os": "disable",
|
||||
"coroutine": "disable",
|
||||
"package": "disable",
|
||||
"math": "disable",
|
||||
"debug": "disable"
|
||||
},
|
||||
"Lua.diagnostics.globals": [
|
||||
"__DebugAdapter",
|
||||
"__Profiler"
|
||||
]
|
||||
}
|
||||
},
|
||||
"neodev": {
|
||||
"library": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
11
lua/prescient-trains/common/constants.lua
Normal file
11
lua/prescient-trains/common/constants.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
local M = {}
|
||||
|
||||
M.item_name = "dispatcher-train-stop"
|
||||
M.input_item_name = M.item_name .. "-input"
|
||||
|
||||
-- Distance from rails for each stop type.
|
||||
-- Useful if, in the future, I decide to
|
||||
-- add support for cargo ships
|
||||
M.stop_offsets = { [M.item_name] = 0 }
|
||||
|
||||
return M
|
1
lua/prescient-trains/control.lua
Normal file
1
lua/prescient-trains/control.lua
Normal file
|
@ -0,0 +1 @@
|
|||
require("scripts.train_stop_events").setup()
|
4
lua/prescient-trains/data.lua
Normal file
4
lua/prescient-trains/data.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
require("prototypes.technologies")
|
||||
require("prototypes.recipes")
|
||||
require("prototypes.items")
|
||||
require("prototypes.entities")
|
9
lua/prescient-trains/info.json
Normal file
9
lua/prescient-trains/info.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "tinker-with-schedules",
|
||||
"version": "0.1.0",
|
||||
"title": "Tinker with schedules",
|
||||
"author": "Adriel",
|
||||
"factorio_version": "1.1",
|
||||
"dependencies": ["base >= 1.1", "stdlib >= 1.4"],
|
||||
"description": "Let's you use combinators to add temporary stops to train schedules"
|
||||
}
|
25
lua/prescient-trains/locale/en/en.cfg
Normal file
25
lua/prescient-trains/locale/en/en.cfg
Normal file
|
@ -0,0 +1,25 @@
|
|||
[mod-setting-name]
|
||||
|
||||
[mod-setting-description]
|
||||
|
||||
[entity-name]
|
||||
dispatcher-train-stop=Dispatcher
|
||||
dispatcher-train-stop-input=Dispatcher input
|
||||
|
||||
[entity-description]
|
||||
dispatcher-train-stop=Allows you to add temporary stops to train schedules using signals
|
||||
dispatcher-train-stop-input=Listens for signals coming from the exterior
|
||||
|
||||
[item-name]
|
||||
dispatcher-train-stop=Dispatcher
|
||||
dispatcher-train-stop-input=Dispatcher input
|
||||
|
||||
[item-description]
|
||||
dispatcher-train-stop=Allows you to add temporary stops to train schedules using signals
|
||||
dispatcher-train-stop-input=Uh, why is this here
|
||||
|
||||
[technology-name]
|
||||
tinker-with-schedules=Tinker with schedules
|
||||
|
||||
[technology-description]
|
||||
tinker-with-schedules=Allows you to add temporary stops to train schedules using signals
|
44
lua/prescient-trains/prototypes/entities.lua
Normal file
44
lua/prescient-trains/prototypes/entities.lua
Normal file
|
@ -0,0 +1,44 @@
|
|||
local C = require("common.constants")
|
||||
|
||||
local function copy(from, name)
|
||||
local copied = table.deepcopy(from)
|
||||
copied.name = name
|
||||
copied.minable.result = name
|
||||
|
||||
return copied
|
||||
end
|
||||
|
||||
-- Train stop
|
||||
-- {{{
|
||||
local dispatcher_train_stop = copy(data.raw["train-stop"]["train-stop"],
|
||||
C.item_name)
|
||||
|
||||
dispatcher_train_stop.color = {r = 0.46, g = 0.01, b = 0.98, a = 1}
|
||||
dispatcher_train_stop.next_upgrade = nil
|
||||
dispatcher_train_stop.selection_box = {{-0.9, -0.6}, {0.9, 0.9}}
|
||||
-- }}}
|
||||
|
||||
-- Input
|
||||
-- {{{
|
||||
local dispatcher_train_stop_in = copy(data.raw["lamp"]["small-lamp"],
|
||||
C.input_item_name)
|
||||
|
||||
dispatcher_train_stop_in.minable = nil
|
||||
dispatcher_train_stop_in.next_upgrade = nil
|
||||
|
||||
dispatcher_train_stop_in.selection_box = {{-0.5, -0.5}, {0.5, 0.5}}
|
||||
dispatcher_train_stop_in.selection_priority =
|
||||
(dispatcher_train_stop_in.selection_priority or 50) + 10
|
||||
|
||||
dispatcher_train_stop_in.collision_box = {{-0.15, -0.15}, {0.15, 0.15}}
|
||||
dispatcher_train_stop_in.collision_mask = {"rail-layer"} -- collide only with rail entities
|
||||
|
||||
dispatcher_train_stop_in.light = {intensity = 1, size = 6}
|
||||
dispatcher_train_stop_in.energy_source = {type = "void"}
|
||||
dispatcher_train_stop_in.energy_usage_per_tick = "10W"
|
||||
dispatcher_train_stop_in.selectable_in_game = true
|
||||
|
||||
dispatcher_train_stop_in.flags = {"placeable-off-grid", "player-creation"}
|
||||
-- }}}
|
||||
|
||||
data:extend({dispatcher_train_stop, dispatcher_train_stop_in})
|
24
lua/prescient-trains/prototypes/items.lua
Normal file
24
lua/prescient-trains/prototypes/items.lua
Normal file
|
@ -0,0 +1,24 @@
|
|||
local C = require("common.constants")
|
||||
|
||||
local item_dispatcher_train_stop =
|
||||
table.deepcopy(data.raw["item"]["train-stop"])
|
||||
|
||||
item_dispatcher_train_stop.name = C.item_name
|
||||
item_dispatcher_train_stop.order = item_dispatcher_train_stop.order .. "-c"
|
||||
item_dispatcher_train_stop.icon_size = 64
|
||||
item_dispatcher_train_stop.place_result = C.item_name
|
||||
item_dispatcher_train_stop.icons = {
|
||||
{
|
||||
icon = "__base__/graphics/icons/train-stop.png",
|
||||
tint = {r = 0.46, g = 0.01, b = 0.98, a = 0.1}
|
||||
}
|
||||
}
|
||||
|
||||
local item_dispatcher_train_stop_in = table.deepcopy(
|
||||
data.raw["item"]["small-lamp"])
|
||||
|
||||
item_dispatcher_train_stop_in.name = C.input_item_name
|
||||
item_dispatcher_train_stop_in.place_result = C.input_item_name
|
||||
item_dispatcher_train_stop_in.flags = {"hidden"}
|
||||
|
||||
data:extend({item_dispatcher_train_stop, item_dispatcher_train_stop_in})
|
14
lua/prescient-trains/prototypes/recipes.lua
Normal file
14
lua/prescient-trains/prototypes/recipes.lua
Normal file
|
@ -0,0 +1,14 @@
|
|||
local C = require("common.constants")
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "recipe",
|
||||
name = C.item_name,
|
||||
energy_required = 5,
|
||||
enabled = false,
|
||||
ingredients = {
|
||||
{"train-stop", 1}, {"electronic-circuit", 20}, {"copper-cable", 20}
|
||||
},
|
||||
result = C.item_name
|
||||
}
|
||||
})
|
20
lua/prescient-trains/prototypes/technologies.lua
Normal file
20
lua/prescient-trains/prototypes/technologies.lua
Normal file
|
@ -0,0 +1,20 @@
|
|||
local C = require("common.constants")
|
||||
|
||||
data:extend({
|
||||
{
|
||||
type = "technology",
|
||||
name = "tinker-with-schedules",
|
||||
icon_size = 256,
|
||||
icon = "__base__/graphics/technology/automated-rail-transportation.png",
|
||||
effects = { { type = "unlock-recipe", recipe = C.item_name } },
|
||||
prerequisites = { "automated-rail-transportation", "circuit-network" },
|
||||
unit = {
|
||||
count = 100,
|
||||
ingredients = {
|
||||
{ "automation-science-pack", 1 }, { "logistic-science-pack", 1 }
|
||||
},
|
||||
time = 15
|
||||
},
|
||||
order = "c-g-aa"
|
||||
}
|
||||
})
|
72
lua/prescient-trains/scripts/helpers.lua
Normal file
72
lua/prescient-trains/scripts/helpers.lua
Normal file
|
@ -0,0 +1,72 @@
|
|||
local M = {}
|
||||
|
||||
-- See [this original implementation](https://github.com/coltonj96/UsefulCombinators/blob/master/UsefulCombinators_0.4.4/control.lua#L2062)
|
||||
---Get the total count of some signal in some control behavior.
|
||||
---@param control LuaControlBehavior
|
||||
---@param signal SignalID
|
||||
function M.get_signal_count(control, signal)
|
||||
if not signal then
|
||||
return 0
|
||||
end
|
||||
|
||||
local red = control.get_circuit_network(defines.wire_type.red)
|
||||
local green = control.get_circuit_network(defines.wire_type.green)
|
||||
|
||||
local total = 0
|
||||
|
||||
if red then
|
||||
total = total + (red.get_signal(signal) or 0)
|
||||
end
|
||||
|
||||
if green then
|
||||
total = total + (green.get_signal(signal) or 0)
|
||||
end
|
||||
|
||||
return total
|
||||
end
|
||||
|
||||
---Returns the index if going past #array or under 1 would loop back the other side.
|
||||
---Assumes the array is nonempty
|
||||
---@generic T
|
||||
---@param array T[]
|
||||
---@param index integer
|
||||
---@returns integer
|
||||
---@nodiscard
|
||||
function M.mod_index(array, index)
|
||||
return (index - 1) % #array + 1
|
||||
end
|
||||
|
||||
---Uses the above function to normalize the index between the start and end of the array.
|
||||
---Assumes the array is nonempty
|
||||
---@generic T
|
||||
---@param array T[]
|
||||
---@param index integer
|
||||
---@return T
|
||||
---@nodiscard
|
||||
function M.get_cycled(array, index)
|
||||
return array[M.mod_index(array, index)]
|
||||
end
|
||||
|
||||
---Annotated version of table.inset
|
||||
---@generic T
|
||||
---@param list T[]
|
||||
---@param at integer
|
||||
---@param value T
|
||||
function M.list_insert(list, at, value)
|
||||
table.insert(list,at, value)
|
||||
end
|
||||
|
||||
---Shallow copies some table
|
||||
---@generic T :table
|
||||
---@param object T
|
||||
---@return T
|
||||
function M.copy(object)
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(object) do
|
||||
result[k] = v end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return M
|
6
lua/prescient-trains/scripts/settings.lua
Normal file
6
lua/prescient-trains/scripts/settings.lua
Normal file
|
@ -0,0 +1,6 @@
|
|||
local M = {}
|
||||
|
||||
M.message_level = tonumber(settings.global["tws-interface-console-level"].value)
|
||||
M.debug_log = settings.global["tws-interface-debug-logfile"].value
|
||||
|
||||
return M
|
313
lua/prescient-trains/scripts/train_stop_events.lua
Normal file
313
lua/prescient-trains/scripts/train_stop_events.lua
Normal file
|
@ -0,0 +1,313 @@
|
|||
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
|
3
lua/prescient-trains/stylua.toml
Normal file
3
lua/prescient-trains/stylua.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
column_width = 80
|
||||
indent_width = 2
|
||||
indent_type = "Spaces"
|
Loading…
Reference in a new issue