Первый фикс

Пачки некоторых позиций увеличены
This commit is contained in:
2024-03-01 20:53:32 +03:00
commit 7c9c708c92
23653 changed files with 767936 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
--- Configuration changed event handling.
-- This module registers events
-- @module Event.Changes
-- @usage
-- local Changes = require('__stdlib__/stdlib/event/changes')
-- Changes.register('mod_versions', 'path_to_version_file')
-- @usage
-- -- version files should return a dictionary of functions indexed by version number.
-- return {['1.0.0'] = function() end}
-- @usage
-- -- Other change files should return a single function and will run in the order they are added.
-- -- Multiple files can be registered to a change type.
-- Changes.register('any-first', 'path_to_file_1')
-- Changes.register('any-first', 'path_to_file_2')
local Event = require('__stdlib__/stdlib/event/event')
---@class Changes
---@field map_first table
---@field any_first table
---@field mod_first table
---@field mod_versions table
---@field mod_last table
---@field any_last table
---@field map_last table
---@field get_file_path function
local Changes = {
__class = 'Changes',
__index = require('__stdlib__/stdlib/core'),
registered_for_events = false
}
setmetatable(Changes, Changes)
local inspect = _ENV.inspect
--[[
ConfigurationChangedData
Table with the following fields:
old_version :: string (optional): Old version of the map. Present only when loading map version other than the current version.
new_version :: string (optional): New version of the map. Present only when loading map version other than the current version.
mod_changes :: dictionary string → ModConfigurationChangedData: Dictionary of mod changes. It is indexed by mod name.
ModConfigurationChangedData
Table with the following fields:
old_version :: string: Old version of the mod. May be nil if the mod wasn't previously present (i.e. it was just added).
new_version :: string: New version of the mod. May be nil if the mod is no longer present (i.e. it was just removed).
--]]
local table = require('__stdlib__/stdlib/utils/table')
local map_changes = {
['map_first'] = true,
['any_first'] = true,
['mod_first'] = true,
['mod_versions'] = true,
['mod_last'] = true,
['any_last'] = true,
['map_last'] = true
}
for change_type in pairs(map_changes) do
Changes[change_type] = {}
end
local function run_if_exists(path)
for _, fun in pairs(path) do
if type(fun) == 'function' then
fun()
end
end
end
function Changes.register_events(change_type, path)
if map_changes[change_type] then
if not Changes.registered_for_events then
Event.register(Event.core_events.configuration_changed, Changes.on_configuration_changed)
if change_type == 'mod_versions' then
-- Register on_init only for mod_versions changes
Event.register(Event.core_events.init, Changes.on_init)
end
end
Changes[change_type][path] = require(path)
else
error('Incorrect change type ' .. (change_type or 'nil') .. ' expected: ' .. table.concat(table.keys(map_changes), ', ') .. '.')
end
return Changes
end
Changes.register = Changes.register_events
function Changes.register_versions(path)
return Changes.register_events('mod_versions', path)
end
-- Mark all version changes as complete during Init
function Changes.on_init()
for _, versions in pairs(Changes.mod_versions) do
local list = {}
local cur_version = game.active_mods[script.mod_name]
for ver in pairs(versions) do
list[ver] = cur_version
end
global._changes = list
end
end
function Changes.on_configuration_changed(event)
run_if_exists(Changes.map_first)
if event.mod_changes then
run_if_exists(Changes.any_first)
if event.mod_changes[script.mod_name] then
run_if_exists(Changes.mod_first)
local this_mod_changes = event.mod_changes[script.mod_name]
Changes.on_mod_changed(this_mod_changes)
log(script.mod_name .. ': version changed from ' .. tostring(this_mod_changes.old_version) .. ' to ' .. tostring(this_mod_changes.new_version))
run_if_exists(Changes.mod_last)
end
run_if_exists(Changes.any_last)
end
run_if_exists(Changes.map_last)
end
function Changes.on_mod_changed(this_mod_changes)
global._changes = global._changes or {}
local old = this_mod_changes.old_version
if old then -- Find the last installed version
local versions = {}
for _, path in pairs(Changes.mod_versions) do
for ver, fun in pairs(path) do
if not global._changes[ver] then
versions[ver] = this_mod_changes.new_version
fun()
log('Migration completed for version ' .. ver)
end
end
end
table.each(
versions,
function(v, k)
global._changes[k] = v
end
)
end
end
function Changes.dump_data()
for change_type in pairs(map_changes) do
if table.size(Changes[change_type]) > 0 then
game.write_file(Changes.get_file_path('Changes/' .. change_type .. '.lua'),
'return ' .. inspect(Changes[change_type], { longkeys = true, arraykeys = true }))
end
end
game.write_file(Changes.get_file_path('Changes/global.lua'), 'return ' .. inspect(global._changes or nil, { longkeys = true, arraykeys = true }))
end
return Changes

View File

@@ -0,0 +1,529 @@
--- Makes working with events in Factorio a lot more simple.
-- <p>By default, Factorio allows you to register **only one handler** to an event.
-- <p>This module lets you easily register **multiple handlers** to an event.
-- <p>Using this module is as simple as replacing @{LuaBootstrap.on_event|script.on_event} with @{Event.register}.
-- <blockquote>
-- Due to the way that Factorio's event system works, it is not recommended to intermingle `script.on_event` and `Event.register` in a mod.
-- <br>This module hooks into Factorio's event system, and using `script.on_event` for the same event will change which events are registered.
-- </blockquote>
-- <blockquote>
-- This module does not have many of the multiplayer protections that `script.on_event` does.
-- <br>Due to this, great care should be taken when registering events conditionally.
-- </blockquote>
-- @module Event.Event
-- @usage local Event = require('__stdlib__/stdlib/event/event')
local config = require('__stdlib__/stdlib/config')
config.control = true
local Event = {
__class = 'Event',
registry = {}, -- Holds registered events
custom_events = {}, -- Holds custom event ids
stop_processing = {}, -- just has to be unique
Filters = require('__stdlib__/stdlib/event/modules/event_filters'),
__index = require('__stdlib__/stdlib/core')
}
setmetatable(Event, Event)
Event.options = {
protected_mode = false,
skip_valid = false,
force_crc = false -- Requires debug_mode to be true
}
local Event_options_meta = { __index = Event.options }
Event.core_events = {
on_init = 'on_init',
on_load = 'on_load',
on_configuration_changed = 'on_configuration_changed',
init = 'on_init',
load = 'on_load',
configuration_changed = 'on_configuration_changed',
init_and_config = { 'on_init', 'on_configuration_changed' },
init_and_load = { 'on_init', 'on_load' }
}
Event.script = {
on_event = script.on_event,
on_nth_tick = script.on_nth_tick,
on_init = script.on_init,
on_load = script.on_load,
on_configuration_changed = script.on_configuration_changed,
generate_event_name = script.generate_event_name,
get_event_handler = script.get_event_handler
}
local Type = require('__stdlib__/stdlib/utils/type')
local table = require('__stdlib__/stdlib/utils/table')
local assert, type, tonumber = assert, type, tonumber
local event_names = table.invert(defines.events)
if not config.skip_script_protections then -- Protections for post and pre registrations
for _, define in pairs(defines.events) do
if Event.script.get_event_handler(define) then
error('Detected attempt to add the STDLIB event module after using script.on_event')
end
end
for name in pairs(Event.script) do
_G.script[name] = function()
error('Detected attempt to register an event using script.' .. name .. ' while using the STDLIB event system ')
end
end
end
local bootstrap_events = {
on_init = function()
Event.dispatch { name = 'on_init' }
end,
on_load = function()
Event.dispatch { name = 'on_load', tick = -1 }
end,
on_configuration_changed = function(event)
event.name = 'on_configuration_changed'
Event.dispatch(event)
end
}
local function valid_id(id)
local id_type = type(id)
return (id_type == 'number' or id_type == 'string'), 'Invalid Event Id, Must be string/int/defines.events, Passed in: ' .. type(id)
end
local function valid_event_id(id)
return (tonumber(id) and id >= 0) or (Type.String(id) and not bootstrap_events[id])
end
local function id_to_name(name)
return event_names[name] or table.invert(Event.custom_events)[name] or name or 'unknown'
end
local stupid_events = {
[defines.events.script_raised_revive] = 'entity',
[defines.events.script_raised_built] = 'entity',
[defines.events.on_entity_cloned] = 'destination'
}
--- Registers a handler for the given events.
-- If a `nil` handler is passed, remove the given events and stop listening to them.
-- <p>Events dispatch in the order they are registered.
-- <p>An *event ID* can be obtained via @{defines.events},
-- @{LuaBootstrap.generate_event_name|script.generate_event_name} which is in <span class="types">@{int}</span>,
-- and can be a custom input name which is in <span class="types">@{string}</span>.
-- <p>The `event_id` parameter takes in either a single, multiple, or mixture of @{defines.events}, @{int}, and @{string}.
-- @usage
-- -- Create an event that prints the current tick every tick.
-- Event.register(defines.events.on_tick, function(event) game.print(event.tick) end)
-- -- Register something for Nth tick using negative numbers.
-- Event.register(-120, function() game.print('Every 120 ticks') end
-- -- Function call chaining
-- Event.register(event1, handler1).register(event2, handler2)
-- @param event_id (<span class="types">@{defines.events}, @{int}, @{string}, or {@{defines.events}, @{int}, @{string},...}</span>)
-- @tparam function handler the function to call when the given events are triggered
-- @tparam[opt=nil] function filter a function whose return determines if the handler is executed. event and pattern are passed into this
-- @tparam[opt=nil] mixed pattern an invariant that can be used in the filter function, passed as the second parameter to your filter
-- @tparam[opt=nil] table options a table of options that take precedence over the module options.
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
function Event.register(event_id, handler, filter, pattern, options)
assert(event_id, 'missing event_id argument')
assert(Type.Function(handler), 'handler function is missing, use Event.remove to un register events')
assert(filter == nil or Type.Function(filter), 'filter must be a function when present')
assert(options == nil or Type.Table(options), 'options must be a table when present')
options = setmetatable(options or {}, Event_options_meta)
--Recursively handle event id tables
if Type.Table(event_id) then
for _, id in pairs(event_id) do
Event.register(id, handler)
end
return Event
end
assert(valid_id(event_id), 'event_id is invalid')
-- If the event_id has never been registered before make sure we call the correct script action to register
-- our Event handler with factorio
if not Event.registry[event_id] then
Event.registry[event_id] = {}
if Type.String(event_id) then
--String event ids will either be Bootstrap events or custom input events
if bootstrap_events[event_id] then
Event.script[event_id](bootstrap_events[event_id])
else
Event.script.on_event(event_id, Event.dispatch)
end
elseif event_id >= 0 then
--Positive values will be defines.events
Event.script.on_event(event_id, Event.dispatch)
elseif event_id < 0 then
--Use negative values to register on_nth_tick
Event.script.on_nth_tick(math.abs(event_id)--[[@as uint]] , Event.dispatch)
end
end
local registry = Event.registry[event_id]
--If handler is already registered for this event: remove it for re-insertion at the end.
if #registry > 0 then
for i, registered in ipairs(registry) do
if registered.handler == handler and registered.pattern == pattern and registered.filter == filter then
table.remove(registry, i)
local output = {
'__' .. script.mod_name .. '__',
' Duplicate handler registered for event ',
event_id .. '(' .. (event_names[event_id] or ' ') .. ')',
' at position ' .. i,
', moving it to the bottom.'
}
log(table.concat(output))
break
end
end
end
--Finally insert the handler
table.insert(registry, { handler = handler, filter = filter, pattern = pattern, options = options })
return Event
end
--- Removes a handler from the given events.
-- <p>When the last handler for an event is removed, stop listening to that event.
-- <p>An *event ID* can be obtained via @{defines.events},
-- @{LuaBootstrap.generate_event_name|script.generate_event_name} which is in <span class="types">@{int}</span>,
-- and can be a custom input name which is in <span class="types">@{string}</span>.
-- <p>The `event_id` parameter takes in either a single, multiple, or mixture of @{defines.events}, @{int}, and @{string}.
-- @param event_id (<span class="types">@{defines.events}, @{int}, @{string}, or {@{defines.events}, @{int}, @{string},...}</span>)
-- @tparam[opt] function handler the handler to remove, if not present remove all registered handlers for the event_id
-- @tparam[opt] function filter
-- @tparam[opt] mixed pattern
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
function Event.remove(event_id, handler, filter, pattern)
assert(event_id, 'missing event_id argument')
-- Handle recursion here
if Type.Table(event_id) then
for _, id in pairs(event_id) do
Event.remove(id, handler)
end
return Event
end
assert(valid_id(event_id), 'event_id is invalid')
local registry = Event.registry[event_id]
if registry then
local found_something = false
for i = #registry, 1, -1 do
local registered = registry[i]
if handler then -- handler, possibly filter, possibly pattern
if handler == registered.handler then
if not filter and not pattern then
table.remove(registry, i)
found_something = true
elseif filter then
if filter == registered.filter then
if not pattern then
table.remove(registry, i)
found_something = true
elseif pattern and pattern == registered.pattern then
table.remove(registry, i)
found_something = true
end
end
elseif pattern and pattern == registered.pattern then
table.remove(registry, i)
found_something = true
end
end
elseif filter then -- no handler, filter, possibly pattern
if filter == registered.filter then
if not pattern then
table.remove(registry, i)
found_something = true
elseif pattern and pattern == registered.pattern then
table.remove(registry, i)
found_something = true
end
end
elseif pattern then -- no handler, no filter, pattern
if pattern == registered.pattern then
table.remove(registry, i)
found_something = true
end
else -- no handler, filter, or pattern
table.remove(registry, i)
found_something = true
end
end
if found_something and table.size(registry) == 0 then
-- Clear the registry data and un subscribe if there are no registered handlers left
Event.registry[event_id] = nil
if Type.String(event_id) then
-- String event ids will either be Bootstrap events or custom input events
if bootstrap_events[event_id] then
Event.script[event_id](nil)
else
Event.script.on_event(event_id, nil)
end
elseif event_id >= 0 then
-- Positive values will be defines.events
Event.script.on_event(event_id, nil)
elseif event_id < 0 then
-- Use negative values to remove on_nth_tick
Event.script.on_nth_tick(math.abs(event_id)--[[@as uint]] , nil)
end
elseif not found_something then
log('Attempt to deregister already non-registered listener from event: ' .. event_id)
end
else
log('Attempt to deregister already non-registered listener from event: ' .. event_id)
end
return Event
end
--- Shortcut for `Event.register(Event.core_events.on_load, function)`
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
function Event.on_load(...)
return Event.register(Event.core_events.on_load, ...)
end
function Event.on_load_if(truthy, ...)
if truthy then
return Event.on_load(...)
end
return Event
end
--- Shortcut for `Event.register(Event.core_events.on_configuration_changed, function)`
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
function Event.on_configuration_changed(...)
return Event.register(Event.core_events.on_configuration_changed, ...)
end
--- Shortcut for `Event.register(Event.core_events.on_init, function)`
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
function Event.on_init(...)
return Event.register(Event.core_events.on_init, ...)
end
function Event.on_init_if(truthy, ...)
if truthy then
return Event.on_init(...)
end
return Event
end
--- Shortcut for `Event.register(-nthTick, function)`
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
function Event.on_nth_tick(nth_tick, ...)
return Event.register(-math.abs(nth_tick), ...)
end
--- Shortcut for `Event.register(defines.events, function)`
-- @function Event.on_event
-- @return (<span class="types">@{Event}</span>) Event module object allowing for call chaining
Event.on_event = Event.register
function Event.register_if(truthy, id, ...)
if truthy then
return Event.register(id, ...)
end
return Event
end
Event.on_event_if = Event.register_if
-- Used to replace pcall in un-protected events.
local function no_pcall(handler, ...)
return true, handler(...)
end
-- A dispatch helper function
-- Call any filter and as applicable the event handler.
-- protected errors are logged to game console if game is available, otherwise a real error
-- is thrown. Bootstrap events are not protected from erroring no matter the option.
local function dispatch_event(event, registered)
local success, match_result, handler_result
local protected = event.options.protected_mode
local pcall = not bootstrap_events[event.name] and protected and pcall or no_pcall
-- If we have a filter run it first passing event, and registered.pattern as parameters
-- If the filter returns truthy call the handler passing event, and the result from the filter
if registered.filter then
success, match_result = pcall(registered.filter, event, registered.pattern)
if success and match_result then
success, handler_result = pcall(registered.handler, event, match_result)
end
else
success, handler_result = pcall(registered.handler, event, registered.pattern)
end
-- If the handler errors lets make sure someone notices
if not success and not Event.log_and_print(handler_result or match_result) then
-- no players received the message, force a real error so someone notices
error(handler_result or match_result)
end
return success and handler_result or nil
end
--- The user should create a table in this format, for a table that will be passed into @{Event.dispatch}.
-- <p>In general, the user should create an event data table that is in a similar format as the one that Factorio returns.
--> The event data table **MUST** have either `name` or `input_name`.
-- @tfield[opt] int|defines.events name unique event ID generated with @{LuaBootstrap.generate_event_name|script.generate_event_name} ***OR*** @{defines.events}
-- @tfield[opt] string input_name custom input name of an event
-- @field[opt] ... any # of additional fields with extra data, which are passed into the handler registered to an event that this table represents
-- @usage
-- -- below code is from Trains module.
-- -- old_id & new_id are additional fields passed into the handler that's registered to Trains.on_train_id_changed event.
-- local event_data = {
-- old_id = renaming.old_id,
-- new_id = renaming.new_id,
-- name = Trains.on_train_id_changed
-- }
-- Event.dispatch(event_data)
-- @table event_data
--- Calls the handlers that are registered to the given event.
-- <p>Abort calling remaining handlers if any one of them has invalid userdata.
-- <p>Handlers are dispatched in the order they were created.
-- @param event (<span class="types">@{event_data}</span>) the event data table
-- @see https://forums.factorio.com/viewtopic.php?t=32039#p202158 Invalid Event Objects
function Event.dispatch(event)
if type(event) ~= 'table' then
error('missing event table')
end
--get the registered handlers from name, input_name, or nth_tick in that priority.
local registry
if event.name and Event.registry[event.name] then
registry = Event.registry[event.name]
elseif event.input_name and Event.registry[event.input_name] then
registry = Event.registry[event.input_name]
elseif event.nth_tick then
registry = Event.registry[-event.nth_tick]
end
if registry then
--add the tick if it is not present, this only affects calling Event.dispatch manually
--doing the check up here as it will faster than checking every iteration for a constant value
event.tick = event.tick or (game and game.tick) or 0
event.define_name = event_names[event.name or '']
event.options = event.options or {}
-- Some events are just stupid and need more help
if stupid_events[event.name] then
event.created_entity = event.created_entity or event.entity or event.destination
end
for _, registered in ipairs(registry) do
event.options = setmetatable(event.options, { __index = registered.options })
-- Check for userdata and stop processing this and further handlers if not valid
-- This is the same behavior as factorio events.
-- This is done inside the loop as other events can modify the event.
if not event.options.skip_valid then
for _, val in pairs(event) do
if type(val) == 'table' and val.__self and not val.valid then
return
end
end
end
-- Dispatch the event, if the event return Event.stop_processing don't process any more events
if dispatch_event(event, registered) == Event.stop_processing then
return
end
-- Force a crc check if option is enabled. This is a debug option and will hamper performance if enabled
if game and event.options.force_crc then
log('CRC check called for event [' .. event.name .. ']')
game.force_crc()
end
end
end
end
function Event.register_player(bool)
require('__stdlib__/stdlib/event/player').register_events(bool)
return Event
end
function Event.register_force(bool)
require('__stdlib__/stdlib/event/force').register_events(bool)
return Event
end
function Event.register_surface(bool)
require('__stdlib__/stdlib/event/surface').register_events(bool)
return Event
end
--- Retrieve or Generate an event_name and store it in Event.custom_events
-- @tparam string event_name the custom name for your event.
-- @treturn int the id associated with the event.
-- @usage
-- Event.register(Event.generate_event_name("my_custom_event"), handler)
function Event.generate_event_name(event_name)
assert(Type.String(event_name), 'event_name must be a string.')
local id
if Type.Number(Event.custom_events[event_name]) then
id = Event.custom_events[event_name]
else
id = Event.script.generate_event_name()
Event.custom_events[event_name] = id
end
return id
end
function Event.set_event_name(event_name, id)
assert(Type.String(event_name), 'event_name must be a string')
assert(Type.Number(id))
Event.custom_events[event_name] = id
return Event.custom_events[event_name]
end
function Event.get_event_name(event_name)
assert(Type.String(event_name), 'event_name must be a string')
return Event.custom_events[event_name]
end
---@todo complete stub
function Event.raise_event(...)
script.raise_event(...)
end
--- Get event handler.
function Event.get_event_handler(event_id)
assert(valid_id(event_id), 'event_id is invalid')
return {
script = bootstrap_events[event_id] or (valid_event_id(event_id) and Event.script.get_event_handler(event_id)),
handlers = Event.registry[event_id]
}
end
--- Set protected mode.
function Event.set_protected_mode(bool)
Event.options.protected_mode = bool and true or false
return Event
end
--- Set debug mode default for Event module.
function Event.set_debug_mode(bool)
Event.debug_mode = bool and true or false
return Event
end
--- Set default options for the event module.
function Event.set_option(option, bool)
Event.options[option] = bool and true or false
return Event
end
Event.dump_data = require('__stdlib__/stdlib/event/modules/dump_event_data')(Event, valid_event_id, id_to_name)
return Event

View File

@@ -0,0 +1,125 @@
--- Force global creation.
-- <p>All new forces will be added to the `global.forces` table.
-- <p>This modules events should be registered after any other Init functions but before any scripts needing `global.players`.
-- <p>This modules can register the following events: `on_force_created`, and `on_forces_merging`.
-- @module Event.Force
-- @usage
-- local Force = require('__stdlib__/stdlib/event/force').register_events()
-- -- inside your Init event Force.init() -- to properly handle any existing forces
local Event = require('__stdlib__/stdlib/event/event')
local Force = {
__class = 'Force',
__index = require('__stdlib__/stdlib/core'),
_new_force_data = {}
}
setmetatable(Force, Force)
local inspect = _ENV.inspect
local Game = require('__stdlib__/stdlib/game')
local table = require('__stdlib__/stdlib/utils/table')
local merge_additional_data = require('__stdlib__/stdlib/event/modules/merge_data')
local assert, type = assert, type
-- return new default force object
local function new(force_name)
local fdata = {
index = force_name,
name = force_name
}
merge_additional_data(Force._new_force_data, fdata)
return fdata
end
function Force.additional_data(...)
for _, func_or_table in pairs { ... } do
local var_type = type(func_or_table)
assert(var_type == 'table' or var_type == 'function', 'Must be table or function')
Force._new_force_data[#Force._new_force_data + 1] = func_or_table
end
return Force
end
--- Get `game.forces[name]` & `global.forces[name]`, or create `global.forces[name]` if it doesn't exist.
-- @tparam string|LuaForce force the force to get data for
-- @treturn LuaForce the force instance
-- @treturn table the force's global data
-- @usage
-- local Force = require('__stdlib__/stdlib/event/force')
-- local force_name, force_data = Force.get("player")
-- local force_name, force_data = Force.get(game.forces["player"])
-- -- Returns data for the force named "player" from either a string or LuaForce object
function Force.get(force)
force = Game.get_force(force)
assert(force, 'force is missing')
return game.forces[force.name], global.forces and global.forces[force.name] or Force.init(force.name)
end
--- Merge a copy of the passed data to all forces in `global.forces`.
-- @tparam table data a table containing variables to merge
-- @usage
-- local data = {a = "abc", b = "def"}
-- Force.add_data_all(data)
function Force.add_data_all(data)
table.each(
global.forces,
function(v)
table.merge(v, table.deepcopy(data))
end
)
end
--- Init or re-init a force or forces.
-- Passing a `nil` event will iterate all existing forces.
-- @tparam[opt] string|table event table or a string containing force name
-- @tparam[opt=false] boolean overwrite the force data
function Force.init(event, overwrite)
global.forces = global.forces or {}
local force = Game.get_force(event)
if force then
if not global.forces[force.name] or (global.forces[force.name] and overwrite) then
global.forces[force.name] = new(force.name)
return global.forces[force.name]
end
else
for name in pairs(game.forces) do
if not global.forces[name] or (global.forces[name] and overwrite) then
global.forces[name] = new(name)
end
end
end
return Force
end
function Force.dump_data()
game.write_file(Force.get_file_path('Force/force_data.lua'), 'return ' .. inspect(Force._new_force_data, { longkeys = true, arraykeys = true }))
game.write_file(Force.get_file_path('Force/global.lua'), 'return ' .. inspect(global.forces or nil, { longkeys = true, arraykeys = true }))
end
--- When forces are merged, just remove the original forces data
function Force.merged(event)
global.forces[event.source_name] = nil
end
function Force.register_init()
Event.register(Event.core_events.init, Force.init)
return Force
end
--- Register Events
function Force.register_events(do_on_init)
Event.register(defines.events.on_force_created, Force.init)
Event.register(defines.events.on_forces_merged, Force.merged)
if do_on_init then
Force.register_init()
end
return Force
end
return Force

View File

@@ -0,0 +1,78 @@
--- Makes monolithic Factorio GUI events more manageable.
-- @module Event.Gui
-- @usage local Gui = require('__stdlib__/stdlib/event/gui')
local Event = require('__stdlib__/stdlib/event/event')
local Gui = {
__class = 'Gui',
__index = require('__stdlib__/stdlib/core')
}
setmetatable(Gui, Gui)
--- Registers a function for a given gui element name or pattern when the element is clicked.
-- @tparam string gui_element_pattern the name or string regular expression to match the gui element
-- @tparam function handler the function to call when gui element is clicked
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_click(gui_element_pattern, handler)
Event.register(defines.events.on_gui_click, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
--- Registers a function for a given GUI element name or pattern when the element checked state changes.
-- @tparam string gui_element_pattern the name or string regular expression to match the GUI element
-- @tparam function handler the function to call when GUI element checked state changes
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_checked_state_changed(gui_element_pattern, handler)
Event.register(defines.events.on_gui_checked_state_changed, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
--- Registers a function for a given GUI element name or pattern when the element text changes.
-- @tparam string gui_element_pattern the name or string regular expression to match the GUI element
-- @tparam function handler the function to call when GUI element text changes
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_text_changed(gui_element_pattern, handler)
Event.register(defines.events.on_gui_text_changed, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
--- Registers a function for a given GUI element name or pattern when the element selection changes.
-- @tparam string gui_element_pattern the name or string regular expression to match the GUI element
-- @tparam function handler the function to call when GUI element selection changes
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_elem_changed(gui_element_pattern, handler)
Event.register(defines.events.on_gui_elem_changed, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
--- Registers a function for a given GUI element name or pattern when the element state changes (drop down).
-- @tparam string gui_element_pattern the name or string regular expression to match the GUI element
-- @tparam function handler the function to call when GUI element state changes
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_selection_state_changed(gui_element_pattern, handler)
Event.register(defines.events.on_gui_selection_state_changed, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
--- Registers a function for a given GUI element name or pattern when the element value changes (slider).
-- @tparam string gui_element_pattern the name or string regular expression to match the GUI element
-- @tparam function handler the function to call when GUI element state changes
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_value_changed(gui_element_pattern, handler)
Event.register(defines.events.on_gui_value_changed, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
--- Registers a function for a given GUI element name or pattern when the element is confirmed.
-- @tparam string gui_element_pattern the name or string regular expression to match the GUI element
-- @tparam function handler the function to call when GUI element state changes
-- @return (<span class="types">@{Gui}</span>)
function Gui.on_confirmed(gui_element_pattern, handler)
Event.register(defines.events.on_gui_confirmed, handler, Event.Filters.gui, gui_element_pattern)
return Gui
end
Event.Gui = Gui
return Gui

View File

@@ -0,0 +1,61 @@
local inspect = _ENV.inspect
local function setup_event_data(Event, valid_event_id, id_to_name)
local function get_registered_counts(reg_type)
local core, nth, on_events = 0, 0, 0
local events = {}
for id, registry in pairs(Event.registry) do
if tonumber(id) then
if id < 0 then
nth = nth + #registry
else
on_events = on_events + #registry
end
else
if Event.core_events[id] then
core = core + #registry
else
on_events = on_events + #registry
end
end
local name = id_to_name(id)
events[name] = (events[name] or 0) + #registry
end
local all = {
core = core,
events = events,
nth = nth,
on_events = on_events,
total = on_events + nth + core
}
return reg_type and all[reg_type] or all
end
local function dump_data()
local event_data = {
count_data = get_registered_counts(),
event_order = script.get_event_order(),
custom_events = Event.custom_events,
registry = Event.registry,
options = {
protected_mode = Event.options.protected_mode,
force_crc = Event.options.force_crc,
inspect_event = Event.options.inspect_event,
skip_valid = Event.options.skip_valid
}
}
local registry, factorio_events = {}, {}
for event, data in pairs(Event.registry) do
registry['[' .. event .. '] ' .. id_to_name(event)] = data
if valid_event_id(event) then
factorio_events['[' .. event .. '] ' .. id_to_name(event)] = Event.script.get_event_handler(event)
end
end
game.write_file(Event.get_file_path('Event/Event.lua'), 'return ' .. inspect(event_data))
game.write_file(Event.get_file_path('Event/Event.registry.lua'), 'return ' .. inspect(registry, { longkeys = true, arraykeys = true }))
game.write_file(Event.get_file_path('Event/Factorio.registry.lua'), 'return ' .. inspect(factorio_events, { longkeys = true, arraykeys = true }))
end
return dump_data
end
return setup_event_data

View File

@@ -0,0 +1,45 @@
--- Event Filters
-- Predefined event filter functions
-- @module Event.Filters
local Filters = {
entity = {},
player = {},
}
function Filters.on_key(event_key, pattern)
return function(event)
local key = event and event[event_key]
return key and key:match(pattern)
end
end
function Filters.entity.name(event, pattern)
local entity = event and (event.created_entity or event.entity)
return entity.name:match(pattern)
end
function Filters.entity.type(event, pattern)
local entity = event and (event.created_entity or event.entity)
return entity.type:match(pattern)
end
function Filters.player.cursor_stack(event, pattern)
local player = game.get_player(event.player_index)
local stack = player.cursor_stack
return stack and stack.valid_for_read and stack.name:match(pattern)
end
function Filters.gui(event, pattern)
if event.element and event.element.valid then
local match_str = event.element.name:match(pattern)
if match_str then
event.match = match_str
event.state = event.name == defines.events.on_gui_checked_state_changed and event.element.state or nil
event.text = event.name == defines.events.on_gui_text_changed and event.element.text or nil
return match_str
end
end
end
return Filters

View File

@@ -0,0 +1,21 @@
local table = require('__stdlib__/stdlib/utils/table')
local function merge_additional_data(additional_data_array, data)
for _, new_data in pairs(additional_data_array) do
if type(new_data) == 'table' then
table.merge(data, table.deepcopy(new_data))
elseif type(new_data) == 'function' then
local new_data_func_result = new_data(data.index)
if type(new_data_func_result) == 'table' then
table.merge(data, new_data_func_result)
else
error('additional data function did not return a table')
end
else
error('additional data present but is not a function or table')
end
end
return data
end
return merge_additional_data

View File

@@ -0,0 +1,143 @@
--- Player global creation.
-- This module adds player helper functions, it does not automatically register events unless Player.register_events() is called
-- @module Event.Player
-- @usage
-- local Player = require('__stdlib__/stdlib/event/player').register_events()
-- -- The fist time this is required it will register player creation events
local Event = require('__stdlib__/stdlib/event/event')
local Player = {
__class = 'Player',
__index = require('__stdlib__/stdlib/core'),
_new_player_data = {}
}
setmetatable(Player, Player)
local Game = require('__stdlib__/stdlib/game')
local table = require('__stdlib__/stdlib/utils/table')
local merge_additional_data = require('__stdlib__/stdlib/event/modules/merge_data')
local assert, type = assert, type
local inspect = _ENV.inspect
-- Return new default player object consiting of index, name, force
local function new(player_index)
local pdata = {
index = player_index,
name = game.players[player_index].name,
force = game.players[player_index].force.name
}
merge_additional_data(Player._new_player_data, pdata)
return pdata
end
function Player.additional_data(...)
for _, func_or_table in pairs { ... } do
local var_type = type(func_or_table)
assert(var_type == 'table' or var_type == 'function', 'Must be table or function')
Player._new_player_data[#Player._new_player_data + 1] = func_or_table
end
return Player
end
--- Get `game.players[index]` & `global.players[index]`, or create `global.players[index]` if it doesn't exist.
-- @tparam number|string|LuaPlayer player the player index to get data for
-- @treturn LuaPlayer the player instance
-- @treturn table the player's global data
-- @usage
-- local Player = require('__stdlib__/stdlib/event/player')
-- local player, player_data = Player.get(event.player_index)
function Player.get(player)
player = Game.get_player(player)
return player, global.players and global.players[player.index] or Player.init(player.index)
end
--- Get the players saved data table. Creates it if it doesn't exsist.
-- @tparam number index The player index to get data for
-- @treturn table the player's global data
function Player.pdata(index)
return global.players and global.players[index] or Player.init(index)
end
--- Merge a copy of the passed data to all players in `global.players`.
-- @tparam table data a table containing variables to merge
-- @usage local data = {a = 'abc', b = 'def'}
-- Player.add_data_all(data)
function Player.add_data_all(data)
local pdata = global.players
table.each(
pdata,
function(v)
table.merge(v, table.deepcopy(data))
end
)
end
--- Remove data for a player when they are deleted.
-- @tparam table event event table containing the `player_index`
function Player.remove(event)
global.players[event.player_index] = nil
end
--- Init or re-init a player or players.
-- Passing a `nil` event will iterate all existing players.
-- @tparam[opt] number|table|string|LuaPlayer event
-- @tparam[opt=false] boolean overwrite the player data
function Player.init(event, overwrite)
-- Create the global.players table if it doesn't exisit
global.players = global.players or {}
--get a valid player object or nil
local player = Game.get_player(event)
if player then --If player is not nil then we are working with a valid player.
if not global.players[player.index] or (global.players[player.index] and overwrite) then
global.players[player.index] = new(player.index)
return global.players[player.index]
end
else --Check all players
for index in pairs(game.players) do
if not global.players[index] or (global.players[index] and overwrite) then
global.players[index] = new(index)
end
end
end
if global._print_queue then
table.each(
global._print_queue,
function(msg)
game.print(tostring(msg))
end
)
global._print_queue = nil
end
return Player
end
function Player.update_force(event)
local player, pdata = Player.get(event.player_index)
pdata.force = player.force.name
end
function Player.dump_data()
game.write_file(Player.get_file_path('Player/player_data.lua'), 'return ' .. inspect(Player._new_player_data, { longkeys = true, arraykeys = true }))
game.write_file(Player.get_file_path('Player/global.lua'), 'return ' .. inspect(global.players or nil, { longkeys = true, arraykeys = true }))
end
function Player.register_init()
Event.register(Event.core_events.init, Player.init)
return Player
end
function Player.register_events(do_on_init)
Event.register(defines.events.on_player_created, Player.init)
Event.register(defines.events.on_player_changed_force, Player.update_force)
Event.register(defines.events.on_player_removed, Player.remove)
if do_on_init then
Player.register_init()
end
return Player
end
return Player

View File

@@ -0,0 +1,105 @@
--- Surface global creation.
-- <p>All surfaces will be added to the `global.surfaces` table.
-- <p>This modules events should be registered after any other Init functions but before any scripts needing `global.surfaces`.
-- <p>This modules can register the following events:
-- @module Event.Surface
-- @usage
-- local surface = require('__stdlib__/stdlib/event/surface').register_events()
local Event = require('__stdlib__/stdlib/event/event')
local Surface = {
__class = 'Surface',
_new_surface_data = {}
}
setmetatable(Surface, require('__stdlib__/stdlib/core'))
local inspect = _ENV.inspect
local merge_additional_data = require('__stdlib__/stdlib/event/modules/merge_data')
local function new(index)
local surface = game.surfaces[index]
local sdata = {
index = surface.index,
name = surface.name,
}
merge_additional_data(Surface._new_surface_data, sdata)
return sdata
end
function Surface.additional_data(...)
for _, func_or_table in pairs { ... } do
local typeof = type(func_or_table)
assert(typeof == 'table' or typeof == 'string', 'Must be table or function')
Surface._new_surface_data[#Surface._new_surface_data + 1] = func_or_table
end
return Surface
end
--- Remove data for a surface when it is deleted.
-- @tparam table event event table containing the surface index
function Surface.remove(event)
global.surfaces[event.surface_index] = nil
end
function Surface.rename(event)
global.surfaces[event.surface_index].name = event.new_name
end
function Surface.import(event)
new(event.surface_index)
end
-- function Surface.cleared(event)
-- end
--- Init or re-init the surfaces.
-- Passing a `nil` event will iterate all existing surfaces.
-- @tparam[opt] number|table|string|LuaSurface event
-- @tparam[opt=false] boolean overwrite the surface data
function Surface.init(event, overwrite)
-- Create the global.surfaces table if it doesn't exisit
global.surfaces = global.surfaces or {}
--get a valid surface object or nil
local surface = game.surfaces[event.surface_index]
if surface then
if not global.surfaces[surface.index] or (global.surfaces[surface.index] and overwrite) then
global.surfaces[surface.index] = new(surface.index)
return global.surfaces[surface.index]
end
else --Check all surfaces
for index in pairs(game.surfaces) do
if not global.surfaces[index] or (global.surfaces[index] and overwrite) then
global.surfaces[index] = new(index)
end
end
end
return Surface
end
function Surface.dump_data()
game.write_file(Surface.get_file_path('Surface/surface_data.lua'), inspect(Surface._new_surface_data, { longkeys = true, arraykeys = true }))
game.write_file(Surface.get_file_path('Surface/global.lua'), inspect(global.surfaces or nil, { longkeys = true, arraykeys = true }))
end
function Surface.register_init()
Event.register(Event.core_events.init, Surface.init)
return Surface
end
function Surface.register_events(do_on_init)
Event.register(defines.events.on_surface_created, Surface.init)
Event.register(defines.events.on_surface_deleted, Surface.remove)
Event.register(defines.events.on_surface_imported, Surface.import)
Event.register(defines.events.on_surface_renamed, Surface.rename)
--Event.register(defines.events.on_surface_cleared, Surface.func)
if do_on_init then
Surface.register_init()
end
return Surface
end
return Surface

View File

@@ -0,0 +1,214 @@
--- Tools for working with trains.
-- When this module is loaded into a mod, it automatically registers a number of new events in order to keep track of
-- the trains as their locomotives and wagons are moved around.
-- <p>To handle the events, you should use the @{Event} module.
-- @module Event.Trains
local Trains = {
__class = 'Trains',
__index = require('__stdlib__/stdlib/core')
}
setmetatable(Trains, Trains)
local Event = require('__stdlib__/stdlib/event/event')
local Surface = require('__stdlib__/stdlib/area/surface')
local Entity = require('__stdlib__/stdlib/entity/entity')
local table = require('__stdlib__/stdlib/utils/table')
--- This event fires when a train's ID changes.
-- <p>The train ID is a property of the main locomotive,
-- which means that when locomotives are attached or detached from their wagons or from other locomotives, the ID of the train changes.
-- <p>For example: A train with a front and rear locomotives will get its ID
-- from the front locomotive. If the front locomotive gets disconnected, the rear locomotive becomes the main one and the train's ID changes.
-- @event on_train_id_changed
-- @tparam uint old_id the ID of the train before the change
-- @tparam uint new_id the ID of the train after the change
-- @usage
---- Event.register(Trains.on_train_id_changed, my_handler)
Trains.on_train_id_changed = Event.generate_event_name()
--- Given a @{criteria|search criteria}, search for trains that match the criteria.
-- If ***criteria.surface*** is not supplied, this function searches through all existing surfaces.
-- If ***criteria.force*** is not supplied, this function searches through all existing forces.
-- If ***criteria.state*** is not supplied, this function gets trains in any @{defines.train_state|state}.
-- @tparam criteria criteria a table used to search for trains
-- @return (<span class="types">{@{train_details},...}</span>) an array of train IDs and LuaTrain instances
-- @usage
-- Trains.find_filtered({ surface = "nauvis", state = defines.train_state.wait_station })
function Trains.find_filtered(criteria)
criteria = criteria or {}
local surface_list = Surface.lookup(criteria.surface)
if criteria.surface == nil then
surface_list = game.surfaces
end
local results = {}
for _, surface in pairs(surface_list) do
local trains = surface.get_trains(criteria.force)
for _, train in pairs(trains) do
table.insert(results, train)
end
end
-- Apply state filters
if criteria.state then
results =
table.filter(
results,
function(train)
return train.state == criteria.state
end
)
end
-- Lastly, look up the train ids
results =
table.map(
results,
function(train)
return { train = train, id = Trains.get_main_locomotive(train).unit_number }
end
)
return results
end
---
-- This table should be passed into @{find_filtered} to find trains that match the criteria.
-- @tfield[opt] ?|nil|string|{string,...}|LuaSurface|{LuaSurface,...} surface the surfaces to look up for the trains
-- @tfield[opt] ?|nil|string|LuaForce force the force of the trains to search
-- @tfield[opt] ?|nil|defines.train_state state the state of the trains to search
-- @table criteria
---
-- @{find_filtered} returns an array with one or more of ***this*** table based on the @{criteria|search criteria}.
-- @tfield LuaTrain train an instance of the train
-- @tfield uint id the ID of the train
-- @table train_details
--- Find the ID of a LuaTrain instance.
-- @tparam LuaTrain train
-- @treturn uint the ID of the train
function Trains.get_train_id(train)
local loco = Trains.get_main_locomotive(train)
return loco and loco.unit_number
end
--- Event fired when some change happens to a locomotive.
-- @lfunction
function Trains._on_locomotive_changed()
-- For all the known trains
local renames = {}
for id, train in pairs(global._train_registry) do
-- Check if their known ID is the same as the LuaTrain's dervied id
local derived_id = Trains.get_train_id(train)
-- If it's not
if (id ~= derived_id) then
-- Capture the rename
table.insert(renames, { old_id = id, new_id = derived_id, train = train })
end
end
-- Go over the captured renaming operations
for _, renaming in pairs(renames) do
-- Rename it in the registry
-- and dispatch a renamed event
global._train_registry[renaming.new_id] = renaming.train
global._train_registry[renaming.old_id] = nil
local event_data = {
old_id = renaming.old_id,
new_id = renaming.new_id,
name = Trains.on_train_id_changed
}
Event.dispatch(event_data)
end
end
--- Get the main locomotive of a train.
-- @tparam LuaTrain train
-- @treturn LuaEntity the main locomotive
function Trains.get_main_locomotive(train)
if train and train.valid and train.locomotives and (#train.locomotives.front_movers > 0 or #train.locomotives.back_movers > 0) then
return train.locomotives.front_movers and train.locomotives.front_movers[1] or train.locomotives.back_movers[1]
end
end
--- Creates an entity from a train that is compatible with the @{Entity.Entity} module.
-- @tparam LuaTrain train
-- @return (<span class="types">@{train_entity}</span>)
function Trains.to_entity(train)
local name = 'train-' .. Trains.get_train_id(train)
return {
name = name,
valid = train.valid,
equals = function(entity)
return name == entity.name
end
}
end
------
-- @{to_entity} returns ***this*** table.
-- @tfield string name the name of the train entity with the train ID as its suffix
-- @tfield boolean valid whether or not if the train is in a valid state in the game
-- @tfield function equals &mdash; *function(entity)* &mdash; a function to check if another entity is equal to the train that ***this*** table represents
-- @table train_entity
--- Associates the user data to a train.
-- This is a helper around @{Entity.Entity.set_data}.
-- <p>The user data will be stored in the global object and it will persist between loads.
--> The user data will be removed from a train when the train becomes invalid.
-- @tparam LuaTrain train the train to set the user data for
-- @tparam ?|nil|Mixed data the user data to set, or nil to delete the user data associated with the train
-- @treturn ?|nil|Mixed the previous user data or nil if the train had no previous user data
function Trains.set_data(train, data)
return Entity.set_data(Trains.to_entity(train), data)
end
--- Gets the user data that is associated with a train.
-- This is a helper around @{Entity.Entity.get_data}.
-- <p>The user data is stored in the global object and it persists between loads.
--> The user data will be removed from a train when the train becomes invalid.
-- @tparam LuaTrain train the train to look up user data for
-- @treturn ?|nil|Mixed the user data, or nil if no user data exists for the train
function Trains.get_data(train)
return Entity.get_data(Trains.to_entity(train))
end
-- Creates a registry of known trains.
-- @return table a mapping of train id to LuaTrain object
function Trains.create_train_registry()
global._train_registry = global._train_registry or {}
local all_trains = Trains.find_filtered()
for _, trainInfo in pairs(all_trains) do
global._train_registry[tonumber(trainInfo.id)] = trainInfo.train
end
return global._train_registry
end
function Trains.on_train_created(event)
local train_id = Trains.get_train_id(event.train)
global._train_registry[train_id] = event.train
end
--- This needs to be called to register events for this module
-- @treturn Trains
function Trains.register_events()
-- When a locomotive is removed ...
local train_remove_events = { defines.events.on_entity_died, defines.events.on_pre_player_mined_item, defines.events.on_robot_pre_mined }
Event.register(train_remove_events, Trains._on_locomotive_changed, Event.Filters.entity.type, 'locomotive')
-- When a locomotive is added ...
Event.register(defines.events.on_train_created, Trains.on_train_created)
-- When the mod is initialized the first time
Event.register(Event.core_events.init_and_config, Trains.create_train_registry)
return Trains
end
return Trains