1
Fork 0

Base functionality has been implemented

This commit is contained in:
Matei Adriel 2022-12-31 19:29:58 +01:00
commit 62e12a5204
14 changed files with 579 additions and 0 deletions

View 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
}
}
}

View 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

View file

@ -0,0 +1 @@
require("scripts.train_stop_events").setup()

View file

@ -0,0 +1,4 @@
require("prototypes.technologies")
require("prototypes.recipes")
require("prototypes.items")
require("prototypes.entities")

View 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"
}

View 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

View 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})

View 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})

View 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
}
})

View 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"
}
})

View 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

View 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

View 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

View file

@ -0,0 +1,3 @@
column_width = 80
indent_width = 2
indent_type = "Spaces"