1.2.8 (#28)
Version: 1.2.8
Date: 2022-1-5
Features:
- Improved placeholder cybernetic combinator art
- Added a wagon control setting to bar unfiltered slots in adjacent cargo wagons
- Added a setting and keybind for toggling on or off the central planner
Changes:
- Sped up the rate at which copy-paste by blueprint will be noticed
Bugfixes:
- Fixed a bug with combinators sometimes failing to connect with train stops
- Fixed wagon control combinators outputting wagon contents after inserters have already taken out items
- Fixed a rare crash on world migration
Scripting:
- Added missing return values to some interface functions
- Migrated to non-deprecated flib modules
57
.luarc.json
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
|
||||
"Lua.diagnostics.disable": [
|
||||
"lowercase-global"
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"mods",
|
||||
"table_size",
|
||||
"log",
|
||||
"localised_print",
|
||||
"serpent",
|
||||
"global",
|
||||
"__DebugAdapter",
|
||||
"__Profiler",
|
||||
"create_inactivity_order"
|
||||
],
|
||||
"Lua.runtime.builtin": {
|
||||
"coroutine": "disable",
|
||||
"debug": "disable",
|
||||
"io": "disable",
|
||||
"math": "disable",
|
||||
"os": "disable",
|
||||
"package": "disable"
|
||||
},
|
||||
"Lua.runtime.plugin": "/home/mami/.config/Code/User/workspaceStorage/9536dbf0665a54126a4b0958ecd5829f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/plugin.lua",
|
||||
"Lua.runtime.special": {
|
||||
"__object_name": "type"
|
||||
},
|
||||
"Lua.workspace.checkThirdParty": false,
|
||||
"Lua.workspace.library": [
|
||||
"~/.steam/steam/steamapps/common/Factorio/data",
|
||||
"~/.steam/steam/steamapps/common/Factorio/data/core/lualib",
|
||||
"./.vscode/factorio",
|
||||
"/home/mami/.config/Code/User/workspaceStorage/9536dbf0665a54126a4b0958ecd5829f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/library"
|
||||
],
|
||||
"runtime": {
|
||||
"plugin": "C:\\Users\\mmoni\\files\\data\\projects\\factorio\\cybersyn\\.vscode\\lua\\plugin.lua",
|
||||
"pluginArgs": [
|
||||
"--global-as-class",
|
||||
"--mode=mods",
|
||||
"--mod_name=ltndless"
|
||||
]
|
||||
},
|
||||
"runtime.path": [
|
||||
"?.lua",
|
||||
"?/init.lua",
|
||||
"src/?.lua",
|
||||
"src/scripts/?.lua",
|
||||
"src/?/init.lua",
|
||||
"src/scripts/?/init.lua"
|
||||
],
|
||||
"runtime.pathStrict": false,
|
||||
"workspace": {
|
||||
"useGitIgnore": false
|
||||
},
|
||||
"workspace.ignoreSubmodules": false
|
||||
}
|
||||
21
.vscode/flib/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 raiguard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
24
.vscode/flib/README.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Factorio Library
|
||||
The Factorio Library is a set of high-quality, commonly-used utilities for
|
||||
creating Factorio mods.
|
||||
|
||||
## Usage
|
||||
|
||||
Download the latest release from the
|
||||
[mod portal](https://mods.factorio.com/mod/flib) unzip it, and put it in your
|
||||
mods directory. You can access libraries provided by flib with
|
||||
`require("__flib__/event")`, etc.
|
||||
|
||||
Add the flib directory to your language server's library and install the
|
||||
[Factorio LSP plugin](https://github.com/JanSharp/FactorioSumnekoLuaPlugin) to
|
||||
enable autocomplete and in-line documentation of flib functions.
|
||||
|
||||
You can view the legacy online documentation
|
||||
[here](https://factoriolib.github.io/flib/index.html). Please note that this
|
||||
documentation is out-of-date, and will be replaced with a new site in the
|
||||
future.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please use the [factorio-mods-devel](https://lists.sr.ht/~raiguard/factorio-mods-devel)
|
||||
mailing list for support, suggestions, and/or patches.
|
||||
359
.vscode/flib/area.lua
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
--- @diagnostic disable
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
local flib_area = {}
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.ceil(self)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
self.left_top = {
|
||||
x = math.floor(self.left_top.x),
|
||||
y = math.floor(self.left_top.y),
|
||||
}
|
||||
self.right_bottom = {
|
||||
x = math.ceil(self.right_bottom.x),
|
||||
y = math.ceil(self.right_bottom.y),
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.center(self)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
return {
|
||||
x = self.left_top.x + (flib_area.width(self) / 2),
|
||||
y = self.left_top.y + (flib_area.height(self) / 2),
|
||||
}
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.center_on(self, center_point)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
local height = flib_area.height(self)
|
||||
local width = flib_area.width(self)
|
||||
|
||||
self.left_top = {
|
||||
x = center_point.x - (width / 2),
|
||||
y = center_point.y - (height / 2),
|
||||
}
|
||||
self.right_bottom = {
|
||||
x = center_point.x + (width / 2),
|
||||
y = center_point.y + (height / 2),
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.contains_area(self, other_area)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
return (
|
||||
self.left_top.x <= other_area.left_top.x
|
||||
and self.left_top.y <= other_area.left_top.y
|
||||
and self.right_bottom.x >= other_area.right_bottom.x
|
||||
and self.right_bottom.y >= other_area.right_bottom.y
|
||||
)
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.contains_position(self, position)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
return (
|
||||
self.left_top.x <= position.x
|
||||
and self.right_bottom.x >= position.x
|
||||
and self.left_top.y <= position.y
|
||||
and self.right_bottom.y >= position.y
|
||||
)
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.corners(self)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
self.left_bottom = {
|
||||
x = self.left_top.x,
|
||||
y = self.right_bottom.y,
|
||||
}
|
||||
self.right_top = {
|
||||
x = self.right_bottom.x,
|
||||
y = self.left_top.y,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.distance_to_nearest_edge(self, position)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
local x_distance = math.min(math.abs(self.left_top.x - position.x), math.abs(self.right_bottom.x - position.x))
|
||||
local y_distance = math.min(math.abs(self.left_top.y - position.y), math.abs(self.right_bottom.y - position.y))
|
||||
|
||||
return math.min(x_distance, y_distance)
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.expand(self, delta)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
self.left_top.x = self.left_top.x - delta
|
||||
self.right_bottom.x = self.right_bottom.x + delta
|
||||
self.left_top.y = self.left_top.y - delta
|
||||
self.right_bottom.y = self.right_bottom.y + delta
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.expand_to_contain_area(self, other_area)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
self.left_top = {
|
||||
x = self.left_top.x < other_area.left_top.x and self.left_top.x or other_area.left_top.x,
|
||||
y = self.left_top.y < other_area.left_top.y and self.left_top.y or other_area.left_top.y,
|
||||
}
|
||||
self.right_bottom = {
|
||||
x = self.right_bottom.x > other_area.right_bottom.x and self.right_bottom.x or other_area.right_bottom.x,
|
||||
y = self.right_bottom.y > other_area.right_bottom.y and self.right_bottom.y or other_area.right_bottom.y,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.expand_to_contain_position(self, position)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
self.left_top = {
|
||||
x = self.left_top.x < position.x and self.left_top.x or position.x,
|
||||
y = self.left_top.y < position.y and self.left_top.y or position.y,
|
||||
}
|
||||
self.right_bottom = {
|
||||
x = self.right_bottom.x > position.x and self.right_bottom.x or position.x,
|
||||
y = self.right_bottom.y > position.y and self.right_bottom.y or position.y,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.floor(self)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
self.left_top = {
|
||||
x = math.ceil(self.left_top.x),
|
||||
y = math.ceil(self.left_top.y),
|
||||
}
|
||||
self.right_bottom = {
|
||||
x = math.floor(self.right_bottom.x),
|
||||
y = math.floor(self.right_bottom.y),
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.from_dimensions(dimensions, center)
|
||||
center = center or { x = 0, y = 0 }
|
||||
local self = {
|
||||
left_top = {
|
||||
x = center.x - (dimensions.width / 2),
|
||||
y = center.y - (dimensions.height / 2),
|
||||
},
|
||||
right_bottom = {
|
||||
x = center.x + (dimensions.width / 2),
|
||||
y = center.y + (dimensions.height / 2),
|
||||
},
|
||||
}
|
||||
flib_area.load(self)
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.from_position(position, snap)
|
||||
local self
|
||||
if snap then
|
||||
local floored_position = { x = math.floor(position.x), y = math.floor(position.y) }
|
||||
self = {
|
||||
left_top = { x = floored_position.x, y = floored_position.y },
|
||||
right_bottom = { x = floored_position.x + 1, y = floored_position.y + 1 },
|
||||
}
|
||||
else
|
||||
self = {
|
||||
left_top = { x = position.x - 0.5, y = position.y - 0.5 },
|
||||
right_bottom = { x = position.x + 0.5, y = position.y + 0.5 },
|
||||
}
|
||||
end
|
||||
|
||||
if self then
|
||||
flib_area.load(self)
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.from_shorthand(area)
|
||||
local self = {
|
||||
left_top = { x = area[1][1], y = area[1][2] },
|
||||
right_bottom = { x = area[2][1], y = area[2][2] },
|
||||
}
|
||||
flib_area.load(self)
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.height(self)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
return math.abs(self.right_bottom.y - self.left_top.y)
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.iterate(self, step, starting_offset)
|
||||
starting_offset = starting_offset or { x = 0, y = 0 }
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
step = step or 1
|
||||
|
||||
local x = self.left_top.x + starting_offset.x
|
||||
local y = self.left_top.y + starting_offset.y
|
||||
local max_x = self.right_bottom.x
|
||||
local max_y = self.right_bottom.y
|
||||
local first = true
|
||||
|
||||
return function()
|
||||
if first then
|
||||
first = false
|
||||
return { x = x, y = y }
|
||||
end
|
||||
|
||||
local new_x = x + step
|
||||
if x < max_x and new_x < max_x then
|
||||
x = new_x
|
||||
else
|
||||
local new_y = y + step
|
||||
if y < max_y and new_y < max_y then
|
||||
x = self.left_top.x + starting_offset.x
|
||||
y = new_y
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
return { x = x, y = y }
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.load(area)
|
||||
if not area.left_top then
|
||||
area = flib_area.from_shorthand(area)
|
||||
end
|
||||
return setmetatable(area, { __index = flib_area })
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.move(self, delta)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
self.left_top.x = self.left_top.x + delta.x
|
||||
self.left_top.y = self.left_top.y + delta.y
|
||||
self.right_bottom.x = self.right_bottom.x + delta.x
|
||||
self.right_bottom.y = self.right_bottom.y + delta.y
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.rotate(self)
|
||||
-- save current properties
|
||||
local center = flib_area.center(self)
|
||||
local height = flib_area.height(self)
|
||||
local width = flib_area.width(self)
|
||||
|
||||
local radius_x = height / 2
|
||||
local radius_y = width / 2
|
||||
|
||||
self.left_top.x = center.x - radius_x
|
||||
self.right_bottom.x = center.x + radius_x
|
||||
|
||||
self.left_top.y = center.y - radius_y
|
||||
self.right_bottom.y = center.y + radius_y
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.square(self)
|
||||
local radius = math.max(flib_area.height(self), flib_area.width(self))
|
||||
|
||||
return flib_area.from_dimensions({ height = radius, width = radius }, flib_area.center(self))
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.strip(self)
|
||||
return {
|
||||
left_top = {
|
||||
x = self.left_top.x,
|
||||
y = self.left_top.y,
|
||||
},
|
||||
right_bottom = {
|
||||
x = self.right_bottom.x,
|
||||
y = self.right_bottom.y,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.to_shorthand(self)
|
||||
if not self.left_top then
|
||||
return self
|
||||
end
|
||||
|
||||
return {
|
||||
{ self.left_top.x, self.left_top.y },
|
||||
{ self.right_bottom.x, self.right_bottom.y },
|
||||
}
|
||||
end
|
||||
|
||||
--- @deprecated Use `bounding-box` instead.
|
||||
function flib_area.width(self)
|
||||
if not self.left_top then
|
||||
self = flib_area.from_shorthand(self)
|
||||
end
|
||||
|
||||
return math.abs(self.right_bottom.x - self.left_top.x)
|
||||
end
|
||||
|
||||
return flib_area
|
||||
329
.vscode/flib/bounding-box.lua
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
local position = require("__flib__/position")
|
||||
|
||||
--- Utilities for manipulating bounding boxes. All functions support both the shorthand and explicit syntaxes for boxes
|
||||
--- and positions, and will preserve the syntax that was passed in. Boxes are considered immutable; all functions will
|
||||
--- return new boxes.
|
||||
--- @class flib_bounding_box
|
||||
local flib_bounding_box = {}
|
||||
|
||||
--- Return a new box expanded to the nearest tile edges.
|
||||
--- @param box BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.ceil(box)
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = math.floor(box.left_top.x), y = math.floor(box.left_top.y) },
|
||||
right_bottom = { x = math.ceil(box.right_bottom.x), y = math.ceil(box.right_bottom.y) },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ math.floor(box[1][1]), math.floor(box[1][2]) },
|
||||
{ math.ceil(box[2][1]), math.ceil(box[2][2]) },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate the centerpoint of the box.
|
||||
--- @param box BoundingBox
|
||||
--- @return MapPosition
|
||||
function flib_bounding_box.center(box)
|
||||
if box.left_top then
|
||||
return {
|
||||
x = (box.left_top.x + box.right_bottom.x) / 2,
|
||||
y = (box.left_top.y + box.right_bottom.y) / 2,
|
||||
}
|
||||
else
|
||||
return {
|
||||
(box[1][1] + box[2][1]) / 2,
|
||||
(box[1][2] + box[2][2]) / 2,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the first box contains the second box.
|
||||
--- @param box1 BoundingBox
|
||||
--- @param box2 BoundingBox
|
||||
--- @return boolean
|
||||
function flib_bounding_box.contains_box(box1, box2)
|
||||
local box1 = flib_bounding_box.ensure_explicit(box1)
|
||||
local box2 = flib_bounding_box.ensure_explicit(box2)
|
||||
|
||||
return box1.left_top.x <= box2.left_top.x
|
||||
and box1.left_top.y <= box2.left_top.y
|
||||
and box1.right_bottom.x >= box2.right_bottom.x
|
||||
and box1.right_bottom.y >= box2.right_bottom.y
|
||||
end
|
||||
|
||||
--- Check if the given box contains the given position.
|
||||
--- @param box BoundingBox
|
||||
--- @param pos MapPosition
|
||||
--- @return boolean
|
||||
function flib_bounding_box.contains_position(box, pos)
|
||||
local box = flib_bounding_box.ensure_explicit(box)
|
||||
local pos = position.ensure_explicit(pos)
|
||||
return
|
||||
box.left_top.x <= pos.x and box.left_top.y <= pos.y and box.right_bottom.x >= pos.x and box.right_bottom.y >= pos.y
|
||||
end
|
||||
|
||||
--- Return the box in explicit form.
|
||||
--- @param box BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.ensure_explicit(box)
|
||||
return {
|
||||
left_top = position.ensure_explicit(box.left_top or box[1]),
|
||||
right_bottom = position.ensure_explicit(box.right_bottom or box[2]),
|
||||
}
|
||||
end
|
||||
|
||||
--- Return the box in shorthand form.
|
||||
--- @param box BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.ensure_short(box)
|
||||
return {
|
||||
position.ensure_short(box.left_top or box[1]),
|
||||
position.ensure_short(box.right_bottom or box[2]),
|
||||
}
|
||||
end
|
||||
|
||||
--- Return a new box with initial dimensions box1, expanded to contain box2.
|
||||
--- @param box1 BoundingBox
|
||||
--- @param box2 BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.expand_to_contain_box(box1, box2)
|
||||
local box2 = flib_bounding_box.ensure_explicit(box2)
|
||||
|
||||
if box1.left_top then
|
||||
return {
|
||||
left_top = {
|
||||
x = math.min(box1.left_top.x, box2.left_top.x),
|
||||
y = math.min(box1.left_top.y, box2.left_top.y),
|
||||
},
|
||||
right_bottom = {
|
||||
x = math.max(box1.right_bottom.x, box2.right_bottom.x),
|
||||
y = math.max(box1.right_bottom.y, box2.right_bottom.y),
|
||||
},
|
||||
}
|
||||
else
|
||||
return {
|
||||
{
|
||||
math.min(box1[1][1], box2.left_top.x),
|
||||
math.min(box1[1][2], box2.left_top.y),
|
||||
},
|
||||
{
|
||||
math.max(box1[2][1], box2.right_bottom.x),
|
||||
math.max(box1[2][2], box2.right_bottom.y),
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a new box expanded to contain the given position.
|
||||
--- @param box BoundingBox
|
||||
--- @param pos MapPosition
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.expand_to_contain_position(box, pos)
|
||||
local pos = position.ensure_explicit(pos)
|
||||
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = math.min(box.left_top.x, pos.x), y = math.min(box.left_top.y, pos.y) },
|
||||
right_bottom = { x = math.max(box.right_bottom.x, pos.x), y = math.max(box.right_bottom.y, pos.y) },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ math.min(box[1][1], pos.x), math.min(box[1][2], pos.y) },
|
||||
{ math.max(box[2][1], pos.x), math.max(box[2][2], pos.y) },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a new box shrunk to the nearest tile edges.
|
||||
--- @param box BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.floor(box)
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = math.ceil(box.left_top.x), y = math.ceil(box.left_top.y) },
|
||||
right_bottom = { x = math.floor(box.right_bottom.x), y = math.floor(box.right_bottom.y) },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ math.ceil(box[1][1]), math.ceil(box[1][2]) },
|
||||
{ math.floor(box[2][1]), math.floor(box[2][2]) },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Create a new box from a centerpoint and dimensions.
|
||||
--- @param center MapPosition
|
||||
--- @param width number
|
||||
--- @param height number
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.from_dimensions(center, width, height)
|
||||
if center.x then
|
||||
return {
|
||||
left_top = { x = center.x - width / 2, y = center.y - height / 2 },
|
||||
right_bottom = { x = center.x - width / 2, y = center.y - height / 2 },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ center[1] - width / 2, center[2] - height / 2 },
|
||||
{ center[1] - width / 2, center[2] - height / 2 },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Create a 1x1 box from the given position, optionally snapped to the containing tile edges.
|
||||
--- @param pos MapPosition
|
||||
--- @param snap boolean?
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.from_position(pos, snap)
|
||||
if snap then
|
||||
pos = position.floor(pos)
|
||||
else
|
||||
pos = position.sub(pos, { 0.5, 0.5 })
|
||||
end
|
||||
local x = pos.x or pos[1]
|
||||
local y = pos.y or pos[2]
|
||||
if pos.x then
|
||||
return {
|
||||
left_top = { x = x, y = y },
|
||||
right_bottom = { x = x + 1, y = y + 1 },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ x, y },
|
||||
{ x + 1, y + 1 },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate the height of the box.
|
||||
--- @param box BoundingBox
|
||||
--- @return number
|
||||
function flib_bounding_box.height(box)
|
||||
if box.left_top then
|
||||
return box.right_bottom.y - box.left_top.y
|
||||
else
|
||||
return box[2][2] - box[1][2]
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the first box intersects (overlaps) the second box.
|
||||
--- @param box1 BoundingBox
|
||||
--- @param box2 BoundingBox
|
||||
--- @return boolean
|
||||
function flib_bounding_box.intersects_box(box1, box2)
|
||||
local box1 = flib_bounding_box.ensure_explicit(box1)
|
||||
local box2 = flib_bounding_box.ensure_explicit(box2)
|
||||
return
|
||||
box1.left_top.x < box2.right_bottom.x
|
||||
and box2.left_top.x < box1.right_bottom.x
|
||||
and box1.left_top.y < box2.right_bottom.y
|
||||
and box2.left_top.y < box1.right_bottom.y
|
||||
end
|
||||
|
||||
--- Return a new box with the same dimensions, moved by the given delta.
|
||||
--- @param box BoundingBox
|
||||
--- @param delta MapPosition
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.move(box, delta)
|
||||
local dx = delta.x or delta[1]
|
||||
local dy = delta.y or delta[2]
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = box.left_top.x + dx, y = box.left_top.y + dy },
|
||||
right_bottom = { x = box.right_bottom.x + dx, y = box.right_bottom.y + dy },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ box[1][1] + dx, box[1][2] + dy },
|
||||
{ box[2][1] + dx, box[2][2] + dy },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a new box with the same dimensions centered on the given position.
|
||||
--- @param box BoundingBox
|
||||
--- @param pos MapPosition
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.recenter_on(box, pos)
|
||||
local height = flib_bounding_box.height(box)
|
||||
local width = flib_bounding_box.width(box)
|
||||
|
||||
local pos_x = pos.x or pos[1]
|
||||
local pos_y = pos.y or pos[2]
|
||||
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = pos_x - (width / 2), y = pos_y - (width / 2) },
|
||||
right_bottom = { x = pos_x + (height / 2), y = pos_y + (height / 2) },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ pos_x - (width / 2), pos_y - (width / 2) },
|
||||
{ pos_x + (height / 2), pos_y + (height / 2) },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a new box grown or shrunk by the given delta. A positive delta will grow the box, a negative delta will
|
||||
--- shrink it.
|
||||
--- @param box BoundingBox
|
||||
--- @param delta number
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.resize(box, delta)
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = box.left_top.x - delta, y = box.left_top.y - delta },
|
||||
right_bottom = { x = box.right_bottom.x + delta, y = box.right_bottom.y + delta },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ box[1][1] - delta, box[1][2] - delta },
|
||||
{ box[2][1] + delta, box[2][2] + delta },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a new box rotated 90 degrees about its center.
|
||||
--- @param box BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.rotate(box)
|
||||
local center = flib_bounding_box.center(box)
|
||||
local radius_x = flib_bounding_box.width(box) / 2
|
||||
local radius_y = flib_bounding_box.height(box) / 2
|
||||
|
||||
if box.left_top then
|
||||
return {
|
||||
left_top = { x = center.x - radius_y, y = center.y - radius_x },
|
||||
right_bottom = { x = center.x + radius_y, y = center.y + radius_x },
|
||||
}
|
||||
else
|
||||
return {
|
||||
{ center.x - radius_y, center.y - radius_x },
|
||||
{ center.x + radius_y, center.y + radius_x },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a new box expanded to create a square.
|
||||
--- @param box BoundingBox
|
||||
--- @return BoundingBox
|
||||
function flib_bounding_box.square(box)
|
||||
local radius = math.max(flib_bounding_box.width(box), flib_bounding_box.height(box))
|
||||
return flib_bounding_box.from_dimensions(flib_bounding_box.center(box), radius, radius)
|
||||
end
|
||||
|
||||
--- Calculate the width of the box.
|
||||
--- @param box BoundingBox
|
||||
--- @return number
|
||||
function flib_bounding_box.width(box)
|
||||
if box.left_top then
|
||||
return box.right_bottom.x - box.left_top.x
|
||||
else
|
||||
return box[2][1] - box[1][1]
|
||||
end
|
||||
end
|
||||
|
||||
return flib_bounding_box
|
||||
319
.vscode/flib/changelog.txt
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.12.4
|
||||
Date: 2022-12-30
|
||||
Changes:
|
||||
- flib_selected_frame_action_button and flib_selected_tool_button styles now have a vertical offset
|
||||
- Improved slot style coloring to be more consistent
|
||||
- Updated table module to use more generics for callback return values
|
||||
Bugfixes:
|
||||
- Fixed missing clicked graphical sets on slot styles
|
||||
- Fixed missing return types in table module callback functions
|
||||
- Fixed that long mod names would cut off in the dictionary-lite progress window
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.12.3
|
||||
Date: 2022-12-14
|
||||
Bugfixes:
|
||||
- Fixed a crash when calling `bounding_box.rotate()`
|
||||
- The base mod dependency is no longer optional - it was causing the game to download flib versions that it could not run
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.12.2
|
||||
Date: 2022-12-06
|
||||
Changes:
|
||||
- [queue] If the queue is empty, returns nil instead of throwing an error
|
||||
- [queue] Renamed functions to be much more clear, updated types to use generics
|
||||
- Updated types to work with recent FMTK releases
|
||||
Bugfixes:
|
||||
- [dictionary-lite] Fixed an occasional crash related to Lua memory management internals
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.12.1
|
||||
Date: 2022-12-06
|
||||
Bugfixes:
|
||||
- [dictionary-lite] Fixed a crash when re-requesting a missed translation batch
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.12.0
|
||||
Date: 2022-12-06
|
||||
Features:
|
||||
- Added 'bounding-box' module
|
||||
- A cleaned up version of the 'area' module that treats bounding boxes as immutable and does not use metatables
|
||||
- Since the cleanup was a breaking change, a new module was created
|
||||
- Added 'dictionary-lite' module
|
||||
- Takes advantage of new Factorio API features to drastically simplify the process
|
||||
- Increases translation speed in singleplayer by a factor of 10
|
||||
- Added 'format' module with various string formatting functions
|
||||
- Added 'gui-lite' module
|
||||
- Removes redundant features, simplifies the building process, and enhances type-checking and autocomplete
|
||||
- Added 'migration.handle_on_configuration_changed()'
|
||||
- Added 'position' module with various position manipulation functions
|
||||
Changes:
|
||||
- Changed preferred require syntax to '/' instead of '.'
|
||||
- Deprecated 'area' module in favor of the 'bounding-box' module
|
||||
- Deprecated 'dictionary' module in favor of the 'dictionary-lite' module
|
||||
- Deprecated 'event' module - type checking did not work and it didn't provide much over using 'script' directly
|
||||
- Deprecated 'gui' module in favor of the 'gui-lite' module
|
||||
- Deprecated 'misc' module in favor of the 'format' and 'position' modules
|
||||
- Removed 'queue.load()' and queue metatables
|
||||
- Updated type definitions to use generics where possible
|
||||
Bugfixes:
|
||||
- [dictionary] Fixed translations getting stuck on Factorio 1.1.73+
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.11.2
|
||||
Date: 2022-09-18
|
||||
Bugfixes:
|
||||
- [dictionary] Fixed a crash when a player left the game under certain circumstances
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.11.1
|
||||
Date: 2022-09-12
|
||||
Changes:
|
||||
- [dictionary] Added tooltip to GUI header explaining what it is
|
||||
Bugfixes:
|
||||
- [dictionary] Fixed that translations would stop being requested under certain circumstances
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.11.0
|
||||
Date: 2022-09-04
|
||||
Features:
|
||||
- [area] Added `area.square()` to turn the area into a square
|
||||
- [area] Added `starting_offset` argument to `area.iterate()`
|
||||
- [dictionary] Added a GUI that shows translation progress
|
||||
- [math] Added `floored` and `ceiled` for flooring and ceiling to N decimal places.
|
||||
- [math] Added `sign` to return the signedness of a number as a multiplier
|
||||
- [math] Added `sum`, `maximum`, `minimum`, `midrange`, `range` for sets of numbers
|
||||
Changes:
|
||||
- [math] Deprecated `round_to`, `ceil_to`, `floor_to`
|
||||
- [math] `clamp` defaults to clamping between 0, 1 when min or max are not set
|
||||
- [dictionary] Removed string nesting to significantly reduce network traffic
|
||||
- This makes translation take significantly longer
|
||||
Bugfixes:
|
||||
- Fixed missing annotations in math and table libraries
|
||||
- [dictionary] Fixed that an empty translation would cause the translation after it to be skipped
|
||||
- [migration] Fixed a crash when loading the tutorial scenario
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.10.1
|
||||
Date: 2022-02-24
|
||||
Bugfixes:
|
||||
- [event] Fixed a crash when passing `nil` to `event.on_nth_tick`
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.10.0
|
||||
Date: 2022-02-24
|
||||
Features:
|
||||
- [direction] Added `direction.from_positions()`
|
||||
- [table] Added `table.get_or_insert()`
|
||||
Changes:
|
||||
- Converted from LDoc to EmmyLua annotations, enabling language server intellisense, but removing the docs website
|
||||
- A new website or other solution for online docs will be added someday. For now, in-line documentation was decided to be more beneficial.
|
||||
- [area] All constructor functions will automatically call `area.load()` to add the metatable
|
||||
- [area] All functions will ensure that any passed areas have `left_top` and `right_bottom` keys, and will automatically convert ones that do not
|
||||
- [migration] `migration.on_config_changed()` version migrations table is now optional
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.9.2
|
||||
Date: 2021-11-18
|
||||
Bugfixes:
|
||||
- Fixed the dictionary module not separating dictionaries from separate mods properly
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.9.1
|
||||
Date: 2021-11-16
|
||||
Bugfixes:
|
||||
- Fixed a crash when calling dictionary.load() before dictionary.init() has been able to fire
|
||||
- Fixed table.slice() and table.splice() stop-n-from-end being one position off
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.9.0
|
||||
Date: 2021-11-11
|
||||
Features:
|
||||
- Added `queue` module
|
||||
- Added `table.array_merge()` and `table.retrieve()`
|
||||
- Added `flib_pin` and `flib_settings` sprites in black, white, and disabled variants
|
||||
- These are for use with frame action buttons
|
||||
- Added `gui.flib-keep-open` and `gui.flib-settings` locales for use with the aforementioned sprites
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.8.4
|
||||
Date: 2021-09-19
|
||||
Features:
|
||||
- Added `flib-dictionary-levels-per-batch` setting
|
||||
Changes:
|
||||
- Children may now be defined in `gui.add()`
|
||||
- If you need to return refs to children, use `gui.build()` instead
|
||||
- `flib-translations-per-tick` setting is now hidden
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.8.3
|
||||
Date: 2021-08-20
|
||||
Bugfixes:
|
||||
- Fixed that math.round() was incorrect for some negative numbers
|
||||
- Fixed that the on-tick-n module was not documented and not mentioned in the v0.8.0 changelog
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.8.2
|
||||
Date: 2021-08-10
|
||||
Bugfixes:
|
||||
- Fixed the dictionary module not handling if the language detection string was lost across save/load
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.8.1
|
||||
Date: 2021-08-09
|
||||
Changes:
|
||||
- The dictionary module no longer provides an event to hook, instead returning the finished dictionaries directly from `process_translation`
|
||||
- This is to allow a single-source-of-truth for the language's dictionaries, which was the original intention, but returning them in an event broke that intention
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.8.0
|
||||
Date: 2021-08-08
|
||||
Features:
|
||||
- Added `area.from_shorthand` and `area.to_shorthand`
|
||||
- Added `dictionary` module, which is a fast and easy-to-use dictionary system for localised string translations
|
||||
- Added `flib_widthless_textfield` style
|
||||
- Added `gui.add`, `gui.set_action`, and `gui.get_action`
|
||||
- Added `on-tick-n` module, which allows you to schedule tasks to be executed on a specific tick
|
||||
- Added support for tags and actions in `gui.update`
|
||||
- Children in a `GuiBuildStructure` or `GuiUpdateStructure` can now be defined in the array portion, instead of in a `children` or `tabs` table
|
||||
- The subtables are still accepted for situations where they are appropriate (i.e. dynamically generated children)
|
||||
- `misc.delineate_number()` now supports decimal numbers
|
||||
Changes:
|
||||
- `area.load` will now automatically convert from a shorthanded area to a proper area if needed
|
||||
- Deprecated the old `gui` module and replaced it with the contents of `gui-beta`
|
||||
- Mods can still require `gui-beta`, but it will simply redirect to `gui` instead
|
||||
- Mods that depended on the old GUI module will need to update to the new one or download a copy of it to their mod - it is no longer supported whatsoever
|
||||
- Deprecated the `translation` module, replaced by the new `dictionary` module
|
||||
- The `translation` module still exists and can be used, but is no longer documented and its use is recommended against
|
||||
Bugfixes:
|
||||
- Fixed a crash when calling `gui.read_action()` when the element was invalid
|
||||
- Fixed `area.from_position` not actually creating a 1x1 area
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.7.0
|
||||
Date: 2021-02-12
|
||||
Features:
|
||||
- Added `area` module
|
||||
- Added `flib_titlebar_flow` style
|
||||
- Added `divisor` argument to `math.round()`, allowing rounding to the nearest multiple of N
|
||||
- Added `math.max_double` and `math.min_double`
|
||||
- Added argument to `misc.ticks_to_timestring` to preserve leading zeroes
|
||||
Optimizations:
|
||||
- Significant performance improvements to `gui-beta.build()`
|
||||
Bugfixes:
|
||||
- Fixed `math.max_uint` being one number too large
|
||||
- Fixed `data-util.create_icons()` not using all of the icon specifications
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.6.1
|
||||
Date: 2021-01-02
|
||||
Features:
|
||||
- Added proper documentation for the `gui-beta` module
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.6.0
|
||||
Date: 2020-11-23
|
||||
Features:
|
||||
- Added WIP `gui-beta` module
|
||||
- This module is experimental, but is currently relatively stable. Use at your own risk. See the source code for documentation.
|
||||
- Additional arguments may be passed to `migration.on_config_changed()` to be available in the migration functions
|
||||
- Added `data-util.dark_red_button_tileset`
|
||||
- Added `flib_tool_button_dark_red` button style
|
||||
Changes:
|
||||
- Updated to Factorio 1.1
|
||||
- Modified `flib_selected_frame_action_button` style to better match the vanilla buttons
|
||||
- Made `new_layers` argument to `data-util.create_icons()` optional
|
||||
- Removed deprecated require paths for `data-util` and `reverse-defines` modules
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.5.0
|
||||
Date: 2020-10-22
|
||||
Features:
|
||||
- Added indicator sprites
|
||||
- Added `flib_indicator` image style
|
||||
Changes:
|
||||
- Updated thumbnail
|
||||
- Changed `table.reduce()` to work with non-arrays
|
||||
- Changed `misc.ticks_to_timestring()` to use `game.ticks_played` by default, instead of `game.tick`
|
||||
Bugfixes:
|
||||
- Fixed a crash with serialise_localised_string (credit to dbeckwith)
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.4.1
|
||||
Date: 2020-09-22
|
||||
Features:
|
||||
- Added a third return value to `table.for_n_of` signifying whether or not the end of the table was reached
|
||||
- Added a third return value to the callback of `table.for_n_of`, which is a flag requesting an immediate iteration abort
|
||||
- Added `flib_tool_button_light_green` button style
|
||||
Changes:
|
||||
- `table.for_n_of` now checks against the first (key) return of `next()` rather than the second (value) return
|
||||
- This changes how custom `next()` functions will behave, and brings it into consistency with the rest of Lua
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.4.0
|
||||
Date: 2020-09-13
|
||||
Features:
|
||||
- Added `direction.to_vector_2d`
|
||||
- Additional arguments passed to `migration.run` will be passed on to the version functions within the migrations table
|
||||
- Added `math` module
|
||||
Bugfixes:
|
||||
- Fixed that using `gui.init()` to reset all filters did not update lookup tables
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.3.3
|
||||
Date: 2020-08-31
|
||||
Features:
|
||||
- Added a map setting to adjust translation speed (avoid multiplayer drops for those with slow internet speeds)
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.3.2
|
||||
Date: 2020-08-30
|
||||
Features:
|
||||
- Added `table.array_copy()`
|
||||
- Added `table.partial_sort()` for sorting an array over multiple ticks
|
||||
Changes:
|
||||
- Passing a custom `next` function to `table.for_n_of()` will bypass the key existence check
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.3.1
|
||||
Date: 2020-08-24
|
||||
Features:
|
||||
- Added event.register_on_entity_destroyed()
|
||||
- Added flib_naked_scroll_pane_no_padding GUI style
|
||||
Changes:
|
||||
- Updated to Factorio 1.0
|
||||
- Renamed data_util to data-util and reverse_defines to reverse-defines
|
||||
- The old paths will still work until v0.4.0, but will print deprecation warnings
|
||||
- Removed vertically_stretchable from the flib_naked_scroll_pane GUI style to fix zero-height issue in non-fixed-height containers
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.3.0
|
||||
Date: 2020-08-11
|
||||
Features:
|
||||
- Added "grey" and "orange" colored slot button styles
|
||||
- Added two new scroll-pane GUI styles
|
||||
- Added "table" module for working with tables
|
||||
Changes:
|
||||
- translation.init() is now required to be run during on_configuration_changed, in addition to on_init
|
||||
- This is to prevent the module translating and returning strings for things that may not exist anymore
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.2.0
|
||||
Date: 2020-07-26
|
||||
Features:
|
||||
- Added gui.check_filter_validity(), which is a new REQUIRED setup function for the GUI module
|
||||
- Added translation.is_translating(), which checks whether the given player is actively translating
|
||||
- Added translation.translating_players_count(), which returns the number of players that are actively translating
|
||||
- Added several button and empty-widget styles, see "styles.md" topic in docs
|
||||
Changes:
|
||||
- The base mod is now marked as optional, to allow compatibility with total overhaul mods
|
||||
Bugfixes:
|
||||
- Fixed a crash with the GUI module when a handler was removed between mod versions while being registered in global
|
||||
- Fixed that gui.remove_player_filters() would not update lookup tables
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.1.5
|
||||
Date: 2020-06-30
|
||||
Changes:
|
||||
- Moved styles into prototypes/styles
|
||||
Bugfixes:
|
||||
- Fixed nonexistent sound file error by inheriting from parent #13
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.1.4
|
||||
Date: 2020-06-18
|
||||
Bugfixes:
|
||||
- Fixed a crash with the translation module related to old code having incorrect variable names
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.1.3
|
||||
Date: 2020-06-18
|
||||
Features:
|
||||
- Added a myriad of colored slot styles; see "slot-styles.md" topic in docs
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.1.2
|
||||
Date: 2020-06-10
|
||||
Bugfixes:
|
||||
- get_energy_value didn't parse energy strings with no SI prefix or deka
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.1.1
|
||||
Date: 2020-06-10
|
||||
Features:
|
||||
- get_energy_value supports exponents from 10^-24 to 10^24
|
||||
Bugfixes:
|
||||
- get_energy_value returned string, string instead of nil when unable to parse exponent
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 0.1.0
|
||||
Date: 2020-05-24
|
||||
Features:
|
||||
- Initial release
|
||||
182
.vscode/flib/data-util.lua
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
--- Utilities for data stage prototype manipulation.
|
||||
--- @class flib_data_util
|
||||
local flib_data_util = {}
|
||||
|
||||
--- Copy a prototype, assigning a new name and minable properties.
|
||||
--- @param prototype table
|
||||
--- @param new_name string string
|
||||
--- @param remove_icon? boolean
|
||||
--- @return table
|
||||
function flib_data_util.copy_prototype(prototype, new_name, remove_icon)
|
||||
if not prototype.type or not prototype.name then
|
||||
error("Invalid prototype: prototypes must have name and type properties.")
|
||||
return --- @diagnostic disable-line
|
||||
end
|
||||
local p = table.deepcopy(prototype)
|
||||
p.name = new_name
|
||||
if p.minable and p.minable.result then
|
||||
p.minable.result = new_name
|
||||
end
|
||||
if p.place_result then
|
||||
p.place_result = new_name
|
||||
end
|
||||
if p.result then
|
||||
p.result = new_name
|
||||
end
|
||||
if p.results then
|
||||
for _, result in pairs(p.results) do
|
||||
if result.name == prototype.name then
|
||||
result.name = new_name
|
||||
end
|
||||
end
|
||||
end
|
||||
if remove_icon then
|
||||
p.icon = nil
|
||||
p.icon_size = nil
|
||||
p.icon_mipmaps = nil
|
||||
p.icons = nil
|
||||
end
|
||||
|
||||
return p
|
||||
end
|
||||
|
||||
--- Copy prototype.icon/icons to a new fully defined icons array, optionally adding new icon layers.
|
||||
---
|
||||
--- Returns `nil` if the prototype's icons are incorrectly or incompletely defined.
|
||||
--- @param prototype table
|
||||
--- @param new_layers? IconSpecification[]
|
||||
--- @return IconSpecification[]|nil
|
||||
function flib_data_util.create_icons(prototype, new_layers)
|
||||
if new_layers then
|
||||
for _, new_layer in pairs(new_layers) do
|
||||
if not new_layer.icon or not new_layer.icon_size then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if prototype.icons then
|
||||
local icons = {}
|
||||
for _, v in pairs(prototype.icons) do
|
||||
-- Over define as much as possible to minimize weirdness: https://forums.factorio.com/viewtopic.php?f=25&t=81980
|
||||
icons[#icons + 1] = {
|
||||
icon = v.icon,
|
||||
icon_size = v.icon_size or prototype.icon_size or 32,
|
||||
icon_mipmaps = v.icon_mipmaps or prototype.icon_mipmaps or 0,
|
||||
tint = v.tint,
|
||||
scale = v.scale or 1,
|
||||
shift = v.shift,
|
||||
}
|
||||
end
|
||||
if new_layers then
|
||||
for _, new_layer in pairs(new_layers) do
|
||||
icons[#icons + 1] = new_layer
|
||||
end
|
||||
end
|
||||
return icons
|
||||
elseif prototype.icon then
|
||||
local icons = {
|
||||
{
|
||||
icon = prototype.icon,
|
||||
icon_size = prototype.icon_size,
|
||||
icon_mipmaps = prototype.icon_mipmaps,
|
||||
tint = { r = 1, g = 1, b = 1, a = 1 },
|
||||
},
|
||||
}
|
||||
if new_layers then
|
||||
for _, new_layer in pairs(new_layers) do
|
||||
icons[#icons + 1] = new_layer
|
||||
end
|
||||
end
|
||||
return icons
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local exponent_multipliers = {
|
||||
["y"] = 0.000000000000000000000001,
|
||||
["z"] = 0.000000000000000000001,
|
||||
["a"] = 0.000000000000000001,
|
||||
["f"] = 0.000000000000001,
|
||||
["p"] = 0.000000000001,
|
||||
["n"] = 0.000000001,
|
||||
["u"] = 0.000001, -- μ is invalid
|
||||
["m"] = 0.001,
|
||||
["c"] = 0.01,
|
||||
["d"] = 0.1,
|
||||
[""] = 1,
|
||||
["da"] = 10,
|
||||
["h"] = 100,
|
||||
["k"] = 1000,
|
||||
["M"] = 1000000,
|
||||
["G"] = 1000000000,
|
||||
["T"] = 1000000000000,
|
||||
["P"] = 1000000000000000,
|
||||
["E"] = 1000000000000000000,
|
||||
["Z"] = 1000000000000000000000,
|
||||
["Y"] = 1000000000000000000000000,
|
||||
}
|
||||
|
||||
--- Convert an energy string to base unit value + suffix.
|
||||
---
|
||||
--- Returns `nil` if `energy_string` is incorrectly formatted.
|
||||
--- @param energy_string string
|
||||
--- @return number?
|
||||
--- @return string?
|
||||
function flib_data_util.get_energy_value(energy_string)
|
||||
if type(energy_string) == "string" then
|
||||
local v, _, exp, unit = string.match(energy_string, "([%-+]?[0-9]*%.?[0-9]+)((%D*)([WJ]))")
|
||||
local value = tonumber(v)
|
||||
if value and exp and exponent_multipliers[exp] then
|
||||
value = value * exponent_multipliers[exp]
|
||||
return value, unit
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Build a sprite from constituent parts.
|
||||
--- @param name? string
|
||||
--- @param position? MapPosition
|
||||
--- @param filename? string
|
||||
--- @param size? Vector
|
||||
--- @param mipmap_count? number
|
||||
--- @param mods? table
|
||||
--- @return SpriteSpecification
|
||||
function flib_data_util.build_sprite(name, position, filename, size, mipmap_count, mods)
|
||||
local def = {
|
||||
type = "sprite",
|
||||
name = name,
|
||||
filename = filename,
|
||||
position = position,
|
||||
size = size,
|
||||
mipmap_count = mipmap_count,
|
||||
flags = { "icon" },
|
||||
}
|
||||
if mods then
|
||||
for k, v in pairs(mods) do
|
||||
def[k] = v
|
||||
end
|
||||
end
|
||||
return def
|
||||
end
|
||||
|
||||
--- An empty image. This image is 8x8 to facilitate usage with GUI styles.
|
||||
flib_data_util.empty_image = "__flib__/graphics/empty.png"
|
||||
|
||||
--- A black image, for use with tool backgrounds. This image is 1x1.
|
||||
flib_data_util.black_image = "__flib__/graphics/black.png"
|
||||
|
||||
--- A desaturated planner image. Tint this sprite to easily add your own planners.
|
||||
flib_data_util.planner_base_image = "__flib__/graphics/planner.png"
|
||||
|
||||
--- A dark red button tileset. Used for the `flib_tool_button_dark_red` style.
|
||||
flib_data_util.dark_red_button_tileset = "__flib__/graphics/dark-red-button.png"
|
||||
|
||||
return flib_data_util
|
||||
|
||||
--- @class IconSpecification
|
||||
--- @field icon string
|
||||
--- @field icon_size int
|
||||
--- @class SpriteSpecification
|
||||
2
.vscode/flib/data.lua
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
require("prototypes/sprite")
|
||||
require("prototypes/style")
|
||||
476
.vscode/flib/dictionary-lite.lua
vendored
Normal file
@@ -0,0 +1,476 @@
|
||||
local gui = require("__flib__/gui-lite")
|
||||
local mod_gui = require("__core__/lualib/mod-gui")
|
||||
local table = require("__flib__/table")
|
||||
|
||||
--- Utilities for creating dictionaries of localised string translations.
|
||||
--- @class flib_dictionary
|
||||
local flib_dictionary = {}
|
||||
|
||||
local request_timeout_ticks = (60 * 5)
|
||||
|
||||
--- @param init_only boolean?
|
||||
--- @return flib_dictionary_global
|
||||
local function get_data(init_only)
|
||||
if not global.__flib or not global.__flib.dictionary then
|
||||
error("Dictionary module was not properly initialized - ensure that all lifecycle events are handled.")
|
||||
end
|
||||
local data = global.__flib.dictionary
|
||||
if init_only and data.init_ran then
|
||||
error("Dictionaries cannot be modified after initialization.")
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
--- @param data flib_dictionary_global
|
||||
--- @param language string
|
||||
--- @return LuaPlayer?
|
||||
local function get_translator(data, language)
|
||||
for player_index, player_language in pairs(data.player_languages) do
|
||||
if player_language == language then
|
||||
local player = game.get_player(player_index)
|
||||
if player and player.connected then
|
||||
return player
|
||||
end
|
||||
end
|
||||
end
|
||||
-- There is no available translator, so remove this language from the pool
|
||||
for player_index, player_language in pairs(data.player_languages) do
|
||||
if player_language == language then
|
||||
data.player_languages[player_index] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param data flib_dictionary_global
|
||||
local function update_gui(data)
|
||||
local wip = data.wip
|
||||
for _, player in pairs(game.players) do
|
||||
local frame_flow = mod_gui.get_frame_flow(player)
|
||||
local window = frame_flow.flib_translation_progress
|
||||
if wip then
|
||||
if not window then
|
||||
_, window = gui.add(frame_flow, {
|
||||
type = "frame",
|
||||
name = "flib_translation_progress",
|
||||
style = mod_gui.frame_style,
|
||||
direction = "vertical",
|
||||
{
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = { "gui.flib-translating-dictionaries" },
|
||||
tooltip = { "gui.flib-translating-dictionaries-description" },
|
||||
},
|
||||
{
|
||||
type = "frame",
|
||||
name = "pane",
|
||||
style = "inside_shallow_frame_with_padding",
|
||||
style_mods = { top_padding = 8 },
|
||||
direction = "vertical",
|
||||
},
|
||||
})
|
||||
end
|
||||
local pane = window.pane --[[@as LuaGuiElement]]
|
||||
local mod_flow = pane[script.mod_name]
|
||||
if not mod_flow then
|
||||
_, mod_flow = gui.add(pane, {
|
||||
type = "flow",
|
||||
name = script.mod_name,
|
||||
style = "centering_horizontal_flow",
|
||||
style_mods = { top_margin = 4, horizontal_spacing = 8 },
|
||||
{
|
||||
type = "label",
|
||||
style = "caption_label",
|
||||
style_mods = { minimal_width = 130 },
|
||||
caption = { "?", { "mod-name." .. script.mod_name }, script.mod_name },
|
||||
ignored_by_interaction = true,
|
||||
},
|
||||
{ type = "empty-widget", style = "flib_horizontal_pusher" },
|
||||
{ type = "label", name = "language", style = "bold_label", ignored_by_interaction = true },
|
||||
{
|
||||
type = "progressbar",
|
||||
name = "bar",
|
||||
style_mods = { top_margin = 1, width = 100 },
|
||||
ignored_by_interaction = true,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "percentage",
|
||||
style = "bold_label",
|
||||
style_mods = { width = 24, horizontal_align = "right" },
|
||||
ignored_by_interaction = true,
|
||||
},
|
||||
})
|
||||
end
|
||||
local progress = wip.received_count / data.raw_count
|
||||
mod_flow.language.caption = wip.language
|
||||
mod_flow.bar.value = progress --[[@as double]]
|
||||
mod_flow.percentage.caption = tostring(math.min(math.floor(progress * 100), 99)) .. "%"
|
||||
mod_flow.tooltip =
|
||||
{ "", (wip.dict or { "gui.flib-finishing" }), "\n" .. wip.received_count .. " / " .. data.raw_count }
|
||||
else
|
||||
if window then
|
||||
local mod_flow = window.pane[script.mod_name]
|
||||
if mod_flow then
|
||||
mod_flow.destroy()
|
||||
end
|
||||
if #window.pane.children == 0 then
|
||||
window.destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param data flib_dictionary_global
|
||||
--- @return boolean success
|
||||
local function request_next_batch(data)
|
||||
local raw = data.raw
|
||||
local wip = data.wip --[[@as DictWipData]]
|
||||
if wip.finished then
|
||||
return false
|
||||
end
|
||||
local requests, strings = {}, {}
|
||||
for i = 1, game.is_multiplayer() and 5 or 50 do
|
||||
local string
|
||||
repeat
|
||||
wip.key, string = next(raw[wip.dict], wip.key)
|
||||
if not wip.key then
|
||||
wip.dict = next(raw, wip.dict)
|
||||
if not wip.dict then
|
||||
-- We are done!
|
||||
wip.finished = true
|
||||
end
|
||||
end
|
||||
until string or wip.finished
|
||||
if wip.finished then
|
||||
break
|
||||
end
|
||||
requests[i] = { dict = wip.dict, key = wip.key }
|
||||
strings[i] = string
|
||||
end
|
||||
|
||||
if #strings == 0 then
|
||||
return false -- Finished
|
||||
end
|
||||
|
||||
local translator = wip.translator
|
||||
if not translator.valid or not translator.connected then
|
||||
local new_translator = get_translator(data, wip.language)
|
||||
if new_translator then
|
||||
wip.translator = new_translator
|
||||
else
|
||||
-- Cancel this translation
|
||||
data.wip = nil
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local ids = wip.translator.request_translations(strings)
|
||||
if not ids then
|
||||
return false
|
||||
end
|
||||
for i = 1, #ids do
|
||||
wip.requests[ids[i]] = requests[i]
|
||||
end
|
||||
wip.request_tick = game.tick
|
||||
|
||||
update_gui(data)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- @param data flib_dictionary_global
|
||||
local function handle_next_language(data)
|
||||
while not data.wip and #data.to_translate > 0 do
|
||||
local next_language = table.remove(data.to_translate, 1)
|
||||
if next_language then
|
||||
local translator = get_translator(data, next_language)
|
||||
if translator then
|
||||
-- Start translation
|
||||
local dicts = {}
|
||||
local first_dict
|
||||
for name in pairs(data.raw) do
|
||||
first_dict = first_dict or name
|
||||
dicts[name] = {}
|
||||
end
|
||||
-- Don't do anything if there are no dictionaries to translate
|
||||
if not first_dict then
|
||||
return
|
||||
end
|
||||
--- @class DictWipData
|
||||
data.wip = {
|
||||
dict = first_dict,
|
||||
dicts = dicts,
|
||||
finished = false,
|
||||
--- @type string?
|
||||
key = nil,
|
||||
language = next_language,
|
||||
received_count = 0,
|
||||
--- @type table<uint, DictTranslationRequest>
|
||||
requests = {},
|
||||
request_tick = 0,
|
||||
translator = translator,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Events
|
||||
|
||||
flib_dictionary.on_player_dictionaries_ready = script.generate_event_name()
|
||||
--- Called when a player's dictionaries are ready to be used. Handling this event is not required.
|
||||
--- @class EventData.on_player_dictionaries_ready: EventData
|
||||
--- @field player_index uint
|
||||
|
||||
flib_dictionary.on_player_language_changed = script.generate_event_name()
|
||||
--- Called when a player's language changes. Handling this event is not required.
|
||||
--- @class EventData.on_player_language_changed: EventData
|
||||
--- @field player_index uint
|
||||
--- @field language string
|
||||
|
||||
-- Lifecycle handlers
|
||||
|
||||
function flib_dictionary.on_init()
|
||||
-- Initialize global data
|
||||
if not global.__flib then
|
||||
global.__flib = {}
|
||||
end
|
||||
--- @class flib_dictionary_global
|
||||
global.__flib.dictionary = {
|
||||
init_ran = false,
|
||||
--- @type table<uint, string>
|
||||
player_languages = {},
|
||||
--- @type table<uint, DictLangRequest>
|
||||
player_language_requests = {},
|
||||
--- @type table<string, Dictionary>
|
||||
raw = {},
|
||||
raw_count = 0,
|
||||
--- @type string[]
|
||||
to_translate = {},
|
||||
--- @type table<string, table<string, TranslatedDictionary>>
|
||||
translated = {},
|
||||
--- @type DictWipData?
|
||||
wip = nil,
|
||||
}
|
||||
-- Initialize all existing players
|
||||
for player_index, player in pairs(game.players) do
|
||||
if player.connected then
|
||||
flib_dictionary.on_player_joined_game({
|
||||
--- @cast player_index uint
|
||||
player_index = player_index,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
flib_dictionary.on_configuration_changed = flib_dictionary.on_init
|
||||
|
||||
function flib_dictionary.on_tick()
|
||||
local data = get_data()
|
||||
if not data.init_ran then
|
||||
data.init_ran = true
|
||||
end
|
||||
|
||||
-- Player language requests
|
||||
for id, request in pairs(data.player_language_requests) do
|
||||
if game.tick - request.tick > request_timeout_ticks then
|
||||
local player = request.player
|
||||
if player.valid and player.connected then
|
||||
local id = player.request_translation({ "locale-identifier" })
|
||||
if id then
|
||||
data.player_language_requests[id] = {
|
||||
player = player,
|
||||
tick = game.tick,
|
||||
}
|
||||
end
|
||||
end
|
||||
-- Deletion must be last so that the deleted entry isn't re-used for the new entry in memory
|
||||
data.player_language_requests[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local wip = data.wip
|
||||
if not wip then
|
||||
return
|
||||
end
|
||||
|
||||
if game.tick - wip.request_tick > request_timeout_ticks then
|
||||
-- next() will return the first string from the last batch because it was inserted first
|
||||
local _, request = next(wip.requests)
|
||||
wip.dict = request.dict
|
||||
wip.finished = false
|
||||
wip.key = request.key
|
||||
wip.requests = {}
|
||||
request_next_batch(data)
|
||||
update_gui(data)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param e EventData.on_string_translated
|
||||
function flib_dictionary.on_string_translated(e)
|
||||
local data = get_data()
|
||||
local id = e.id
|
||||
|
||||
-- Player language requests
|
||||
local request = data.player_language_requests[id]
|
||||
if request then
|
||||
data.player_language_requests[id] = nil
|
||||
if not e.translated then
|
||||
error("Language key request for player " .. e.player_index .. " failed")
|
||||
end
|
||||
if data.player_languages[e.player_index] ~= e.result then
|
||||
data.player_languages[e.player_index] = e.result
|
||||
script.raise_event(
|
||||
flib_dictionary.on_player_language_changed,
|
||||
{ player_index = e.player_index, language = e.result }
|
||||
)
|
||||
if data.translated[e.result] then
|
||||
script.raise_event(flib_dictionary.on_player_dictionaries_ready, { player_index = e.player_index })
|
||||
return
|
||||
elseif data.wip and data.wip.language == e.result then
|
||||
return
|
||||
elseif table.find(data.to_translate, e.result) then
|
||||
return
|
||||
else
|
||||
table.insert(data.to_translate, e.result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
handle_next_language(data)
|
||||
|
||||
local wip = data.wip
|
||||
if not wip then
|
||||
return
|
||||
end
|
||||
|
||||
local request = wip.requests[id]
|
||||
if request then
|
||||
wip.requests[id] = nil
|
||||
wip.received_count = wip.received_count + 1
|
||||
if e.translated then
|
||||
wip.dicts[request.dict][request.key] = e.result
|
||||
end
|
||||
end
|
||||
|
||||
while wip and table_size(wip.requests) == 0 and not request_next_batch(data) do
|
||||
if wip.finished then
|
||||
data.translated[wip.language] = wip.dicts
|
||||
data.wip = nil
|
||||
for player_index, language in pairs(data.player_languages) do
|
||||
if wip.language == language then
|
||||
script.raise_event(flib_dictionary.on_player_dictionaries_ready, { player_index = player_index })
|
||||
end
|
||||
end
|
||||
end
|
||||
handle_next_language(data)
|
||||
update_gui(data)
|
||||
wip = data.wip
|
||||
end
|
||||
end
|
||||
|
||||
--- @param e EventData.on_player_joined_game
|
||||
function flib_dictionary.on_player_joined_game(e)
|
||||
-- Request the player's locale identifier
|
||||
local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
|
||||
local id = player.request_translation({ "locale-identifier" })
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
local data = get_data()
|
||||
data.player_language_requests[id] = {
|
||||
player = player,
|
||||
tick = game.tick,
|
||||
}
|
||||
update_gui(data)
|
||||
end
|
||||
|
||||
--- Handle all non-bootstrap events with default event handlers. Will not overwrite any existing handlers. If you have
|
||||
--- custom handlers for on_tick, on_string_translated, or on_player_joined_game, ensure that you call the corresponding
|
||||
--- module lifecycle handler..
|
||||
function flib_dictionary.handle_events()
|
||||
for id, handler in pairs({
|
||||
[defines.events.on_tick] = flib_dictionary.on_tick,
|
||||
[defines.events.on_string_translated] = flib_dictionary.on_string_translated,
|
||||
[defines.events.on_player_joined_game] = flib_dictionary.on_player_joined_game,
|
||||
}) do
|
||||
if
|
||||
not script.get_event_handler(id --[[@as uint]])
|
||||
then
|
||||
script.on_event(id, handler)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Dictionary creation
|
||||
|
||||
--- Create a new dictionary. The name must be unique.
|
||||
--- @param name string
|
||||
--- @param initial_strings Dictionary?
|
||||
function flib_dictionary.new(name, initial_strings)
|
||||
local raw = get_data(true).raw
|
||||
if raw[name] then
|
||||
error("Attempted to create dictionary '" .. name .. "' twice.")
|
||||
end
|
||||
raw[name] = initial_strings or {}
|
||||
end
|
||||
|
||||
--- Add the given string to the dictionary.
|
||||
--- @param dict_name string
|
||||
--- @param key string
|
||||
--- @param localised LocalisedString
|
||||
function flib_dictionary.add(dict_name, key, localised)
|
||||
local data = get_data(true)
|
||||
local raw = data.raw[dict_name]
|
||||
if not raw then
|
||||
error("Dictionary '" .. dict_name .. "' does not exist.")
|
||||
end
|
||||
if not raw[key] then
|
||||
data.raw_count = data.raw_count + 1
|
||||
end
|
||||
raw[key] = localised
|
||||
end
|
||||
|
||||
--- Get all dictionaries for the player. Will return `nil` if the player's language has not finished translating.
|
||||
--- @param player_index uint
|
||||
--- @return table<string, TranslatedDictionary>?
|
||||
function flib_dictionary.get_all(player_index)
|
||||
local data = get_data()
|
||||
local language = data.player_languages[player_index]
|
||||
if not language then
|
||||
return
|
||||
end
|
||||
return data.translated[language]
|
||||
end
|
||||
|
||||
--- Get the specified dictionary for the player. Will return `nil` if the dictionary has not finished translating.
|
||||
--- @param player_index uint
|
||||
--- @param dict_name string
|
||||
--- @return TranslatedDictionary?
|
||||
function flib_dictionary.get(player_index, dict_name)
|
||||
local data = get_data()
|
||||
if not data.raw[dict_name] then
|
||||
error("Dictionary '" .. dict_name .. "' does not exist.")
|
||||
end
|
||||
local language_dicts = flib_dictionary.get_all(player_index) or {}
|
||||
return language_dicts[dict_name]
|
||||
end
|
||||
|
||||
--- @class DictLangRequest
|
||||
--- @field player LuaPlayer
|
||||
--- @field tick uint
|
||||
|
||||
--- @class DictTranslationRequest
|
||||
--- @field language string
|
||||
--- @field dict string
|
||||
--- @field key string
|
||||
|
||||
--- Localised strings identified by an internal key. Keys must be unique and language-agnostic.
|
||||
--- @alias Dictionary table<string, LocalisedString>
|
||||
|
||||
--- Translations are identified by their internal key. If the translation failed, then it will not be present. Locale
|
||||
--- fallback groups can be used if every key needs a guaranteed translation.
|
||||
--- @alias TranslatedDictionary table<string, string>
|
||||
|
||||
return flib_dictionary
|
||||
424
.vscode/flib/dictionary.lua
vendored
Normal file
@@ -0,0 +1,424 @@
|
||||
local gui = require("__flib__/gui-lite")
|
||||
local mod_gui = require("__core__/lualib/mod-gui")
|
||||
local table = require("__flib__/table")
|
||||
|
||||
--- @diagnostic disable
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
local flib_dictionary = {}
|
||||
|
||||
local inner_separator = "" -- U+E000
|
||||
local separator = "" -- U+E001
|
||||
local translation_timeout = 600
|
||||
|
||||
-- Depending on the value of `use_local_storage`, this will be tied to `global` or will be re-generated during `on_load`
|
||||
local raw = { _total_strings = 0 }
|
||||
|
||||
local use_local_storage = false
|
||||
|
||||
local function key_value(key, value)
|
||||
return key .. inner_separator .. value .. separator
|
||||
end
|
||||
|
||||
local RawDictionary = {}
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function RawDictionary:add(internal, translation)
|
||||
local to_add = { "", internal, inner_separator, { "?", translation, "FLIB_TRANSLATION_FAILED" }, separator }
|
||||
|
||||
local ref = self.ref
|
||||
local i = self.batch_i + 1
|
||||
-- Due to network saturation concerns, only group five strings together
|
||||
-- See https://github.com/factoriolib/flib/issues/45
|
||||
if i < 5 then
|
||||
ref[i] = to_add --- @diagnostic disable-line
|
||||
self.batch_i = i
|
||||
else
|
||||
local s_i = self.dict_i + 1
|
||||
self.dict_i = s_i
|
||||
local new_set = { "", to_add }
|
||||
self.ref = new_set
|
||||
self.strings[s_i] = new_set
|
||||
self.batch_i = 2
|
||||
end
|
||||
self.total = self.total + 1
|
||||
raw._total_strings = raw._total_strings + 1
|
||||
end
|
||||
|
||||
--- @class RawDictionary
|
||||
function flib_dictionary.new(name, keep_untranslated, initial_contents)
|
||||
if raw[name] then
|
||||
error("Dictionary with the name `" .. name .. "` already exists.")
|
||||
end
|
||||
|
||||
--- @type LocalisedString
|
||||
local initial_string = { "" }
|
||||
--- @class RawDictionary
|
||||
local self = {
|
||||
-- Indices
|
||||
batch_i = 1,
|
||||
dict_i = 1,
|
||||
total = 1,
|
||||
-- Internal
|
||||
ref = initial_string,
|
||||
--- @type LocalisedString
|
||||
strings = { initial_string },
|
||||
-- Meta
|
||||
name = name,
|
||||
}
|
||||
setmetatable(self, { __index = RawDictionary })
|
||||
|
||||
for key, value in pairs(initial_contents or {}) do
|
||||
self:add(key, value)
|
||||
end
|
||||
--- @diagnostic disable-next-line
|
||||
raw[name] = { strings = self.strings, keep_untranslated = keep_untranslated }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.init()
|
||||
if not global.__flib then
|
||||
global.__flib = {}
|
||||
end
|
||||
global.__flib.dictionary = {
|
||||
in_process = {},
|
||||
players = {},
|
||||
raw = { _total_strings = 0 },
|
||||
translated = {},
|
||||
}
|
||||
if use_local_storage then
|
||||
raw = { _total_strings = 0 }
|
||||
else
|
||||
raw = global.__flib.dictionary.raw
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.load()
|
||||
if not use_local_storage and global.__flib and global.__flib.dictionary then
|
||||
raw = global.__flib.dictionary.raw
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.translate(player)
|
||||
if not player.connected then
|
||||
error("Player must be connected to the game before this function can be called!")
|
||||
end
|
||||
|
||||
local player_data = global.__flib.dictionary.players[player.index]
|
||||
if player_data then
|
||||
return
|
||||
end
|
||||
global.__flib.dictionary.players[player.index] = {
|
||||
player = player,
|
||||
status = "get_language",
|
||||
requested_tick = game.tick,
|
||||
}
|
||||
|
||||
player.request_translation({ "", "FLIB_LOCALE_IDENTIFIER", separator, { "locale-identifier" } })
|
||||
end
|
||||
|
||||
local function request_translation(player_data)
|
||||
local string = raw[player_data.dictionary].strings[player_data.i]
|
||||
|
||||
-- We use `while` instead of `if` here just in case a dictionary doesn't have any strings in it
|
||||
while not string do
|
||||
local next_dictionary = next(raw, player_data.dictionary)
|
||||
if next_dictionary then
|
||||
-- Set the next dictionary and reset index
|
||||
player_data.dictionary = next_dictionary
|
||||
player_data.i = 1
|
||||
string = raw[next_dictionary].strings[1]
|
||||
else
|
||||
-- We're done!
|
||||
player_data.status = "finished"
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
player_data.player.request_translation({
|
||||
"",
|
||||
key_value("FLIB_DICTIONARY_MOD", script.mod_name),
|
||||
key_value("FLIB_DICTIONARY_NAME", player_data.dictionary),
|
||||
key_value("FLIB_DICTIONARY_LANGUAGE", player_data.language),
|
||||
key_value("FLIB_DICTIONARY_STRING_INDEX", player_data.i),
|
||||
string,
|
||||
})
|
||||
|
||||
player_data.requested_tick = game.tick
|
||||
end
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.check_skipped()
|
||||
local script_data = global.__flib.dictionary
|
||||
local tick = game.tick
|
||||
for _, player_data in pairs(script_data.players) do
|
||||
-- If it's been longer than the timeout, request the string again
|
||||
-- This is to solve a very rare edge case where translations requested on the same tick that a singleplayer game
|
||||
-- is saved will not be returned when that save is loaded
|
||||
if (player_data.requested_tick or 0) + translation_timeout <= tick then
|
||||
if player_data.status == "get_language" then
|
||||
player_data.player.request_translation({ "", "FLIB_LOCALE_IDENTIFIER", separator, { "locale-identifier" } })
|
||||
end
|
||||
if player_data.status == "translating" then
|
||||
request_translation(player_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Escape match special characters
|
||||
local function match_literal(s)
|
||||
return string.gsub(s, "%-", "%%-")
|
||||
end
|
||||
|
||||
--- @param dict_lang string
|
||||
local function clean_gui(dict_lang)
|
||||
for _, player in pairs(game.players) do
|
||||
local window = mod_gui.get_frame_flow(player).flib_translation_progress
|
||||
if window then
|
||||
local pane = window.pane
|
||||
local mod_flow = pane[script.mod_name]
|
||||
if mod_flow then
|
||||
local lang_flow = mod_flow[dict_lang]
|
||||
if lang_flow then
|
||||
lang_flow.destroy()
|
||||
end
|
||||
if #mod_flow.children == 1 then
|
||||
mod_flow.destroy()
|
||||
end
|
||||
end
|
||||
if #pane.children == 0 then
|
||||
window.destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dictionary_match_string = key_value("^FLIB_DICTIONARY_MOD", match_literal(script.mod_name))
|
||||
.. key_value("FLIB_DICTIONARY_NAME", "(.-)")
|
||||
.. key_value("FLIB_DICTIONARY_LANGUAGE", "(.-)")
|
||||
.. key_value("FLIB_DICTIONARY_STRING_INDEX", "(%d-)")
|
||||
.. "(.*)$"
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.process_translation(event_data)
|
||||
if not event_data.translated then
|
||||
return
|
||||
end
|
||||
local script_data = global.__flib.dictionary
|
||||
if string.find(event_data.result, "FLIB_DICTIONARY_NAME") then
|
||||
local _, _, dict_name, dict_lang, string_index, translation =
|
||||
string.find(event_data.result, dictionary_match_string)
|
||||
|
||||
if dict_name and dict_lang and string_index and translation then
|
||||
local language_data = script_data.in_process[dict_lang]
|
||||
-- In some cases, this can fire before on_configuration_changed
|
||||
if not language_data then
|
||||
return
|
||||
end
|
||||
local dictionary = language_data.dictionaries[dict_name]
|
||||
if not dictionary then
|
||||
return
|
||||
end
|
||||
local dict_data = raw[dict_name]
|
||||
local player_data = script_data.players[event_data.player_index]
|
||||
|
||||
-- If this number does not match, this is a duplicate, so ignore it
|
||||
if tonumber(string_index) == player_data.i then
|
||||
-- Extract current string's translations
|
||||
for str in string.gmatch(translation, "(.-)" .. separator) do
|
||||
local _, _, key, value = string.find(str, "^(.-)" .. inner_separator .. "(.-)$")
|
||||
if key then
|
||||
-- If `keep_untranslated` is true, then use the key as the value if it failed
|
||||
local failed = string.find(value, "FLIB_TRANSLATION_FAILED")
|
||||
if failed and dict_data.keep_untranslated then
|
||||
value = key
|
||||
elseif failed then
|
||||
value = nil
|
||||
end
|
||||
if value then
|
||||
dictionary[key] = value
|
||||
end
|
||||
language_data.translated_i = language_data.translated_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Request next translation
|
||||
player_data.i = player_data.i + 1
|
||||
request_translation(player_data)
|
||||
|
||||
-- GUI
|
||||
for _, player in pairs(game.players) do
|
||||
--- @type LuaGuiElement
|
||||
local flow = mod_gui.get_frame_flow(player)
|
||||
if not flow.flib_translation_progress then
|
||||
gui.add(flow, {
|
||||
type = "frame",
|
||||
name = "flib_translation_progress",
|
||||
style = mod_gui.frame_style,
|
||||
style_mods = { width = 350 },
|
||||
direction = "vertical",
|
||||
{
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = { "gui.flib-translating-dictionaries" },
|
||||
tooltip = { "gui.flib-translating-dictionaries-description" },
|
||||
},
|
||||
{
|
||||
type = "frame",
|
||||
name = "pane",
|
||||
style = "inside_shallow_frame_with_padding",
|
||||
style_mods = { top_padding = 8 },
|
||||
direction = "vertical",
|
||||
},
|
||||
})
|
||||
end
|
||||
local pane = flow.flib_translation_progress.pane --[[@as LuaGuiElement]]
|
||||
if not pane[script.mod_name] then
|
||||
gui.add(pane, {
|
||||
type = "flow",
|
||||
name = script.mod_name,
|
||||
direction = "vertical",
|
||||
{ type = "label", style = "caption_label", style_mods = { top_margin = 4 }, caption = script.mod_name },
|
||||
})
|
||||
end
|
||||
local mod_flow = pane[script.mod_name] --[[@as LuaGuiElement]]
|
||||
if not mod_flow[dict_lang] then
|
||||
gui.add(mod_flow, {
|
||||
type = "flow",
|
||||
name = dict_lang,
|
||||
style_mods = { vertical_align = "center", horizontal_spacing = 8 },
|
||||
{ type = "label", style = "bold_label", caption = dict_lang },
|
||||
{ type = "progressbar", name = "bar", style_mods = { horizontally_stretchable = true } },
|
||||
{ type = "label", name = "label", style = "bold_label" },
|
||||
})
|
||||
end
|
||||
local progress = language_data.translated_i / raw._total_strings
|
||||
mod_flow[dict_lang].bar.value = progress --[[@as double]]
|
||||
mod_flow[dict_lang].label.caption = tostring(math.ceil(progress * 100)) .. "%"
|
||||
mod_flow[dict_lang].label.tooltip = dict_name
|
||||
.. "\n"
|
||||
.. language_data.translated_i
|
||||
.. " / "
|
||||
.. raw._total_strings
|
||||
end
|
||||
|
||||
if player_data.status == "finished" then
|
||||
-- Clean up translation data
|
||||
script_data.translated[dict_lang] = language_data.dictionaries
|
||||
script_data.in_process[dict_lang] = nil
|
||||
for _, player_index in pairs(language_data.players) do
|
||||
script_data.players[player_index] = nil
|
||||
end
|
||||
|
||||
-- Clean up GUI
|
||||
clean_gui(dict_lang)
|
||||
|
||||
return { dictionaries = language_data.dictionaries, language = dict_lang, players = language_data.players }
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif string.find(event_data.result, "^FLIB_LOCALE_IDENTIFIER") then
|
||||
local _, _, language = string.find(event_data.result, "^FLIB_LOCALE_IDENTIFIER" .. separator .. "(.*)$")
|
||||
if language then
|
||||
local player_data = script_data.players[event_data.player_index]
|
||||
-- Handle duplicates
|
||||
if not player_data or player_data.status ~= "get_language" then
|
||||
return
|
||||
end
|
||||
|
||||
player_data.language = language
|
||||
|
||||
-- Check if this language is already translated or being translated
|
||||
local dictionaries = script_data.translated[language]
|
||||
if dictionaries then
|
||||
script_data.players[event_data.player_index] = nil
|
||||
return { dictionaries = dictionaries, language = language, players = { event_data.player_index } }
|
||||
end
|
||||
local in_process = script_data.in_process[language]
|
||||
if in_process then
|
||||
table.insert(in_process.players, event_data.player_index)
|
||||
player_data.status = "waiting"
|
||||
return
|
||||
end
|
||||
|
||||
-- Set up player data for translating
|
||||
player_data.status = "translating"
|
||||
player_data.dictionary = next(raw, "_total_strings")
|
||||
player_data.i = 1
|
||||
|
||||
-- Add language to in process data
|
||||
script_data.in_process[language] = {
|
||||
dictionaries = table.map(raw, function(_, k)
|
||||
if k ~= "_total_strings" then
|
||||
return {}
|
||||
end
|
||||
end),
|
||||
players = { event_data.player_index },
|
||||
translated_i = 0,
|
||||
}
|
||||
|
||||
-- Start translating
|
||||
request_translation(player_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.cancel_translation(player_index)
|
||||
local script_data = global.__flib.dictionary
|
||||
local player_data = script_data.players[player_index]
|
||||
if not player_data then
|
||||
return
|
||||
end
|
||||
-- Delete this player's data from global
|
||||
script_data.players[player_index] = nil
|
||||
|
||||
local in_process = script_data.in_process[player_data.language]
|
||||
if not in_process then
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove this player from the players table
|
||||
local i = table.find(in_process.players, player_index)
|
||||
if i then
|
||||
table.remove(in_process.players, i)
|
||||
end
|
||||
|
||||
if player_data.status ~= "translating" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Find the next player in the list with valid data
|
||||
local next_player_data
|
||||
for _, player_index in pairs(in_process.players) do
|
||||
local player_data = script_data.players[player_index]
|
||||
if player_data then
|
||||
next_player_data = player_data
|
||||
break
|
||||
end
|
||||
end
|
||||
-- If there are no more valid players
|
||||
if not next_player_data then
|
||||
-- Completely cancel the translation
|
||||
script_data.in_process[player_data.language] = nil
|
||||
clean_gui(player_data.language)
|
||||
return
|
||||
end
|
||||
--Update player info
|
||||
next_player_data.status = "translating"
|
||||
next_player_data.dictionary = player_data.dictionary
|
||||
next_player_data.i = player_data.i
|
||||
-- Resume translating with the new player
|
||||
request_translation(next_player_data)
|
||||
end
|
||||
|
||||
--- @deprecated Use 'dictionary-lite' instead.
|
||||
function flib_dictionary.set_use_local_storage(value)
|
||||
use_local_storage = value
|
||||
end
|
||||
|
||||
return flib_dictionary
|
||||
115
.vscode/flib/direction.lua
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
local flib_math = require("__flib__/math")
|
||||
|
||||
--- Functions for working with directions.
|
||||
--- @class flib_direction
|
||||
local flib_direction = {}
|
||||
|
||||
--- defines.direction.north
|
||||
flib_direction.north = defines.direction.north
|
||||
--- defines.direction.east
|
||||
flib_direction.east = defines.direction.east
|
||||
--- defines.direction.west
|
||||
flib_direction.west = defines.direction.west
|
||||
--- defines.direction.south
|
||||
flib_direction.south = defines.direction.south
|
||||
--- defines.direction.northeast
|
||||
flib_direction.northeast = defines.direction.northeast
|
||||
--- defines.direction.northwest
|
||||
flib_direction.northwest = defines.direction.northwest
|
||||
--- defines.direction.southeast
|
||||
flib_direction.southeast = defines.direction.southeast
|
||||
--- defines.direction.southwest
|
||||
flib_direction.southwest = defines.direction.southwest
|
||||
|
||||
--- Calculate the opposite direction.
|
||||
--- @param direction defines.direction
|
||||
--- @return defines.direction
|
||||
function flib_direction.opposite(direction)
|
||||
return (direction + 4) % 8 --[[@as defines.direction]]
|
||||
end
|
||||
|
||||
--- Calculate the next four-way or eight-way direction.
|
||||
--- @param direction defines.direction
|
||||
--- @param eight_way? boolean
|
||||
--- @return defines.direction
|
||||
function flib_direction.next(direction, eight_way)
|
||||
return (direction + (eight_way and 1 or 2)) % 8 --[[@as defines.direction]]
|
||||
end
|
||||
|
||||
--- Calculate the previous four-way or eight-way direction.
|
||||
--- @param direction defines.direction
|
||||
--- @param eight_way? boolean
|
||||
--- @return defines.direction
|
||||
function flib_direction.previous(direction, eight_way)
|
||||
return (direction + (eight_way and -1 or -2)) % 8 --[[@as defines.direction]]
|
||||
end
|
||||
|
||||
--- Calculate an orientation from a direction.
|
||||
--- @param direction defines.direction
|
||||
--- @return RealOrientation
|
||||
function flib_direction.to_orientation(direction)
|
||||
return direction / 8 --[[@as RealOrientation]]
|
||||
end
|
||||
|
||||
--- Calculate a vector from a direction.
|
||||
--- @param direction defines.direction
|
||||
--- @param distance? number default: `1`
|
||||
--- @return MapPosition
|
||||
function flib_direction.to_vector(direction, distance)
|
||||
distance = distance or 1
|
||||
local x, y = 0, 0
|
||||
if direction == flib_direction.north then
|
||||
y = y - distance
|
||||
elseif direction == flib_direction.northeast then
|
||||
x, y = x + distance, y - distance
|
||||
elseif direction == flib_direction.east then
|
||||
x = x + distance
|
||||
elseif direction == flib_direction.southeast then
|
||||
x, y = x + distance, y + distance
|
||||
elseif direction == flib_direction.south then
|
||||
y = y + distance
|
||||
elseif direction == flib_direction.southwest then
|
||||
x, y = x - distance, y + distance
|
||||
elseif direction == flib_direction.west then
|
||||
x = x - distance
|
||||
elseif direction == flib_direction.northwest then
|
||||
x, y = x - distance, y - distance
|
||||
end
|
||||
return { x = x, y = y }
|
||||
end
|
||||
|
||||
--- Calculate a two-dimensional vector from a cardinal direction.
|
||||
--- @param direction defines.direction
|
||||
--- @param longitudinal number Distance to move in the specified direction.
|
||||
--- @param orthogonal number Distance to move perpendicular to the specified direction. A negative distance will move "left" and a positive distance will move "right" from the perspective of the direction.
|
||||
--- @return MapPosition?
|
||||
function flib_direction.to_vector_2d(direction, longitudinal, orthogonal)
|
||||
if direction == defines.direction.north then
|
||||
return { x = orthogonal, y = -longitudinal }
|
||||
elseif direction == defines.direction.south then
|
||||
return { x = -orthogonal, y = longitudinal }
|
||||
elseif direction == defines.direction.east then
|
||||
return { x = longitudinal, y = orthogonal }
|
||||
elseif direction == defines.direction.west then
|
||||
return { x = -longitudinal, y = -orthogonal }
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate the direction of travel from the source to the target.
|
||||
--- @param source MapPosition
|
||||
--- @param target MapPosition
|
||||
--- @param round? boolean If true, round to the nearest `defines.direction`.
|
||||
--- @return defines.direction
|
||||
function flib_direction.from_positions(source, target, round)
|
||||
local deg = math.deg(math.atan2(target.y - source.y, target.x - source.x))
|
||||
local direction = (deg + 90) / 45
|
||||
if direction < 0 then
|
||||
direction = direction + 8
|
||||
end
|
||||
if round then
|
||||
direction = flib_math.round(direction)
|
||||
end
|
||||
return direction --[[@as defines.direction]]
|
||||
end
|
||||
|
||||
return flib_direction
|
||||
BIN
.vscode/flib/docs/assets/indicator-examples.png
vendored
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
.vscode/flib/docs/assets/slot-style-examples.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
115
.vscode/flib/docs/gui-styles.md
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
flib includes several GUI styles for your use and convenience. For help and information on how to use these styles effectively, refer to the [work-in-progress GUI style guide](https://github.com/raiguard/Factorio-SmallMods/wiki/GUI-Style-Guide).
|
||||
|
||||
**IMPORTANT:** Modifying these styles in any way will modify them for all mods using them. Therefore, unless you are specifically creating a GUI skin mod, **DO NOT MODIFY THESE STYLES!** Instead, create your own new styles using these styles as parents, then modify those new styles as you wish.
|
||||
|
||||
### Button styles
|
||||
|
||||
**flib_selected_frame_action_button**
|
||||
|
||||
A "selected" frame action button. Use when a frame action button can "toggle" on and off.
|
||||
|
||||
**flib_selected_tool_button**
|
||||
|
||||
A "selected" tool button. Use when a tool button can "toggle" on and off.
|
||||
|
||||
**flib_tool_button_light_green**
|
||||
|
||||
A light green tool button. Similar to the `item_and_count_select_confirm` style, but has margin and padding fixes to match other tool buttons.
|
||||
|
||||
**flib_tool_button_dark_red**
|
||||
|
||||
A dark red tool button, similar to the red shortcut button style.
|
||||
|
||||
#### Slot styles
|
||||
|
||||
flib includes a myriad of colored slot styles for use with `sprite-button`s:
|
||||
|
||||

|
||||
|
||||
There are three categories of style, from top to bottom: `slot`, `slot_button`, and `standalone_slot_button`. From left to right, the colors are `default`, `grey`, `red`, `orange`, `yellow`, `green`, `cyan`, `blue`, `purple`, and `pink`.
|
||||
|
||||
The styles are formatted as `flib_CATEGORY_COLOR`. For example, if I want a pink standalone slot button (bottom-right on the preview image), I would use `flib_standalone_slot_button_pink`.
|
||||
|
||||
Each slot style also has a `selected` variant, which uses the hovered graphics as default. This is intended to let a user "select" a button, and to let the mod visually distinguish it from other buttons around it. To use these styles, replace `flib_` with `flib_selected_` in the style you wish to use (e.g. `flib_selected_slot_button_green`).
|
||||
|
||||
### Empty widget styles
|
||||
|
||||
**flib_dialog_footer_drag_handle**
|
||||
|
||||
A drag handle suitable for placement in the footer of a **dialog** window.
|
||||
|
||||
**flib_dialog_footer_drag_handle_no_right**
|
||||
|
||||
A dialog footer drag handle with the right margin removed. Suitable for dialog windows without a `confirm` button.
|
||||
|
||||
**flib_dialog_titlebar_drag_handle**
|
||||
|
||||
A drag handle suitable for placement in the titlebar of a **dialog** window. Use inside of a `flib_titlebar_flow` flow.
|
||||
|
||||
**flib_horizontal_pusher**
|
||||
|
||||
An invisible element that has `horizontally_stretchable` set, thereby "pushing" everything to the right.
|
||||
|
||||
**flib_titlebar_drag_handle**
|
||||
|
||||
A drag handle suitable for placement in the titlebar of a **standard** window (a window with a close button, or any other frame action buttons in the titlebar). Use inside of a `flib_titlebar_flow` flow.
|
||||
|
||||
**flib_vertical_pusher**
|
||||
|
||||
An invisible element that has `vertically_stretchable` set, thereby "pushing" everything to the bottom.
|
||||
|
||||
### Flow styles
|
||||
|
||||
**flib_indicator_flow**
|
||||
|
||||
A flow designed for use with indicators (see below).
|
||||
|
||||
**flib_titlebar_flow**
|
||||
|
||||
A flow for use in a custom window titlebar. Identical to a regular horizontal flow, except for an increased horizontal spacing.
|
||||
|
||||
### Frame styles
|
||||
|
||||
**flib_shallow_frame_in_shallow_frame**
|
||||
|
||||
A shallow frame nested in another shallow frame. Use of this is generally recommended against, but can be useful in some specific situations.
|
||||
|
||||
### Image styles
|
||||
|
||||
**flib_indicator**
|
||||
|
||||
A 16x16 image style. Designed for use with flib's indicator sprites (see `sprites.md`).
|
||||
|
||||
### Scroll pane styles
|
||||
|
||||
**flib_naked_scroll_pane**
|
||||
|
||||
A marginless scroll pane for use inside of content panes. When activated, it draws a shadow around its edges to give a more "inset" effect, to make it more obviously scrollable. The content is given an automatic 12px padding.
|
||||
|
||||
**flib_naked_scroll_pane_under_tabs**
|
||||
|
||||
Identical to `flib_naked_scroll_pane`, but has an inset on the top side when activated. Designed for use inside of a `tabbed_pane_with_no_side_padding` when not using a toolbar.
|
||||
|
||||
**flib_naked_scroll_pane_no_padding**
|
||||
|
||||
Identical to `flib_naked_scroll_pane`, but has no padding for the content that's put inside. Useful for wrapping a table in a scroll pane, for example.
|
||||
|
||||
**flib_shallow_scroll_pane**
|
||||
|
||||
A scroll pane that is inset from a shallow frame, instead of an outer frame.
|
||||
|
||||
### Tabbed pane styles
|
||||
|
||||
**flib_tabbed_pane_with_no_padding**
|
||||
|
||||
A tabbed pane with no padding whatsoever on the content container. Useful for specific situations where you need to have full control of the content padding.
|
||||
|
||||
### Textfield styles
|
||||
|
||||
**flib_widthless_textfield**
|
||||
|
||||
A textfield with no width defined on it. The default textfield style has a width of 200, which can wreak havoc.
|
||||
|
||||
**flib_widthless_invalid_textfield**
|
||||
|
||||
A widthless textfield that has a red background. Suitable for situations where the content of the textfield is invalid in some way.
|
||||
9
.vscode/flib/docs/sprites.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
The following are sprites provided by flib for your use:
|
||||
|
||||
## Indicator sprites
|
||||
|
||||

|
||||
|
||||
As seen above, indicator sprites are used to display the "status" of something. They are 16x16 in size, and are intended to be accompanied by a status label. The sprite names follow the `flib_indicator_COLOR` pattern, replacing `COLOR` with the corresponding color as shown in the preview above (e.g. `flib_indicator_blue`).
|
||||
|
||||
To align the indicator with an adjacent label, create both the indicator and label as children of a flow with the `flib_indicator_flow` style (see `gui-styles.md`).
|
||||
90
.vscode/flib/event.lua
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
--- @diagnostic disable
|
||||
--- @deprecated use `script` directly
|
||||
local flib_event = {}
|
||||
|
||||
for name, id in pairs(defines.events) do
|
||||
flib_event[name] = function(handler, filters)
|
||||
return script.on_event(id, handler, filters)
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.on_init(handler) --
|
||||
script.on_init(handler)
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.on_load(handler) --
|
||||
script.on_load(handler)
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.on_configuration_changed(handler) --
|
||||
script.on_configuration_changed(handler)
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.on_nth_tick(nth_tick, handler) --
|
||||
if handler then
|
||||
script.on_nth_tick(nth_tick, handler)
|
||||
else
|
||||
script.on_nth_tick(nth_tick)
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.register(ids, handler, filters) --
|
||||
if type(ids) ~= "table" then
|
||||
ids = { ids }
|
||||
end
|
||||
for i = 1, #ids do
|
||||
-- the game doesn't like you passing filters to events that don't support them, even if they're `nil`
|
||||
if filters then
|
||||
script.on_event(ids[i], handler, filters)
|
||||
else
|
||||
script.on_event(ids[i], handler)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.register_on_entity_destroyed(entity) --
|
||||
return script.register_on_entity_destroyed(entity)
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.generate_id() --
|
||||
return script.generate_event_name()
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.get_handler(id) --
|
||||
return script.get_event_handler(id)
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.raise(id, event_data) --
|
||||
script.raise_event(id, event_data)
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.get_order() --
|
||||
return script.get_event_order()
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.set_filters(ids, filters) --
|
||||
if type(ids) ~= "table" then
|
||||
ids = { ids }
|
||||
end
|
||||
for i = 1, #ids do
|
||||
script.set_event_filter(ids[i], filters)
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `script` directly
|
||||
function flib_event.get_filters(id) --
|
||||
script.get_event_filter(id)
|
||||
end
|
||||
|
||||
return flib_event
|
||||
79
.vscode/flib/format.lua
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
--- Various string formatting functions.
|
||||
--- @class flib_format
|
||||
local flib_format = {}
|
||||
|
||||
local suffix_list = {
|
||||
{ "Y", 1e24 }, -- yotta
|
||||
{ "Z", 1e21 }, -- zetta
|
||||
{ "E", 1e18 }, -- exa
|
||||
{ "P", 1e15 }, -- peta
|
||||
{ "T", 1e12 }, -- tera
|
||||
{ "G", 1e9 }, -- giga
|
||||
{ "M", 1e6 }, -- mega
|
||||
{ "k", 1e3 }, -- kilo
|
||||
}
|
||||
|
||||
--- Format a number for display, adding commas and an optional SI suffix.
|
||||
--- Specify `fixed_precision` to display the number with the given width,
|
||||
--- adjusting precision as necessary.
|
||||
--- @param amount number
|
||||
--- @param append_suffix boolean?
|
||||
--- @param fixed_precision number?
|
||||
--- @return string
|
||||
function flib_format.number(amount, append_suffix, fixed_precision)
|
||||
local suffix = ""
|
||||
if append_suffix then
|
||||
for _, data in ipairs(suffix_list) do
|
||||
if math.abs(amount) >= data[2] then
|
||||
amount = amount / data[2]
|
||||
suffix = " " .. data[1]
|
||||
break
|
||||
end
|
||||
end
|
||||
if not fixed_precision then
|
||||
amount = math.floor(amount * 10) / 10
|
||||
end
|
||||
end
|
||||
local formatted, k = tostring(amount), nil
|
||||
if fixed_precision then
|
||||
-- Show the number with fixed width precision
|
||||
local len_before = #tostring(math.floor(amount))
|
||||
local len_after = math.max(0, fixed_precision - len_before - 1)
|
||||
formatted = string.format("%." .. len_after .. "f", amount)
|
||||
end
|
||||
-- Add commas to result
|
||||
while true do
|
||||
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", "%1,%2")
|
||||
if k == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return formatted .. suffix
|
||||
end
|
||||
|
||||
--- Convert the given tick or game.tick into "[hh:]mm:ss" format.
|
||||
--- @param tick uint?
|
||||
--- @param include_leading_zeroes boolean?
|
||||
--- @return string
|
||||
function flib_format.time(tick, include_leading_zeroes)
|
||||
local total_seconds = math.floor((tick or game.ticks_played) / 60)
|
||||
local seconds = total_seconds % 60
|
||||
local minutes = math.floor(total_seconds / 60)
|
||||
if minutes > 59 then
|
||||
minutes = minutes % 60
|
||||
local hours = math.floor(total_seconds / 3600)
|
||||
if include_leading_zeroes then
|
||||
return string.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
else
|
||||
return string.format("%d:%02d:%02d", hours, minutes, seconds)
|
||||
end
|
||||
else
|
||||
if include_leading_zeroes then
|
||||
return string.format("%02d:%02d", minutes, seconds)
|
||||
else
|
||||
return string.format("%d:%02d", minutes, seconds)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return flib_format
|
||||
BIN
.vscode/flib/graphics/black.png
vendored
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
.vscode/flib/graphics/dark-red-button.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
.vscode/flib/graphics/empty.png
vendored
Normal file
|
After Width: | Height: | Size: 131 B |
BIN
.vscode/flib/graphics/frame-action-icons.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
.vscode/flib/graphics/indicators.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
.vscode/flib/graphics/planner.png
vendored
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
.vscode/flib/graphics/slots.png
vendored
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
.vscode/flib/graphics/slots.xcf
vendored
Normal file
BIN
.vscode/flib/graphics/subheader-line.png
vendored
Normal file
|
After Width: | Height: | Size: 90 B |
2
.vscode/flib/gui-beta.lua
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
--- @deprecated use `gui` or `gui-lite` instead.`
|
||||
return require("__flib__/gui")
|
||||
201
.vscode/flib/gui-lite.lua
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
--- Utilities for building GUIs and handling GUI events.
|
||||
--- @class flib_gui
|
||||
local flib_gui = {}
|
||||
|
||||
local handler_tag_key = "__" .. script.mod_name .. "_handler"
|
||||
|
||||
--- @type table<GuiElemHandler, string>
|
||||
local handlers = {}
|
||||
--- @type table<string, GuiElemHandler>
|
||||
local handlers_lookup = {}
|
||||
|
||||
--- Add a new child or children to the given GUI element.
|
||||
--- @param parent LuaGuiElement
|
||||
--- @param def GuiElemDef Can be a single element, or an array of elements.
|
||||
--- @param elems table<string, LuaGuiElement>? Optional initial `elems` table.
|
||||
--- @return table<string, LuaGuiElement> elems Elements with names will be collected into this table.
|
||||
--- @return LuaGuiElement first The element that was created first; the "top level" element.
|
||||
function flib_gui.add(parent, def, elems)
|
||||
if not parent or not parent.valid then
|
||||
error("Parent element is missing or invalid")
|
||||
end
|
||||
if not elems then
|
||||
elems = {}
|
||||
end
|
||||
-- If a single def was passed, wrap it in an array
|
||||
if def.type or (def.tab and def.content) then
|
||||
def = { def }
|
||||
end
|
||||
local first
|
||||
for i = 1, #def do
|
||||
local def = def[i]
|
||||
if def.type then
|
||||
-- Remove custom attributes from the def so the game doesn't serialize them
|
||||
local children = def.children
|
||||
local elem_mods = def.elem_mods
|
||||
local handler = def.handler
|
||||
local style_mods = def.style_mods
|
||||
local drag_target = def.drag_target
|
||||
-- If children were defined in the array portion, remove and collect them
|
||||
local has_array_children = false
|
||||
if def[1] then
|
||||
if children then
|
||||
error("Cannot define children in array portion and subtable simultaneously")
|
||||
end
|
||||
has_array_children = true
|
||||
children = {}
|
||||
for i = 1, #def do
|
||||
children[i] = def[i]
|
||||
def[i] = nil
|
||||
end
|
||||
end
|
||||
def.children = nil
|
||||
def.elem_mods = nil
|
||||
def.handler = nil
|
||||
def.style_mods = nil
|
||||
def.drag_target = nil
|
||||
|
||||
local elem = parent.add(def)
|
||||
|
||||
if not first then
|
||||
first = elem
|
||||
end
|
||||
if def.name then
|
||||
elems[def.name] = elem
|
||||
end
|
||||
if style_mods then
|
||||
for key, value in pairs(style_mods) do
|
||||
elem.style[key] = value
|
||||
end
|
||||
end
|
||||
if elem_mods then
|
||||
for key, value in pairs(elem_mods) do
|
||||
elem[key] = value
|
||||
end
|
||||
end
|
||||
if drag_target then
|
||||
local target = elems[drag_target]
|
||||
if not target then
|
||||
error("Drag target '" .. drag_target .. "' not found.")
|
||||
end
|
||||
elem.drag_target = target
|
||||
end
|
||||
if handler then
|
||||
local out
|
||||
if type(handler) == "table" then
|
||||
out = {}
|
||||
for name, handler in pairs(handler) do
|
||||
out[tostring(name)] = handlers[handler]
|
||||
end
|
||||
else
|
||||
out = handlers[handler]
|
||||
end
|
||||
local tags = elem.tags
|
||||
tags[handler_tag_key] = out
|
||||
elem.tags = tags
|
||||
end
|
||||
if children then
|
||||
flib_gui.add(elem, children, elems)
|
||||
end
|
||||
|
||||
-- Re-add custom attributes
|
||||
if children and has_array_children then
|
||||
for i = 1, #children do
|
||||
def[i] = children[i]
|
||||
end
|
||||
else
|
||||
def.children = children
|
||||
end
|
||||
def.elem_mods = elem_mods
|
||||
def.handler = handler
|
||||
def.style_mods = style_mods
|
||||
elseif def.tab and def.content then
|
||||
local _, tab = flib_gui.add(parent, def.tab, elems)
|
||||
local _, content = flib_gui.add(parent, def.content, elems)
|
||||
parent.add_tab(tab, content)
|
||||
end
|
||||
end
|
||||
return elems, first
|
||||
end
|
||||
|
||||
--- Add the given handler functions to the registry for use with `flib_gui.add`. Each handler must have a unique name. If a
|
||||
--- `wrapper` function is provided, it will be called instead, and will receive the event data and handler. The wrapper
|
||||
--- can be used to execute logic or gather data common to all handler functions for this GUI.
|
||||
--- @param new_handlers table<string, fun(e: GuiEventData)>
|
||||
--- @param wrapper fun(e: GuiEventData, handler: function)?
|
||||
function flib_gui.add_handlers(new_handlers, wrapper)
|
||||
for name, handler in pairs(new_handlers) do
|
||||
if type(handler) == "function" then
|
||||
if handlers_lookup[name] then
|
||||
error("Attempted to register two GUI event handlers with the same name: " .. name)
|
||||
end
|
||||
handlers[handler] = name
|
||||
if wrapper then
|
||||
handlers_lookup[name] = function(e)
|
||||
wrapper(e, handler)
|
||||
end
|
||||
else
|
||||
handlers_lookup[name] = handler
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Dispatch the handler associated with this event and GUI element. The handler must have been added using
|
||||
--- `flib_gui.add_handlers`.
|
||||
--- @param e GuiEventData
|
||||
--- @return boolean handled True if an event handler was called.
|
||||
function flib_gui.dispatch(e)
|
||||
local elem = e.element
|
||||
if not elem then
|
||||
return false
|
||||
end
|
||||
local tags = elem.tags --[[@as Tags]]
|
||||
local handler_def = tags[handler_tag_key]
|
||||
if not handler_def then
|
||||
return false
|
||||
end
|
||||
local handler_type = type(handler_def)
|
||||
if handler_type == "table" then
|
||||
handler_def = handler_def[tostring(e.name)]
|
||||
end
|
||||
if handler_def then
|
||||
local handler = handlers_lookup[handler_def]
|
||||
if handler then
|
||||
handler(e)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Handle all GUI events with `flib_gui.dispatch`. Will not override any existing handlers.
|
||||
function flib_gui.handle_events()
|
||||
for name, id in pairs(defines.events) do
|
||||
if string.find(name, "on_gui_") and not script.get_event_handler(id) then
|
||||
script.on_event(id, flib_gui.dispatch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- A GUI element definition. This extends `LuaGuiElement.add_param` with several new attributes.
|
||||
--- Children may be defined in the array portion as an alternative to the `children` subtable.
|
||||
--- @class GuiElemDefClass: LuaGuiElement.add_param
|
||||
--- @field style_mods LuaStyle? Modifications to make to the element's style
|
||||
--- @field elem_mods LuaGuiElement? Modifications to make to the element itself
|
||||
--- @field drag_target string? Set the element's drag target to the element whose name matches this string. The drag target must be present in the `elems` table.
|
||||
--- @field handler GuiElemHandler? Handler(s) to assign to this element
|
||||
--- @field children GuiElemDef[]? Children to add to this element
|
||||
--- @field tab GuiElemDef? To add a tab, specify `tab` and `content` and leave all other fields unset.
|
||||
--- @field content GuiElemDef? To add a tab, specify `tab` and `content` and leave all other fields unset.
|
||||
|
||||
--- @alias GuiElemDef GuiElemDefClass|GuiElemDef[]
|
||||
|
||||
--- A handler function to invoke when receiving GUI events for this element. Alternatively, separate handlers may be
|
||||
--- specified for different events.
|
||||
--- @alias GuiElemHandler fun(e: GuiEventData)|table<defines.events, fun(e: GuiEventData)>
|
||||
|
||||
--- Aggregate type of all possible GUI events.
|
||||
--- @alias GuiEventData EventData.on_gui_checked_state_changed|EventData.on_gui_click|EventData.on_gui_closed|EventData.on_gui_confirmed|EventData.on_gui_elem_changed|EventData.on_gui_location_changed|EventData.on_gui_opened|EventData.on_gui_selected_tab_changed|EventData.on_gui_selection_state_changed|EventData.on_gui_switch_state_changed|EventData.on_gui_text_changed|EventData.on_gui_value_changed
|
||||
|
||||
return flib_gui
|
||||
328
.vscode/flib/gui.lua
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
--- @diagnostic disable
|
||||
|
||||
local mod_name = script.mod_name
|
||||
local gui_event_defines = {}
|
||||
|
||||
local event_id_to_string_mapping = {}
|
||||
for name, id in pairs(defines.events) do
|
||||
if string.find(name, "^on_gui") then
|
||||
gui_event_defines[name] = id
|
||||
event_id_to_string_mapping[id] = string.gsub(name, "^on_gui", "on")
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
local flib_gui = {}
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.hook_events(callback)
|
||||
local on_event = script.on_event
|
||||
for _, id in pairs(gui_event_defines) do
|
||||
on_event(id, callback)
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.read_action(event_data)
|
||||
local elem = event_data.element
|
||||
if not elem or not elem.valid then
|
||||
return
|
||||
end
|
||||
|
||||
local mod_tags = elem.tags[mod_name]
|
||||
if not mod_tags then
|
||||
return
|
||||
end
|
||||
|
||||
local elem_actions = mod_tags.flib
|
||||
if not elem_actions then
|
||||
return
|
||||
end
|
||||
|
||||
local event_name = event_id_to_string_mapping[event_data.name]
|
||||
local msg = elem_actions[event_name]
|
||||
|
||||
return msg
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
local function recursive_build(parent, structure, refs)
|
||||
-- If the structure has no type, just ignore it
|
||||
-- This is to make it possible to pass unit types `{}` to represent "no element" without breaking things
|
||||
if not structure.type then
|
||||
return
|
||||
end
|
||||
|
||||
-- Prepare tags
|
||||
local original_tags = structure.tags
|
||||
local tags = original_tags or {}
|
||||
local actions = structure.actions
|
||||
local tags_flib = tags.flib
|
||||
tags.flib = actions
|
||||
structure.tags = { [mod_name] = tags }
|
||||
|
||||
-- Make the game not convert these into a property tree for no reason
|
||||
structure.actions = nil
|
||||
-- Substructures can be defined in special tables or as the array portion of this structure
|
||||
local substructures
|
||||
local substructures_len = #structure
|
||||
if substructures_len > 0 then
|
||||
if structure.children or structure.tabs then
|
||||
error("Children or tab-and-content pairs must ALL be in the array portion, or a subtable. Not both at once!")
|
||||
end
|
||||
substructures = {}
|
||||
for i = 1, substructures_len do
|
||||
substructures[i] = structure[i]
|
||||
structure[i] = nil
|
||||
end
|
||||
else
|
||||
substructures = structure.children or structure.tabs
|
||||
structure.children = nil
|
||||
structure.tabs = nil
|
||||
end
|
||||
|
||||
-- Create element
|
||||
local elem = parent.add(structure)
|
||||
|
||||
-- Restore structure
|
||||
structure.tags = original_tags
|
||||
structure.actions = actions
|
||||
tags.flib = tags_flib
|
||||
|
||||
local style_mods = structure.style_mods
|
||||
if style_mods then
|
||||
for k, v in pairs(style_mods) do
|
||||
elem.style[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
local elem_mods = structure.elem_mods
|
||||
if elem_mods then
|
||||
for k, v in pairs(elem_mods) do
|
||||
elem[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
local ref = structure.ref
|
||||
if ref then
|
||||
-- Recursively create tables as needed
|
||||
local prev = refs
|
||||
local ref_length = #ref
|
||||
for i = 1, ref_length - 1 do
|
||||
local current_key = ref[i]
|
||||
local current = prev[current_key]
|
||||
if not current then
|
||||
current = {}
|
||||
prev[current_key] = current
|
||||
end
|
||||
prev = current
|
||||
end
|
||||
prev[ref[ref_length]] = elem
|
||||
end
|
||||
|
||||
-- Substructures
|
||||
if substructures then
|
||||
if structure.type == "tabbed-pane" then
|
||||
local add_tab = elem.add_tab
|
||||
for i = 1, #substructures do
|
||||
local tab_and_content = substructures[i]
|
||||
if not (tab_and_content.tab and tab_and_content.content) then
|
||||
error("TabAndContent must have `tab` and `content` fields")
|
||||
end
|
||||
local tab = recursive_build(elem, tab_and_content.tab, refs)
|
||||
local content = recursive_build(elem, tab_and_content.content, refs)
|
||||
add_tab(tab, content)
|
||||
end
|
||||
else
|
||||
for i = 1, #substructures do
|
||||
recursive_build(elem, substructures[i], refs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return elem
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.build(parent, structures)
|
||||
local refs = {}
|
||||
for i = 1, #structures do
|
||||
recursive_build(parent, structures[i], refs)
|
||||
end
|
||||
return refs
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.add(parent, structure)
|
||||
-- Just in case they had a ref in the structure already, extract it
|
||||
local previous_ref = structure.ref
|
||||
-- Put in a known ref that we can use later
|
||||
structure.ref = { "FLIB_ADD_ROOT" }
|
||||
-- Build the element
|
||||
local refs = {}
|
||||
recursive_build(parent, structure, refs)
|
||||
-- Restore the previous ref
|
||||
structure.ref = previous_ref
|
||||
-- Return the element
|
||||
return refs.FLIB_ADD_ROOT
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
local function recursive_update(elem, updates)
|
||||
if updates.cb then
|
||||
updates.cb(elem)
|
||||
end
|
||||
|
||||
if updates.style then
|
||||
elem.style = updates.style
|
||||
end
|
||||
|
||||
if updates.style_mods then
|
||||
for key, value in pairs(updates.style_mods) do
|
||||
elem.style[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
if updates.elem_mods then
|
||||
for key, value in pairs(updates.elem_mods) do
|
||||
elem[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
if updates.tags then
|
||||
flib_gui.update_tags(elem, updates.tags)
|
||||
end
|
||||
|
||||
-- TODO: This could be a lot better
|
||||
if updates.actions then
|
||||
for event_name, payload in pairs(updates.actions) do
|
||||
flib_gui.set_action(elem, event_name, payload)
|
||||
end
|
||||
end
|
||||
|
||||
local substructures
|
||||
local substructures_len = #updates
|
||||
if substructures_len > 0 then
|
||||
if updates.children or updates.tabs then
|
||||
error("Children or tab-and-content pairs must ALL be in the array portion, or a subtable. Not both at once!")
|
||||
end
|
||||
substructures = {}
|
||||
for i = 1, substructures_len do
|
||||
substructures[i] = updates[i]
|
||||
updates[i] = nil
|
||||
end
|
||||
else
|
||||
substructures = updates.children or updates.tabs
|
||||
updates.children = nil
|
||||
updates.tabs = nil
|
||||
end
|
||||
local subelements
|
||||
if elem.type == "tabbed-pane" then
|
||||
subelements = elem.tabs
|
||||
else
|
||||
subelements = elem.children
|
||||
end
|
||||
|
||||
if substructures then
|
||||
for i, substructure in pairs(substructures) do
|
||||
if substructure.tab or substructure.content then
|
||||
local elem_tab_and_content = subelements[i]
|
||||
if elem_tab_and_content then
|
||||
local tab = elem_tab_and_content.tab
|
||||
local tab_updates = substructures.tab
|
||||
if tab and tab_updates then
|
||||
recursive_update(tab, tab_updates)
|
||||
end
|
||||
local content = elem_tab_and_content.content
|
||||
local content_updates = substructures.content
|
||||
if content and content_updates then
|
||||
recursive_update(content, content_updates)
|
||||
end
|
||||
end
|
||||
elseif subelements[i] then
|
||||
recursive_update(subelements[i], substructure)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.update(elem, updates)
|
||||
recursive_update(elem, updates)
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.get_tags(elem)
|
||||
return elem.tags[mod_name] or {}
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.set_tags(elem, tags)
|
||||
local elem_tags = elem.tags
|
||||
elem_tags[mod_name] = tags
|
||||
elem.tags = elem_tags
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.delete_tags(elem)
|
||||
local elem_tags = elem.tags
|
||||
elem_tags[mod_name] = nil
|
||||
elem.tags = elem_tags
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.update_tags(elem, updates)
|
||||
local elem_tags = elem.tags
|
||||
local existing = elem_tags[mod_name]
|
||||
|
||||
if not existing then
|
||||
existing = {}
|
||||
elem_tags[mod_name] = existing
|
||||
end
|
||||
|
||||
for k, v in pairs(updates) do
|
||||
existing[k] = v
|
||||
end
|
||||
|
||||
elem.tags = elem_tags
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.set_action(elem, event_name, msg)
|
||||
local elem_tags = elem.tags
|
||||
local existing = elem_tags[mod_name]
|
||||
|
||||
if not existing then
|
||||
existing = {}
|
||||
elem_tags[mod_name] = existing
|
||||
end
|
||||
|
||||
local actions = existing.flib
|
||||
if not actions then
|
||||
actions = {}
|
||||
existing.flib = actions
|
||||
end
|
||||
|
||||
actions[event_name] = msg or nil
|
||||
|
||||
elem.tags = elem_tags
|
||||
end
|
||||
|
||||
--- @deprecated use `gui-lite` instead
|
||||
function flib_gui.get_action(elem, event_name)
|
||||
local elem_tags = elem.tags
|
||||
local existing = elem_tags[mod_name]
|
||||
|
||||
if not existing then
|
||||
return
|
||||
end
|
||||
|
||||
local actions = existing.flib
|
||||
if not actions then
|
||||
return
|
||||
end
|
||||
|
||||
return actions[event_name]
|
||||
end
|
||||
|
||||
return flib_gui
|
||||
24
.vscode/flib/info.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "flib",
|
||||
"version": "0.12.4",
|
||||
"title": "Factorio Library",
|
||||
"author": "raiguard, Optera, justarandomgeek, Nexela",
|
||||
"contact": "https://lists.sr.ht/~raiguard/factorio-mods-devel",
|
||||
"homepage": "https://git.sr.ht/~raiguard/flib",
|
||||
"description": "A set of high-quality, commonly-used utilities for creating Factorio mods.",
|
||||
"factorio_version": "1.1",
|
||||
"dependencies": [ "base >= 1.1.74" ],
|
||||
"package": {
|
||||
"ignore": [
|
||||
"*.code-workspace",
|
||||
"crowdin.yml",
|
||||
".gitattributes",
|
||||
".gitignore",
|
||||
".kakrc",
|
||||
".mailmap",
|
||||
"stylua.toml",
|
||||
"tests",
|
||||
".vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
.vscode/flib/locale/af/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=af
|
||||
locale-name=Afrikaans
|
||||
|
||||
3
.vscode/flib/locale/ar/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=ar
|
||||
locale-name=العَرَبِيَّة
|
||||
|
||||
2
.vscode/flib/locale/be/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=be
|
||||
locale-name=Беларуская
|
||||
2
.vscode/flib/locale/bg/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=bg
|
||||
locale-name=български език
|
||||
3
.vscode/flib/locale/ca/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=ca
|
||||
locale-name=Català
|
||||
|
||||
3
.vscode/flib/locale/cs/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=cs
|
||||
locale-name=Čeština
|
||||
|
||||
3
.vscode/flib/locale/da/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=da
|
||||
locale-name=Dansk
|
||||
|
||||
3
.vscode/flib/locale/de/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=de
|
||||
locale-name=Deutsch
|
||||
|
||||
10
.vscode/flib/locale/de/flib.cfg
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[gui]
|
||||
flib-keep-open=Offen bleiben
|
||||
flib-settings=Einstellungen
|
||||
flib-finishing=Abschließen...
|
||||
flib-translating-dictionaries=Übersetze Wörterbücher [img=info]
|
||||
flib-translating-dictionaries-description=Mods, die das Wörterbuchsystem der Factorio Library verwenden, fordern Übersetzungen im Laufe der Zeit an, um Suchen in Ihrer Muttersprache zu ermöglichen. Dieses Fenster zeigt den Fortschritt der Übersetzungen jeder Mod für jede gewünschte Sprache an. Dieses Fenster schließt sich automatisch sobald alle Übersetzungen abgeschlossen sind.
|
||||
|
||||
[mod-name]
|
||||
flib=Factorio Library
|
||||
|
||||
3
.vscode/flib/locale/el/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=el
|
||||
locale-name=Ελληνικά
|
||||
|
||||
2
.vscode/flib/locale/en/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=en
|
||||
locale-name=English
|
||||
11
.vscode/flib/locale/en/flib.cfg
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
[gui]
|
||||
# For use with the frame action button sprites
|
||||
flib-keep-open=Keep open
|
||||
flib-settings=Settings
|
||||
# Translation progress GUI
|
||||
flib-finishing=Finishing...
|
||||
flib-translating-dictionaries=Translating dictionaries [img=info]
|
||||
flib-translating-dictionaries-description=Mods that use the Factorio Library's dictionary system request translations over time in order to allow text search in your native language. This GUI shows the progress of each mod's translations for each required language. This GUI will automatically remove itself once all translations have finished.
|
||||
|
||||
[mod-name]
|
||||
flib=Factorio Library
|
||||
2
.vscode/flib/locale/eo/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=eo
|
||||
locale-name=Esperanto
|
||||
3
.vscode/flib/locale/es-ES/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=es-ES
|
||||
locale-name=Español
|
||||
|
||||
2
.vscode/flib/locale/et/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=et
|
||||
locale-name=Eesti
|
||||
3
.vscode/flib/locale/fi/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=fi
|
||||
locale-name=Suomi
|
||||
|
||||
3
.vscode/flib/locale/fr/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=fr
|
||||
locale-name=Français
|
||||
|
||||
2
.vscode/flib/locale/fy-NL/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=fy-NL
|
||||
locale-name=Frisian
|
||||
2
.vscode/flib/locale/ga-IE/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=ga-IE
|
||||
locale-name=Gaeilge
|
||||
3
.vscode/flib/locale/he/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=he
|
||||
locale-name=עברית
|
||||
|
||||
2
.vscode/flib/locale/hr/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=hr
|
||||
locale-name=Hrvatski
|
||||
3
.vscode/flib/locale/hu/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=hu
|
||||
locale-name=Magyar
|
||||
|
||||
2
.vscode/flib/locale/id/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=id
|
||||
locale-name=Bahasa Indonesia
|
||||
3
.vscode/flib/locale/it/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=it
|
||||
locale-name=Italiano
|
||||
|
||||
3
.vscode/flib/locale/ja/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=ja
|
||||
locale-name=日本語
|
||||
|
||||
3
.vscode/flib/locale/ko/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=ko
|
||||
locale-name=한국어
|
||||
|
||||
2
.vscode/flib/locale/lt/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=lt
|
||||
locale-name=Lietuvių
|
||||
2
.vscode/flib/locale/lv/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=lv
|
||||
locale-name=Latviešu
|
||||
3
.vscode/flib/locale/nl/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=nl
|
||||
locale-name=Nederlands
|
||||
|
||||
3
.vscode/flib/locale/no/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=no
|
||||
locale-name=Norsk
|
||||
|
||||
3
.vscode/flib/locale/pl/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=pl
|
||||
locale-name=Polski
|
||||
|
||||
2
.vscode/flib/locale/pt-BR/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=pt-BR
|
||||
locale-name=Português, Brasil
|
||||
3
.vscode/flib/locale/pt-PT/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=pt-PT
|
||||
locale-name=Português
|
||||
|
||||
3
.vscode/flib/locale/ro/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=ro
|
||||
locale-name=Română
|
||||
|
||||
3
.vscode/flib/locale/ru/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=ru
|
||||
locale-name=Русский
|
||||
|
||||
2
.vscode/flib/locale/sk/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=sk
|
||||
locale-name=Slovenčina
|
||||
2
.vscode/flib/locale/sl/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=sl
|
||||
locale-name=Slovenščina
|
||||
2
.vscode/flib/locale/sq/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=sq
|
||||
locale-name=Shqip
|
||||
3
.vscode/flib/locale/sr/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=sr
|
||||
locale-name=Српски
|
||||
|
||||
3
.vscode/flib/locale/sv-SE/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=sv-SE
|
||||
locale-name=Svenska
|
||||
|
||||
2
.vscode/flib/locale/th/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
locale-identifier=th
|
||||
locale-name=ภาษาไทย
|
||||
3
.vscode/flib/locale/tr/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=tr
|
||||
locale-name=Türkçe
|
||||
|
||||
3
.vscode/flib/locale/uk/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=uk
|
||||
locale-name=Українська
|
||||
|
||||
3
.vscode/flib/locale/vi/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=vi
|
||||
locale-name=Tiếng Việt Nam
|
||||
|
||||
3
.vscode/flib/locale/zh-CN/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=zh-CN
|
||||
locale-name=简体中文
|
||||
|
||||
3
.vscode/flib/locale/zh-TW/dictionary.cfg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
locale-identifier=zh-TW
|
||||
locale-name=繁體中文
|
||||
|
||||
190
.vscode/flib/math.lua
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
--- Extension of the Lua 5.2 math library.
|
||||
--- @class flib_math: factorio.mathlib
|
||||
local flib_math = {}
|
||||
|
||||
local unpack = table.unpack
|
||||
|
||||
-- Import lua math functions
|
||||
for name, func in pairs(math) do
|
||||
flib_math[name] = func
|
||||
end
|
||||
|
||||
--- Multiply by degrees to convert to radians.
|
||||
--- ```lua
|
||||
--- local rad = 1 x flib_math.deg_to_rad -- 0.0174533
|
||||
--- ```
|
||||
flib_math.deg_to_rad = flib_math.pi / 180 --- @type number
|
||||
|
||||
--- Multiply by radians to convert to degrees.
|
||||
---
|
||||
--- ```lua
|
||||
--- local deg = 1 x flib_math.rad_to_deg -- 57.2958
|
||||
--- ```
|
||||
flib_math.rad_to_deg = 180 / flib_math.pi --- @type number
|
||||
|
||||
flib_math.max_double = 0X1.FFFFFFFFFFFFFP+1023
|
||||
flib_math.min_double = -0X1.FFFFFFFFFFFFFP+1023
|
||||
flib_math.max_int8 = 127 --- 127
|
||||
flib_math.min_int8 = -128 --- -128
|
||||
flib_math.max_uint8 = 255 --- 255
|
||||
flib_math.max_int16 = 32767 --- 32,767
|
||||
flib_math.min_int16 = -32768 --- -32,768
|
||||
flib_math.max_uint16 = 65535 --- 65,535
|
||||
flib_math.max_int = 2147483647 --- 2,147,483,647
|
||||
flib_math.min_int = -2147483648 --- -2,147,483,648
|
||||
flib_math.max_uint = 4294967295 --- 4,294,967,295
|
||||
flib_math.max_int53 = 0x1FFFFFFFFFFFFF --- 9,007,199,254,740,991
|
||||
flib_math.min_int53 = -0x20000000000000 --- -9,007,199,254,740,992
|
||||
|
||||
--- Round a number to the nearest multiple of divisor.
|
||||
--- Defaults to nearest integer if divisor is not provided.
|
||||
---
|
||||
--- From [lua-users.org](http://lua-users.org/wiki/SimpleRound).
|
||||
--- @param num number
|
||||
--- @param divisor? number `num` will be rounded to the nearest multiple of `divisor` (default: 1).
|
||||
--- @return number
|
||||
function flib_math.round(num, divisor)
|
||||
divisor = divisor or 1
|
||||
if num >= 0 then
|
||||
return flib_math.floor((num / divisor) + 0.5) * divisor
|
||||
else
|
||||
return flib_math.ceil((num / divisor) - 0.5) * divisor
|
||||
end
|
||||
end
|
||||
|
||||
--- Ceil a number to the nearest multiple of divisor.
|
||||
--- @param num number
|
||||
--- @param divisor? number `num` will be ceiled to the nearest multiple of `divisor` (default: 1).
|
||||
function flib_math.ceiled(num, divisor)
|
||||
if divisor then
|
||||
return flib_math.ceil(num / divisor) * divisor
|
||||
end
|
||||
return flib_math.ceil(num)
|
||||
end
|
||||
|
||||
--- Floor a number to the nearest multiple of divisor.
|
||||
--- @param num number
|
||||
--- @param divisor? number `num` will be floored to the nearest multiple of `divisor` (default: 1).
|
||||
function flib_math.floored(num, divisor)
|
||||
if divisor then
|
||||
return flib_math.floor(num / divisor) * divisor
|
||||
end
|
||||
return flib_math.floor(num)
|
||||
end
|
||||
|
||||
--- Round a number to the nearest N decimal places.
|
||||
---
|
||||
--- From [lua-users.org](http://lua-users.org/wiki/SimpleRound).
|
||||
--- @param num number
|
||||
--- @param num_decimals number
|
||||
--- @return number
|
||||
--- @deprecated Use flib_math.round
|
||||
function flib_math.round_to(num, num_decimals)
|
||||
local mult = 10 ^ num_decimals
|
||||
return flib_math.floor(num * mult + 0.5) / mult
|
||||
end
|
||||
|
||||
--- Ceil a number to N decimal places.
|
||||
--- Use `math.ceil` directly if no decimals are needed.
|
||||
--- @param num number
|
||||
--- @param num_decimals number
|
||||
--- @return number
|
||||
--- @deprecated Use flib_math.ceiled
|
||||
function flib_math.ceil_to(num, num_decimals)
|
||||
local mult = 10 ^ num_decimals
|
||||
return flib_math.ceil(num * mult) / mult
|
||||
end
|
||||
|
||||
--- Floor a number to N decimal places.
|
||||
--- Use `math.floor` directly if no decimals are needed.
|
||||
--- @param num number
|
||||
--- @param num_decimals number
|
||||
--- @return number
|
||||
--- @deprecated use flib_math.floored
|
||||
function flib_math.floor_to(num, num_decimals)
|
||||
local mult = 10 ^ num_decimals
|
||||
return flib_math.floor(num * mult) / mult
|
||||
end
|
||||
|
||||
--- Returns the argument with the maximum value from a set.
|
||||
--- @param set number[]
|
||||
--- @return number
|
||||
function flib_math.maximum(set)
|
||||
return flib_math.max(unpack(set))
|
||||
end
|
||||
|
||||
--- Returns the argument with the minimum value from a set.
|
||||
--- @param set number[]
|
||||
--- @return number
|
||||
function flib_math.minimum(set)
|
||||
return flib_math.min(unpack(set))
|
||||
end
|
||||
|
||||
--- Calculate the sum of a set of numbers.
|
||||
--- @param set number[]
|
||||
--- @return number
|
||||
function flib_math.sum(set)
|
||||
local sum = set[2] or 0
|
||||
for i = 2, #set do
|
||||
sum = sum + set[i]
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
--- Calculate the mean (average) of a set of numbers.
|
||||
--- @param set number[]
|
||||
--- @return number
|
||||
function flib_math.mean(set)
|
||||
return flib_math.sum(set) / #set
|
||||
end
|
||||
|
||||
--- Calculate the mean of the largest and the smallest values in a set of numbers.
|
||||
--- @param set number[]
|
||||
--- @return number
|
||||
function flib_math.midrange(set)
|
||||
return 0.5 * (flib_math.minimum(set) + flib_math.maximum(set))
|
||||
end
|
||||
|
||||
--- Calculate the range in a set of numbers.
|
||||
--- @param set number[]
|
||||
--- @return number
|
||||
function flib_math.range(set)
|
||||
return flib_math.maximum(set) - flib_math.minimum(set)
|
||||
end
|
||||
|
||||
--- Clamp a number between minimum and maximum values.
|
||||
--- @param x number
|
||||
--- @param min? number default 0
|
||||
--- @param max? number default 1
|
||||
--- @return number
|
||||
function flib_math.clamp(x, min, max)
|
||||
x = x == 0 and 0 or x -- Treat -0 as 0
|
||||
min, max = min or 0, max or 1
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
--- Return the signedness of a number as a multiplier.
|
||||
--- @param x number
|
||||
--- @return number
|
||||
function flib_math.sign(x)
|
||||
return (x >= 0 and 1) or -1
|
||||
end
|
||||
|
||||
--- Linearly interpolate between `num1` and `num2` by `amount`.
|
||||
---
|
||||
--- The parameter `amount` is clamped between `0` and `1`.
|
||||
---
|
||||
--- When `amount = 0`, returns `num1`.
|
||||
---
|
||||
--- When `amount = 1`, returns `num2`.
|
||||
---
|
||||
--- When `amount = 0.5`, returns the midpoint of `num1` and `num2`.
|
||||
--- @param num1 number
|
||||
--- @param num2 number
|
||||
--- @param amount number
|
||||
--- @return number
|
||||
function flib_math.lerp(num1, num2, amount)
|
||||
return num1 + (num2 - num1) * flib_math.clamp(amount, 0, 1)
|
||||
end
|
||||
|
||||
return flib_math
|
||||
134
.vscode/flib/migration.lua
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
--- Mod migration and version comparison functions.
|
||||
--- @class flib_migration
|
||||
local flib_migration = {}
|
||||
|
||||
local string = string
|
||||
local table = table
|
||||
|
||||
local version_pattern = "%d+"
|
||||
local version_format = "%02d"
|
||||
|
||||
--- Normalize version strings for easy comparison.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- migration.format_version("1.10.1234", "%04d")
|
||||
--- migration.format_version("3", "%02d")
|
||||
--- ```
|
||||
--- @param version string
|
||||
--- @param format string? default: `%02d`
|
||||
--- @return string?
|
||||
function flib_migration.format_version(version, format)
|
||||
if version then
|
||||
format = format or version_format
|
||||
local tbl = {}
|
||||
for v in string.gmatch(version, version_pattern) do
|
||||
tbl[#tbl + 1] = string.format(format, v)
|
||||
end
|
||||
if next(tbl) then
|
||||
return table.concat(tbl, ".")
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Check if current_version is newer than old_version.
|
||||
--- @param old_version string
|
||||
--- @param current_version string
|
||||
--- @param format string? default: `%02d`
|
||||
--- @return boolean?
|
||||
function flib_migration.is_newer_version(old_version, current_version, format)
|
||||
local v1 = flib_migration.format_version(old_version, format)
|
||||
local v2 = flib_migration.format_version(current_version, format)
|
||||
if v1 and v2 then
|
||||
if v2 > v1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Run migrations against the given version.
|
||||
--- @param old_version string
|
||||
--- @param migrations MigrationsTable
|
||||
--- @param format? string default: `%02d`
|
||||
--- @param ... any All additional arguments will be passed to each function within `migrations`.
|
||||
function flib_migration.run(old_version, migrations, format, ...)
|
||||
local migrate = false
|
||||
for version, func in pairs(migrations) do
|
||||
if migrate or flib_migration.is_newer_version(old_version, version, format) then
|
||||
migrate = true
|
||||
func(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Determine if migrations need to be run for this mod, then run them if needed.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- script.on_configuration_changed(function(e)
|
||||
--- if migration.on_config_changed(e, migrations) then
|
||||
--- -- Run generic (non-init) migrations
|
||||
--- rebuild_prototype_data()
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
--- @param e ConfigurationChangedData
|
||||
--- @param migrations? MigrationsTable
|
||||
--- @param mod_name? string The mod to check against. Defaults to the current mod.
|
||||
--- @param ... any All additional arguments will be passed to each function within `migrations`.
|
||||
--- @return boolean run_generic_micrations
|
||||
function flib_migration.on_config_changed(e, migrations, mod_name, ...)
|
||||
local changes = e.mod_changes[mod_name or script.mod_name]
|
||||
local old_version = changes and changes.old_version
|
||||
if old_version or not changes then
|
||||
if migrations then
|
||||
flib_migration.run(old_version, migrations, nil, ...)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Handle on_configuration_changed with the given generic and version-specific migrations. Will override any existing
|
||||
--- on_configuration_changed event handler. Both arguments are optional.
|
||||
--- @param version_migrations MigrationsTable?
|
||||
--- @param generic_handler fun(e: ConfigurationChangedData)?
|
||||
function flib_migration.handle_on_configuration_changed(version_migrations, generic_handler)
|
||||
script.on_configuration_changed(function(e)
|
||||
if flib_migration.on_config_changed(e, version_migrations) and generic_handler then
|
||||
generic_handler(e)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return flib_migration
|
||||
|
||||
--- Migration code to run for specific mod version. A given function will run if the previous mod version is less
|
||||
--- than the given version.
|
||||
---
|
||||
--- # Example
|
||||
---
|
||||
--- ```lua
|
||||
--- {
|
||||
--- ["1.0.1"] = function()
|
||||
--- global.foo = nil
|
||||
--- for _, player_table in pairs(global.players) do
|
||||
--- player_table.bar = "Lorem ipsum"
|
||||
--- end
|
||||
--- end,
|
||||
--- ["1.0.7"] = function()
|
||||
--- global.bar = {}
|
||||
--- end
|
||||
--- ["1.1.0"] = function(arg)
|
||||
--- global.foo = arg
|
||||
--- end
|
||||
--- }
|
||||
--- ```
|
||||
---
|
||||
--- If the mod is upgraded from 1.0.4 to 1.1.0, then the migrations for 1.0.7 and 1.1.0 will be run.
|
||||
--- @alias MigrationsTable table<string, fun(...: any)>
|
||||
72
.vscode/flib/misc.lua
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
--- @diagnostic disable
|
||||
--- @deprecated use `format` and `position` modules instead.`
|
||||
local flib_misc = {}
|
||||
|
||||
local math = math
|
||||
local string = string
|
||||
|
||||
--- @deprecated use `flib_position.distance` instead.`
|
||||
function flib_misc.get_distance(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return math.sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2)
|
||||
end
|
||||
|
||||
--- @deprecated use `flib_position.distance_squared` instead.`
|
||||
function flib_misc.get_distance_squared(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return (x1 - x2) ^ 2 + (y1 - y2) ^ 2
|
||||
end
|
||||
|
||||
--- @deprecated Use `flib_format.time`.
|
||||
function flib_misc.ticks_to_timestring(tick, include_leading_zeroes)
|
||||
local total_seconds = math.floor((tick or game.ticks_played) / 60)
|
||||
local seconds = total_seconds % 60
|
||||
local minutes = math.floor(total_seconds / 60)
|
||||
if minutes > 59 then
|
||||
minutes = minutes % 60
|
||||
local hours = math.floor(total_seconds / 3600)
|
||||
if include_leading_zeroes then
|
||||
return string.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
else
|
||||
return string.format("%d:%02d:%02d", hours, minutes, seconds)
|
||||
end
|
||||
else
|
||||
if include_leading_zeroes then
|
||||
return string.format("%02d:%02d", minutes, seconds)
|
||||
else
|
||||
return string.format("%d:%02d", minutes, seconds)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated Use `flib_format.number`.
|
||||
function flib_misc.delineate_number(number, delimiter)
|
||||
delimiter = delimiter or ","
|
||||
-- Handle decimals
|
||||
local _, _, before, after = string.find(tostring(number), "^(%d*)(%.%d*)")
|
||||
if before and after then
|
||||
number = tonumber(before) --[[@as number]]
|
||||
after = after
|
||||
else
|
||||
before = math.floor(number)
|
||||
after = ""
|
||||
end
|
||||
local formatted = before
|
||||
local k
|
||||
while true do
|
||||
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", "%1" .. delimiter .. "%2")
|
||||
if k == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return formatted .. after
|
||||
end
|
||||
|
||||
return flib_misc
|
||||
--- @diagnostic enable
|
||||
91
.vscode/flib/on-tick-n.lua
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
--- Schedule tasks to be executed later.
|
||||
--- @class flib_on_tick_n
|
||||
local on_tick_n = {}
|
||||
|
||||
--- Initialize the module's script data table.
|
||||
---
|
||||
--- Must be called at the **beginning** of `on_init`. Can also be used to delete all current tasks.
|
||||
function on_tick_n.init()
|
||||
if not global.__flib then
|
||||
global.__flib = {}
|
||||
end
|
||||
--- @type table<number, Tasks>
|
||||
global.__flib.on_tick_n = {}
|
||||
end
|
||||
|
||||
--- Retrieve the tasks for the given tick, if any.
|
||||
---
|
||||
--- Must be called **during** `on_tick`.
|
||||
--- @param tick number
|
||||
--- @return Tasks?
|
||||
function on_tick_n.retrieve(tick)
|
||||
-- Failsafe for rare cases where on_tick can fire before on_init
|
||||
if not global.__flib or not global.__flib.on_tick_n then
|
||||
return
|
||||
end
|
||||
local actions = global.__flib.on_tick_n[tick]
|
||||
if actions then
|
||||
global.__flib.on_tick_n[tick] = nil
|
||||
return actions
|
||||
end
|
||||
end
|
||||
|
||||
--- Add a task to execute on the given tick.
|
||||
--- @param tick number
|
||||
--- @param task any The data representing this task. This can be anything except for a `function`.
|
||||
--- @return TaskIdent ident An identifier for the task. Save this if you might remove the task before execution.
|
||||
function on_tick_n.add(tick, task)
|
||||
local list = global.__flib.on_tick_n
|
||||
local tick_list = list[tick]
|
||||
if tick_list then
|
||||
local index = #tick_list + 1
|
||||
tick_list[index] = task
|
||||
return { index = index, tick = tick }
|
||||
else
|
||||
list[tick] = { task }
|
||||
return { index = 1, tick = tick }
|
||||
end
|
||||
end
|
||||
|
||||
--- Remove a scheduled task.
|
||||
--- @param ident TaskIdent The identifier object for the task, as returned from `on-tick-n.add`.
|
||||
function on_tick_n.remove(ident)
|
||||
local tick_list = global.__flib.on_tick_n[ident.tick]
|
||||
if not tick_list or not tick_list[ident.index] then
|
||||
return false
|
||||
end
|
||||
|
||||
tick_list[ident.index] = nil
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- A unique identifier for a previously added task, used in `on-tick-n.remove`.
|
||||
--- @class TaskIdent
|
||||
--- @field tick number The tick this task is scheduled for.
|
||||
--- @field index number The tasks' index in the tick's `Tasks` table.
|
||||
|
||||
--- A table of tasks.
|
||||
---
|
||||
--- Each task can be anything that is not a function, as specified in `on-tick-n.add`.
|
||||
---
|
||||
--- **This is not an array, there may be gaps. Always use `pairs` to iterate this table.**
|
||||
---
|
||||
--- # Example
|
||||
---
|
||||
--- ```lua
|
||||
--- event.on_tick(function(e)
|
||||
--- for _, task in pairs(on_tick_n.retrieve(e.tick) or {}) do
|
||||
--- if task == "say_hi" then
|
||||
--- game.print("Hello there!")
|
||||
--- elseif task == "order_66" then
|
||||
--- for _, player in pairs(game.players) do
|
||||
--- player.die()
|
||||
--- end
|
||||
--- end
|
||||
--- end
|
||||
--- end)
|
||||
--- ```
|
||||
--- @alias Tasks table<number, any>
|
||||
|
||||
return on_tick_n
|
||||
39
.vscode/flib/orientation.lua
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
--- Functions for working with orientations.
|
||||
--- @class flib_orientation
|
||||
local flib_orientation = {}
|
||||
|
||||
flib_orientation.north = defines.direction.north / 8
|
||||
flib_orientation.east = defines.direction.east / 8
|
||||
flib_orientation.west = defines.direction.west / 8
|
||||
flib_orientation.south = defines.direction.south / 8
|
||||
flib_orientation.northeast = defines.direction.northeast / 8
|
||||
flib_orientation.northwest = defines.direction.northwest / 8
|
||||
flib_orientation.southeast = defines.direction.southeast / 8
|
||||
flib_orientation.southwest = defines.direction.southwest / 8
|
||||
|
||||
--- Returns a 4way or 8way direction from an orientation.
|
||||
--- @param orientation number
|
||||
--- @param eight_way boolean
|
||||
--- @return defines.direction
|
||||
function flib_orientation.to_direction(orientation, eight_way)
|
||||
local ways = eight_way and 8 or 4
|
||||
local mod = eight_way and 1 or 2
|
||||
return math.floor(orientation * ways + 0.5) % ways * mod --[[@as defines.direction]]
|
||||
end
|
||||
|
||||
--- Returns the opposite orientation.
|
||||
--- @param orientation number
|
||||
--- @return number
|
||||
function flib_orientation.opposite(orientation)
|
||||
return (orientation + 0.5) % 1
|
||||
end
|
||||
|
||||
--- Add two orientations together.
|
||||
--- @param orientation1 number
|
||||
--- @param orientation2 number
|
||||
--- @return number the orientations added together
|
||||
function flib_orientation.add(orientation1, orientation2)
|
||||
return (orientation1 + orientation2) % 1
|
||||
end
|
||||
|
||||
return flib_orientation
|
||||
257
.vscode/flib/position.lua
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
--- Utilities for manipulating positions. All functions support both the shorthand and explicit syntaxes and will
|
||||
--- preserve the syntax that was passed in.
|
||||
--- @class flib_position
|
||||
local flib_position = {}
|
||||
|
||||
--- FIXME: Sumneko doesn't properly handle generics yet and throws a bunch of bogus warnings.
|
||||
--- @diagnostic disable
|
||||
|
||||
--- Add two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return P
|
||||
function flib_position.add(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
if pos1.x then
|
||||
return { x = x1 + x2, y = y1 + y2 }
|
||||
else
|
||||
return { x1 + x2, y1 + y2 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Ceil the given position.
|
||||
--- @generic P
|
||||
--- @param pos `P`
|
||||
--- @return P
|
||||
function flib_position.ceil(pos)
|
||||
if pos.x then
|
||||
return { x = math.ceil(pos.x), y = math.ceil(pos.y) }
|
||||
else
|
||||
return { math.ceil(pos[1]), math.ceil(pos[2]) }
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate the distance between two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return number
|
||||
function flib_position.distance(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return math.sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2)
|
||||
end
|
||||
|
||||
--- Calculate the squared distance between two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return number
|
||||
function flib_position.distance_squared(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return (x1 - x2) ^ 2 + (y1 - y2) ^ 2
|
||||
end
|
||||
|
||||
--- Divide two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return P
|
||||
function flib_position.div(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
if pos1.x then
|
||||
return { x = x1 / x2, y = y1 / y2 }
|
||||
else
|
||||
return { x1 / x2, y1 / y2 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Return the position in explicit form.
|
||||
--- @generic P
|
||||
--- @param pos `P`
|
||||
--- @return P
|
||||
function flib_position.ensure_explicit(pos)
|
||||
if pos.x then
|
||||
return pos
|
||||
else
|
||||
return { x = pos[1], y = pos[2] }
|
||||
end
|
||||
end
|
||||
|
||||
--- Return the position in shorthand form.
|
||||
--- @generic P
|
||||
--- @param pos `P`
|
||||
--- @return P
|
||||
function flib_position.ensure_short(pos)
|
||||
if pos.x then
|
||||
return { pos.x, pos.y }
|
||||
else
|
||||
return pos
|
||||
end
|
||||
end
|
||||
|
||||
--- Test if two positions are equal.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return boolean
|
||||
function flib_position.eq(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return x1 == x2 and y1 == y2
|
||||
end
|
||||
|
||||
--- Floor the given position.
|
||||
--- @generic P
|
||||
--- @param pos `P`
|
||||
--- @return P
|
||||
function flib_position.floor(pos)
|
||||
if pos.x then
|
||||
return { x = math.floor(pos.x), y = math.floor(pos.y) }
|
||||
else
|
||||
return { math.floor(pos[1]), math.floor(pos[2]) }
|
||||
end
|
||||
end
|
||||
|
||||
--- Convert a `ChunkPosition` into a `TilePosition` by multiplying by 32.
|
||||
--- @param pos ChunkPosition
|
||||
--- @return TilePosition
|
||||
function flib_position.from_chunk(pos)
|
||||
if pos.x then
|
||||
return { x = pos.x * 32, y = pos.y * 32 }
|
||||
else
|
||||
return { pos[1] * 32, pos[2] * 32 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Test if `pos1` is less than or equal to `pos2`.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return boolean
|
||||
function flib_position.le(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return x1 <= x2 and y1 <= y2
|
||||
end
|
||||
|
||||
--- Test if `pos1` is less than `pos2`.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return boolean
|
||||
function flib_position.lt(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
return x1 < x2 and y1 < y2
|
||||
end
|
||||
|
||||
--- Take the remainder (modulus) of two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return P
|
||||
function flib_position.mod(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
if pos1.x then
|
||||
return { x = x1 % x2, y = y1 % y2 }
|
||||
else
|
||||
return { x1 % x2, y1 % y2 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Multiply two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return P
|
||||
function flib_position.mul(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
if pos1.x then
|
||||
return { x = x1 * x2, y = y1 * y2 }
|
||||
else
|
||||
return { x1 * x2, y1 * y2 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Subtract two positions.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return P
|
||||
function flib_position.sub(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
if pos1.x then
|
||||
return { x = x1 - x2, y = y1 - y2 }
|
||||
else
|
||||
return { x1 - x2, y1 - y2 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Take the power of two positions. `pos1^pos2`.
|
||||
--- @generic P
|
||||
--- @param pos1 `P`
|
||||
--- @param pos2 `P`
|
||||
--- @return P
|
||||
function flib_position.pow(pos1, pos2)
|
||||
local x1 = pos1.x or pos1[1]
|
||||
local y1 = pos1.y or pos1[2]
|
||||
local x2 = pos2.x or pos2[1]
|
||||
local y2 = pos2.y or pos2[2]
|
||||
if pos1.x then
|
||||
return { x = x1 ^ x2, y = y1 ^ y2 }
|
||||
else
|
||||
return { x1 ^ x2, y1 ^ y2 }
|
||||
end
|
||||
end
|
||||
|
||||
--- Convert a `MapPosition` or `TilePosition` into a `ChunkPosition` by dividing by 32 and flooring.
|
||||
--- @param pos MapPosition|TilePosition
|
||||
--- @return ChunkPosition
|
||||
function flib_position.to_chunk(pos)
|
||||
if pos.x then
|
||||
return { x = math.floor(pos.x / 32), y = math.floor(pos.y / 32) }
|
||||
else
|
||||
return { math.floor(pos[1] / 32), math.floor(pos[2] / 32) }
|
||||
end
|
||||
end
|
||||
|
||||
--- Convert a `MapPosition` into a `TilePosition` by flooring.
|
||||
--- @param pos MapPosition
|
||||
--- @return TilePosition
|
||||
function flib_position.to_tile(pos)
|
||||
if pos.x then
|
||||
return { x = math.floor(pos.x), y = math.floor(pos.y) }
|
||||
else
|
||||
return { math.floor(pos[1]), math.floor(pos[2]) }
|
||||
end
|
||||
end
|
||||
|
||||
return flib_position
|
||||
53
.vscode/flib/prototypes/sprite.lua
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
-- INDICATOR SPRITES
|
||||
|
||||
local indicators = {}
|
||||
for i, color in ipairs({ "black", "white", "red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink" }) do
|
||||
indicators[i] = {
|
||||
type = "sprite",
|
||||
name = "flib_indicator_" .. color,
|
||||
filename = "__flib__/graphics/indicators.png",
|
||||
y = (i - 1) * 32,
|
||||
size = 32,
|
||||
flags = { "icon" },
|
||||
}
|
||||
end
|
||||
data:extend(indicators)
|
||||
|
||||
local fab = "__flib__/graphics/frame-action-icons.png"
|
||||
|
||||
data:extend({
|
||||
{ type = "sprite", name = "flib_pin_black", filename = fab, position = { 0, 0 }, size = 32, flags = { "icon" } },
|
||||
{ type = "sprite", name = "flib_pin_white", filename = fab, position = { 32, 0 }, size = 32, flags = { "icon" } },
|
||||
{
|
||||
type = "sprite",
|
||||
name = "flib_pin_disabled",
|
||||
filename = fab,
|
||||
position = { 64, 0 },
|
||||
size = 32,
|
||||
flags = { "icon" },
|
||||
},
|
||||
{
|
||||
type = "sprite",
|
||||
name = "flib_settings_black",
|
||||
filename = fab,
|
||||
position = { 0, 32 },
|
||||
size = 32,
|
||||
flags = { "icon" },
|
||||
},
|
||||
{
|
||||
type = "sprite",
|
||||
name = "flib_settings_white",
|
||||
filename = fab,
|
||||
position = { 32, 32 },
|
||||
size = 32,
|
||||
flags = { "icon" },
|
||||
},
|
||||
{
|
||||
type = "sprite",
|
||||
name = "flib_settings_disabled",
|
||||
filename = fab,
|
||||
position = { 64, 32 },
|
||||
size = 32,
|
||||
flags = { "icon" },
|
||||
},
|
||||
})
|
||||
335
.vscode/flib/prototypes/style.lua
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
local data_util = require("__flib__/data-util")
|
||||
|
||||
local styles = data.raw["gui-style"].default
|
||||
|
||||
-- SLOT BUTTON STYLES
|
||||
|
||||
local slot_tileset = "__flib__/graphics/slots.png"
|
||||
|
||||
local function gen_slot(x, y, default_offset)
|
||||
default_offset = default_offset or 0
|
||||
return {
|
||||
type = "button_style",
|
||||
parent = "slot",
|
||||
size = 40,
|
||||
default_graphical_set = {
|
||||
base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset },
|
||||
},
|
||||
hovered_graphical_set = {
|
||||
base = { border = 4, position = { x + 80, y }, size = 80, filename = slot_tileset },
|
||||
},
|
||||
clicked_graphical_set = {
|
||||
base = { border = 4, position = { x + 160, y }, size = 80, filename = slot_tileset },
|
||||
},
|
||||
disabled_graphical_set = { -- identical to default graphical set
|
||||
base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset },
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local function gen_slot_button(x, y, default_offset, glow)
|
||||
default_offset = default_offset or 0
|
||||
return {
|
||||
type = "button_style",
|
||||
parent = "slot_button",
|
||||
size = 40,
|
||||
default_graphical_set = {
|
||||
base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_2_rounded_corners_glow(_ENV.default_dirt_color),
|
||||
},
|
||||
hovered_graphical_set = {
|
||||
base = { border = 4, position = { x + 80, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_2_rounded_corners_glow(_ENV.default_dirt_color),
|
||||
glow = _ENV.offset_by_2_rounded_corners_glow(glow),
|
||||
},
|
||||
clicked_graphical_set = {
|
||||
base = { border = 4, position = { x + 160, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_2_rounded_corners_glow(_ENV.default_dirt_color),
|
||||
},
|
||||
disabled_graphical_set = { -- identical to default graphical set
|
||||
base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_2_rounded_corners_glow(_ENV.default_dirt_color),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local function gen_standalone_slot_button(x, y, default_offset)
|
||||
default_offset = default_offset or 0
|
||||
return {
|
||||
type = "button_style",
|
||||
parent = "slot_button",
|
||||
size = 40,
|
||||
default_graphical_set = {
|
||||
base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_4_rounded_corners_subpanel_inset,
|
||||
},
|
||||
hovered_graphical_set = {
|
||||
base = { border = 4, position = { x + 80, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_4_rounded_corners_subpanel_inset,
|
||||
},
|
||||
clicked_graphical_set = {
|
||||
base = { border = 4, position = { x + 160, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_4_rounded_corners_subpanel_inset,
|
||||
},
|
||||
disabled_graphical_set = { -- identical to default graphical set
|
||||
base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset },
|
||||
shadow = _ENV.offset_by_4_rounded_corners_subpanel_inset,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local slot_data = {
|
||||
{ name = "default", y = 0, glow = _ENV.default_glow_color },
|
||||
{ name = "grey", y = 80, glow = _ENV.default_glow_color },
|
||||
{ name = "red", y = 160, glow = { 230, 135, 135 } },
|
||||
{ name = "orange", y = 240, glow = { 216, 169, 122 } },
|
||||
{ name = "yellow", y = 320, glow = { 230, 218, 135 } },
|
||||
{ name = "green", y = 400, glow = { 153, 230, 135 } },
|
||||
{ name = "cyan", y = 480, glow = { 135, 230, 230 } },
|
||||
{ name = "blue", y = 560, glow = { 135, 186, 230 } },
|
||||
{ name = "purple", y = 640, glow = { 188, 135, 230 } },
|
||||
{ name = "pink", y = 720, glow = { 230, 135, 230 } },
|
||||
}
|
||||
|
||||
for _, data in pairs(slot_data) do
|
||||
styles["flib_slot_" .. data.name] = gen_slot(0, data.y)
|
||||
styles["flib_selected_slot_" .. data.name] = gen_slot(0, data.y, 80)
|
||||
styles["flib_slot_button_" .. data.name] = gen_slot_button(240, data.y, 0, data.glow)
|
||||
styles["flib_selected_slot_button_" .. data.name] = gen_slot_button(240, data.y, 80, data.glow)
|
||||
styles["flib_standalone_slot_button_" .. data.name] = gen_standalone_slot_button(240, data.y)
|
||||
styles["flib_selected_standalone_slot_button_" .. data.name] = gen_standalone_slot_button(240, data.y, 80)
|
||||
end
|
||||
|
||||
-- BUTTON STYLES
|
||||
|
||||
styles.flib_selected_frame_action_button = {
|
||||
type = "button_style",
|
||||
parent = "frame_action_button",
|
||||
default_font_color = _ENV.button_hovered_font_color,
|
||||
default_graphical_set = {
|
||||
base = { position = { 225, 17 }, corner_size = 8 },
|
||||
shadow = { position = { 440, 24 }, corner_size = 8, draw_type = "outer" },
|
||||
},
|
||||
hovered_font_color = _ENV.button_hovered_font_color,
|
||||
hovered_graphical_set = {
|
||||
base = { position = { 369, 17 }, corner_size = 8 },
|
||||
shadow = { position = { 440, 24 }, corner_size = 8, draw_type = "outer" },
|
||||
},
|
||||
clicked_font_color = _ENV.button_hovered_font_color,
|
||||
clicked_graphical_set = {
|
||||
base = { position = { 352, 17 }, corner_size = 8 },
|
||||
shadow = { position = { 440, 24 }, corner_size = 8, draw_type = "outer" },
|
||||
},
|
||||
-- Simulate clicked-vertical-offset
|
||||
top_padding = 1,
|
||||
bottom_padding = -1,
|
||||
}
|
||||
|
||||
local btn = styles.button
|
||||
|
||||
styles.flib_selected_tool_button = {
|
||||
type = "button_style",
|
||||
parent = "tool_button",
|
||||
default_font_color = btn.selected_font_color,
|
||||
default_graphical_set = btn.selected_graphical_set,
|
||||
hovered_font_color = btn.selected_hovered_font_color,
|
||||
hovered_graphical_set = btn.selected_hovered_graphical_set,
|
||||
clicked_font_color = btn.selected_clicked_font_color,
|
||||
clicked_graphical_set = btn.selected_clicked_graphical_set,
|
||||
-- Simulate clicked-vertical-offset
|
||||
top_padding = 1,
|
||||
bottom_padding = -1,
|
||||
}
|
||||
|
||||
styles.flib_tool_button_light_green = {
|
||||
type = "button_style",
|
||||
parent = "item_and_count_select_confirm",
|
||||
padding = 2,
|
||||
top_margin = 0,
|
||||
}
|
||||
|
||||
styles.flib_tool_button_dark_red = {
|
||||
type = "button_style",
|
||||
parent = "tool_button",
|
||||
default_graphical_set = {
|
||||
base = { filename = data_util.dark_red_button_tileset, position = { 0, 0 }, corner_size = 8 },
|
||||
shadow = _ENV.default_dirt,
|
||||
},
|
||||
hovered_graphical_set = {
|
||||
base = { filename = data_util.dark_red_button_tileset, position = { 17, 0 }, corner_size = 8 },
|
||||
shadow = _ENV.default_dirt,
|
||||
glow = _ENV.default_glow({ 236, 130, 130, 127 }, 0.5),
|
||||
},
|
||||
clicked_graphical_set = {
|
||||
base = { filename = data_util.dark_red_button_tileset, position = { 34, 0 }, corner_size = 8 },
|
||||
shadow = _ENV.default_dirt,
|
||||
},
|
||||
}
|
||||
|
||||
-- EMPTY-WIDGET STYLES
|
||||
|
||||
styles.flib_dialog_footer_drag_handle = {
|
||||
type = "empty_widget_style",
|
||||
parent = "draggable_space",
|
||||
height = 32,
|
||||
horizontally_stretchable = "on",
|
||||
}
|
||||
|
||||
styles.flib_dialog_footer_drag_handle_no_right = {
|
||||
type = "empty_widget_style",
|
||||
parent = "flib_dialog_footer_drag_handle",
|
||||
right_margin = 0,
|
||||
}
|
||||
|
||||
styles.flib_dialog_titlebar_drag_handle = {
|
||||
type = "empty_widget_style",
|
||||
parent = "flib_titlebar_drag_handle",
|
||||
right_margin = 0,
|
||||
}
|
||||
|
||||
styles.flib_horizontal_pusher = {
|
||||
type = "empty_widget_style",
|
||||
horizontally_stretchable = "on",
|
||||
}
|
||||
|
||||
styles.flib_titlebar_drag_handle = {
|
||||
type = "empty_widget_style",
|
||||
parent = "draggable_space",
|
||||
left_margin = 4,
|
||||
right_margin = 4,
|
||||
height = 24,
|
||||
horizontally_stretchable = "on",
|
||||
}
|
||||
|
||||
styles.flib_vertical_pusher = {
|
||||
type = "empty_widget_style",
|
||||
vertically_stretchable = "on",
|
||||
}
|
||||
|
||||
-- FLOW STYLES
|
||||
|
||||
styles.flib_indicator_flow = {
|
||||
type = "horizontal_flow_style",
|
||||
vertical_align = "center",
|
||||
}
|
||||
|
||||
styles.flib_titlebar_flow = {
|
||||
type = "horizontal_flow_style",
|
||||
horizontal_spacing = 8,
|
||||
}
|
||||
|
||||
-- FRAME STYLES
|
||||
|
||||
styles.flib_shallow_frame_in_shallow_frame = {
|
||||
type = "frame_style",
|
||||
parent = "frame",
|
||||
padding = 0,
|
||||
graphical_set = {
|
||||
base = {
|
||||
position = { 85, 0 },
|
||||
corner_size = 8,
|
||||
center = { position = { 76, 8 }, size = { 1, 1 } },
|
||||
draw_type = "outer",
|
||||
},
|
||||
shadow = _ENV.default_inner_shadow,
|
||||
},
|
||||
vertical_flow_style = {
|
||||
type = "vertical_flow_style",
|
||||
vertical_spacing = 0,
|
||||
},
|
||||
}
|
||||
|
||||
-- IMAGE STYLES
|
||||
|
||||
styles.flib_indicator = {
|
||||
type = "image_style",
|
||||
size = 16,
|
||||
stretch_image_to_widget_size = true,
|
||||
}
|
||||
|
||||
-- LINE STYLES
|
||||
|
||||
styles.flib_subheader_horizontal_line = {
|
||||
type = "line_style",
|
||||
horizontally_stretchable = "on",
|
||||
left_margin = -8,
|
||||
right_margin = -8,
|
||||
top_margin = -2,
|
||||
bottom_margin = -2,
|
||||
border = {
|
||||
border_width = 8,
|
||||
horizontal_line = { filename = "__flib__/graphics/subheader-line.png", size = { 1, 8 } },
|
||||
},
|
||||
}
|
||||
|
||||
-- SCROLL-PANE STYLES
|
||||
|
||||
styles.flib_naked_scroll_pane = {
|
||||
type = "scroll_pane_style",
|
||||
extra_padding_when_activated = 0,
|
||||
padding = 12,
|
||||
graphical_set = {
|
||||
shadow = _ENV.default_inner_shadow,
|
||||
},
|
||||
}
|
||||
|
||||
styles.flib_naked_scroll_pane_under_tabs = {
|
||||
type = "scroll_pane_style",
|
||||
parent = "flib_naked_scroll_pane",
|
||||
graphical_set = {
|
||||
base = {
|
||||
top = { position = { 93, 0 }, size = { 1, 8 } },
|
||||
draw_type = "outer",
|
||||
},
|
||||
shadow = _ENV.default_inner_shadow,
|
||||
},
|
||||
}
|
||||
|
||||
styles.flib_naked_scroll_pane_no_padding = {
|
||||
type = "scroll_pane_style",
|
||||
parent = "flib_naked_scroll_pane",
|
||||
padding = 0,
|
||||
}
|
||||
|
||||
styles.flib_shallow_scroll_pane = {
|
||||
type = "scroll_pane_style",
|
||||
padding = 0,
|
||||
graphical_set = {
|
||||
base = { position = { 85, 0 }, corner_size = 8, draw_type = "outer" },
|
||||
shadow = _ENV.default_inner_shadow,
|
||||
},
|
||||
}
|
||||
|
||||
-- TABBED PANE STYLES
|
||||
|
||||
styles.flib_tabbed_pane_with_no_padding = {
|
||||
type = "tabbed_pane_style",
|
||||
tab_content_frame = {
|
||||
type = "frame_style",
|
||||
top_padding = 0,
|
||||
bottom_padding = 0,
|
||||
left_padding = 0,
|
||||
right_padding = 0,
|
||||
graphical_set = {
|
||||
base = {
|
||||
-- Same as tabbed_pane_graphical_set - but without bottom
|
||||
top = { position = { 76, 0 }, size = { 1, 8 } },
|
||||
center = { position = { 76, 8 }, size = { 1, 1 } },
|
||||
},
|
||||
shadow = _ENV.top_shadow,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-- TEXTFIELD STYLES
|
||||
|
||||
styles.flib_widthless_textfield = {
|
||||
type = "textbox_style",
|
||||
width = 0,
|
||||
}
|
||||
|
||||
styles.flib_widthless_invalid_textfield = {
|
||||
type = "textbox_style",
|
||||
parent = "invalid_value_textfield",
|
||||
width = 0,
|
||||
}
|
||||
142
.vscode/flib/queue.lua
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
--- Lua queue implementation.
|
||||
---
|
||||
--- Based on "Queues and Double Queues" from [Programming in Lua](http://www.lua.org/pil/11.4.html).
|
||||
--- @class flib_queue
|
||||
local flib_queue = {}
|
||||
|
||||
---@class Queue<T>: { [integer]: T, first: integer, last: integer }
|
||||
|
||||
--- Create a new queue.
|
||||
--- @return Queue
|
||||
function flib_queue.new()
|
||||
return { first = 0, last = -1 }
|
||||
end
|
||||
|
||||
--- Push an element into the front of the queue.
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @param value T
|
||||
function flib_queue.push_front(self, value)
|
||||
local first = self.first - 1
|
||||
self.first = first
|
||||
self[first] = value
|
||||
end
|
||||
|
||||
--- Push an element into the back of the queue.
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @param value `T`
|
||||
function flib_queue.push_back(self, value)
|
||||
local last = self.last + 1
|
||||
self.last = last
|
||||
self[last] = value
|
||||
end
|
||||
|
||||
--- Retrieve an element from the front of the queue.
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @return T?
|
||||
function flib_queue.pop_front(self)
|
||||
local first = self.first
|
||||
if first > self.last then
|
||||
return
|
||||
end
|
||||
local value = self[first]
|
||||
self[first] = nil
|
||||
self.first = first + 1
|
||||
return value
|
||||
end
|
||||
|
||||
--- Retrieve an element from the back of the queue.
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @return T?
|
||||
function flib_queue.pop_back(self)
|
||||
local last = self.last
|
||||
if self.first > last then
|
||||
return
|
||||
end
|
||||
local value = self[last]
|
||||
self[last] = nil
|
||||
self.last = last - 1
|
||||
return value
|
||||
end
|
||||
|
||||
--- Iterate over a queue's elements from the beginning to the end.
|
||||
---
|
||||
--- # Example
|
||||
---
|
||||
--- ```lua
|
||||
--- local my_queue = queue.new()
|
||||
--- for i = 1, 10 do
|
||||
--- queue.push_back(my_queue, 1)
|
||||
--- end
|
||||
---
|
||||
--- -- 1 2 3 4 5 6 7 8 9 10
|
||||
--- for num in queue.iter(my_queue) do
|
||||
--- log(i)
|
||||
--- end
|
||||
--- ```
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @return fun(self: Queue<T>, index: integer): T
|
||||
function flib_queue.iter(self)
|
||||
local i = self.first - 1
|
||||
return function()
|
||||
if i < self.last then
|
||||
i = i + 1
|
||||
return i, self[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Iterate over a queue's elements from the end to the beginning.
|
||||
---
|
||||
--- # Example
|
||||
---
|
||||
--- ```lua
|
||||
--- local my_queue = queue.new()
|
||||
--- for i = 1, 10 do
|
||||
--- queue.push_back(my_queue, 1)
|
||||
--- end
|
||||
---
|
||||
--- -- 10 9 8 7 6 5 4 3 2 1
|
||||
--- for num in queue.iter_rev(my_queue) do
|
||||
--- log(i)
|
||||
--- end
|
||||
--- ```
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @return fun(self: Queue<T>, index: integer): T
|
||||
function flib_queue.iter_rev(self)
|
||||
local i = self.last + 1
|
||||
return function()
|
||||
if i > self.first then
|
||||
i = i - 1
|
||||
return i, self[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the length of the queue.
|
||||
--- @generic T
|
||||
--- @param self Queue<T>
|
||||
--- @return number
|
||||
function flib_queue.length(self)
|
||||
return math.abs(self.last - self.first + 1)
|
||||
end
|
||||
|
||||
--- @deprecated Use `flib_queue.push_front` instead
|
||||
flib_queue.push_left = flib_queue.push_front
|
||||
--- @deprecated Use `flib_queue.push_back` instead
|
||||
flib_queue.push_right = flib_queue.push_back
|
||||
--- @deprecated Use `flib_queue.pop_front` instead
|
||||
flib_queue.pop_left = flib_queue.pop_front
|
||||
--- @deprecated Use `flib_queue.pop_back` instead
|
||||
flib_queue.pop_right = flib_queue.pop_back
|
||||
--- @deprecated Use `flib_queue.iter` instead
|
||||
flib_queue.iter_left = flib_queue.iter
|
||||
--- @deprecated Use `flib_queue.iter_rev` instead
|
||||
flib_queue.iter_right = flib_queue.iter_rev
|
||||
|
||||
return flib_queue
|
||||
30
.vscode/flib/reverse-defines.lua
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
--- Defines reverse lookup table.
|
||||
---
|
||||
--- NOTE: Type intellisense simply does not work for this module, and there is no easy way to fix
|
||||
--- it. Use of this module is discouraged.
|
||||
---
|
||||
--- # Example
|
||||
---
|
||||
--- ```lua
|
||||
--- event.on_built_entity(function(e)
|
||||
--- local player = game.get_player(e.player_index)
|
||||
--- local controller_name = reverse_defines.controllers[player.controller_type]
|
||||
--- end)
|
||||
--- ```
|
||||
local flib_reverse_defines = {}
|
||||
|
||||
local function build_reverse_defines(lookup_table, base_table)
|
||||
lookup_table = lookup_table or {}
|
||||
for k, v in pairs(base_table) do
|
||||
if type(v) == "table" then
|
||||
lookup_table[k] = {}
|
||||
build_reverse_defines(lookup_table[k], v)
|
||||
else
|
||||
lookup_table[v] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
build_reverse_defines(flib_reverse_defines, defines)
|
||||
|
||||
return flib_reverse_defines
|
||||
552
.vscode/flib/table.lua
vendored
Normal file
@@ -0,0 +1,552 @@
|
||||
--- Extension of the Lua 5.2 table library.
|
||||
---
|
||||
--- **NOTE:** Several functions in this module will only work with [arrays](https://www.lua.org/pil/11.1.html),
|
||||
--- which are tables with sequentially numbered keys. All table functions will work with arrays as well, but
|
||||
--- array functions **will not** work with tables.
|
||||
--- @class flib_table: tablelib
|
||||
local flib_table = {}
|
||||
|
||||
-- Import lua table functions
|
||||
for name, func in pairs(table) do
|
||||
flib_table[name] = func
|
||||
end
|
||||
|
||||
--- Shallow copy an array's values into a new array.
|
||||
---
|
||||
--- This function is optimized specifically for arrays, and should be used in place of `table.shallow_copy` for arrays.
|
||||
--- @param arr Array
|
||||
--- @return Array
|
||||
function flib_table.array_copy(arr)
|
||||
local new_arr = {}
|
||||
for i = 1, #arr do
|
||||
new_arr[i] = arr[i]
|
||||
end
|
||||
return new_arr
|
||||
end
|
||||
|
||||
--- Merge all of the given arrays into a single array.
|
||||
--- @param arrays Array An array of arrays to merge.
|
||||
--- @return Array
|
||||
function flib_table.array_merge(arrays)
|
||||
local output = {}
|
||||
local i = 0
|
||||
for j = 1, #arrays do
|
||||
local arr = arrays[j]
|
||||
for k = 1, #arr do
|
||||
i = i + 1
|
||||
output[i] = arr[k]
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- Recursively compare two tables for inner equality.
|
||||
---
|
||||
--- Does not compare metatables.
|
||||
--- @param tbl1 table
|
||||
--- @param tbl2 table
|
||||
--- @return boolean
|
||||
function flib_table.deep_compare(tbl1, tbl2)
|
||||
if tbl1 == tbl2 then
|
||||
return true
|
||||
end
|
||||
for k, v in pairs(tbl1) do
|
||||
if type(v) == "table" and type(tbl2[k]) == "table" then
|
||||
if not flib_table.deep_compare(v, tbl2[k]) then
|
||||
return false
|
||||
end
|
||||
else
|
||||
if v ~= tbl2[k] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
for k in pairs(tbl2) do
|
||||
if tbl1[k] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Recursively copy the contents of a table into a new table.
|
||||
---
|
||||
--- Does not create new copies of Factorio objects.
|
||||
--- @generic T
|
||||
--- @param tbl T The table to make a copy of.
|
||||
--- @return T
|
||||
function flib_table.deep_copy(tbl)
|
||||
local lookup_table = {}
|
||||
local function _copy(object)
|
||||
if type(object) ~= "table" then
|
||||
return object
|
||||
-- don't copy factorio rich objects
|
||||
elseif object.__self then
|
||||
return object
|
||||
elseif lookup_table[object] then
|
||||
return lookup_table[object]
|
||||
end
|
||||
|
||||
local new_table = {}
|
||||
lookup_table[object] = new_table
|
||||
for index, value in pairs(object) do
|
||||
new_table[_copy(index)] = _copy(value)
|
||||
end
|
||||
|
||||
return setmetatable(new_table, getmetatable(object))
|
||||
end
|
||||
return _copy(tbl)
|
||||
end
|
||||
|
||||
--- Recursively merge two or more tables.
|
||||
---
|
||||
--- Values from earlier tables are overwritten by values from later tables, unless both values are tables, in which case
|
||||
--- they are recursively merged.
|
||||
---
|
||||
--- Non-merged tables are deep-copied, so the result is brand-new.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {foo = "bar"}
|
||||
--- log(tbl.foo) -- logs "bar"
|
||||
--- log (tbl.bar) -- errors (key is nil)
|
||||
--- tbl = table.merge{tbl, {foo = "baz", set = 3}}
|
||||
--- log(tbl.foo) -- logs "baz"
|
||||
--- log(tbl.bar) -- logs "3"
|
||||
--- ```
|
||||
--- @param tables Array An array of tables to merge.
|
||||
--- @return table
|
||||
function flib_table.deep_merge(tables)
|
||||
local output = {}
|
||||
for _, tbl in ipairs(tables) do
|
||||
for k, v in pairs(tbl) do
|
||||
if type(v) == "table" then
|
||||
if type(output[k] or false) == "table" then
|
||||
output[k] = flib_table.deep_merge({ output[k], v })
|
||||
else
|
||||
output[k] = flib_table.deep_copy(v)
|
||||
end
|
||||
else
|
||||
output[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- Find and return the first key containing the given value.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {"foo", "bar"}
|
||||
--- local key_of_foo = table.find(tbl, "foo") -- 1
|
||||
--- local key_of_baz = table.find(tbl, "baz") -- nil
|
||||
--- ```
|
||||
--- @generic K, V
|
||||
--- @param tbl table<K, V> The table to search.
|
||||
--- @param value V The value to match. Must have an `eq` metamethod set, otherwise will error.
|
||||
--- @return K? key The first key corresponding to `value`, if any.
|
||||
function flib_table.find(tbl, value)
|
||||
for k, v in pairs(tbl) do
|
||||
if v == value then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Call the given function for each item in the table, and abort if the function returns truthy.
|
||||
---
|
||||
--- Calls `callback(value, key)` for each item in the table, and immediately ceases iteration if the callback returns truthy.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {1, 2, 3, 4, 5}
|
||||
--- -- Run a function for each item (identical to a standard FOR loop)
|
||||
--- table.for_each(tbl, function(v) game.print(v) end)
|
||||
--- -- Determine if any value in the table passes the test
|
||||
--- local value_is_even = table.for_each(tbl, function(v) return v % 2 == 0 end)
|
||||
--- -- Determine if ALL values in the table pass the test (invert the test result and function return)
|
||||
--- local all_values_less_than_six = not table.for_each(tbl, function(v) return not (v < 6) end)
|
||||
--- ```
|
||||
--- @generic K, V
|
||||
--- @param tbl table<K, V>
|
||||
--- @param callback fun(value: V, key: K): boolean Receives `value` and `key` as parameters.
|
||||
--- @return boolean Whether the callback returned truthy for any one item, and thus halted iteration.
|
||||
function flib_table.for_each(tbl, callback)
|
||||
for k, v in pairs(tbl) do
|
||||
if callback(v, k) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Call the given function on a set number of items in a table, returning the next starting key.
|
||||
---
|
||||
--- Calls `callback(value, key)` over `n` items from `tbl`, starting after `from_k`.
|
||||
---
|
||||
--- The first return value of each invocation of `callback` will be collected and returned in a table keyed by the
|
||||
--- current item's key.
|
||||
---
|
||||
--- The second return value of `callback` is a flag requesting deletion of the current item.
|
||||
---
|
||||
--- The third return value of `callback` is a flag requesting that the iteration be immediately aborted. Use this flag to
|
||||
--- early return on some condition in `callback`. When aborted, `for_n_of` will return the previous key as `from_k`, so
|
||||
--- the next call to `for_n_of` will restart on the key that was aborted (unless it was also deleted).
|
||||
---
|
||||
--- **DO NOT** delete entires from `tbl` from within `callback`, this will break the iteration. Use the deletion flag
|
||||
--- instead.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local extremely_large_table = {
|
||||
--- [1000] = 1,
|
||||
--- [999] = 2,
|
||||
--- [998] = 3,
|
||||
--- ...,
|
||||
--- [2] = 999,
|
||||
--- [1] = 1000,
|
||||
--- }
|
||||
--- event.on_tick(function()
|
||||
--- global.from_k = table.for_n_of(extremely_large_table, global.from_k, 10, function(v) game.print(v) end)
|
||||
--- end)
|
||||
--- ```
|
||||
--- @generic K, V, C
|
||||
--- @param tbl table<K, V> The table to iterate over.
|
||||
--- @param from_k K The key to start iteration at, or `nil` to start at the beginning of `tbl`. If the key does not exist in `tbl`, it will be treated as `nil`, _unless_ a custom `_next` function is used.
|
||||
--- @param n number The number of items to iterate.
|
||||
--- @param callback fun(value: V, key: K):C,boolean,boolean Receives `value` and `key` as parameters.
|
||||
--- @param _next? fun(tbl: table<K, V>, from_k: K):K,V A custom `next()` function. If not provided, the default `next()` will be used.
|
||||
--- @return K? next_key Where the iteration ended. Can be any valid table key, or `nil`. Pass this as `from_k` in the next call to `for_n_of` for `tbl`.
|
||||
--- @return table<K, C> results The results compiled from the first return of `callback`.
|
||||
--- @return boolean reached_end Whether or not the end of the table was reached on this iteration.
|
||||
function flib_table.for_n_of(tbl, from_k, n, callback, _next)
|
||||
-- Bypass if a custom `next` function was provided
|
||||
if not _next then
|
||||
-- Verify start key exists, else start from scratch
|
||||
if from_k and not tbl[from_k] then
|
||||
from_k = nil
|
||||
end
|
||||
-- Use default `next`
|
||||
_next = next
|
||||
end
|
||||
|
||||
local delete
|
||||
local prev
|
||||
local abort
|
||||
local result = {}
|
||||
|
||||
-- Run `n` times
|
||||
for _ = 1, n, 1 do
|
||||
local v
|
||||
if not delete then
|
||||
prev = from_k
|
||||
end
|
||||
from_k, v = _next(tbl, from_k)
|
||||
if delete then
|
||||
tbl[delete] = nil
|
||||
end
|
||||
|
||||
if from_k then
|
||||
result[from_k], delete, abort = callback(v, from_k)
|
||||
if delete then
|
||||
delete = from_k
|
||||
end
|
||||
if abort then
|
||||
break
|
||||
end
|
||||
else
|
||||
return from_k, result, true
|
||||
end
|
||||
end
|
||||
|
||||
if delete then
|
||||
tbl[delete] = nil
|
||||
from_k = prev
|
||||
elseif abort then
|
||||
from_k = prev
|
||||
end
|
||||
return from_k, result, false
|
||||
end
|
||||
|
||||
-- TODO: Remove array_insert, create separate array_filter function
|
||||
|
||||
--- Create a filtered version of a table based on the results of a filter function.
|
||||
---
|
||||
--- Calls `filter(value, key)` on each element in the table, returning a new table with only pairs for which
|
||||
--- `filter` returned a truthy value.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {1, 2, 3, 4, 5, 6}
|
||||
--- local just_evens = table.filter(tbl, function(v) return v % 2 == 0 end) -- {[2] = 2, [4] = 4, [6] = 6}
|
||||
--- local just_evens_arr = table.filter(tbl, function(v) return v % 2 == 0 end, true) -- {2, 4, 6}
|
||||
--- ```
|
||||
--- @generic K, V
|
||||
--- @param tbl table<K, V>
|
||||
--- @param filter fun(value: V, key: K): boolean
|
||||
--- @param array_insert boolean? If true, the result will be constructed as an array of values that matched the filter. Key references will be lost.
|
||||
--- @return table<K, V>
|
||||
function flib_table.filter(tbl, filter, array_insert)
|
||||
local output = {}
|
||||
local i = 0
|
||||
for k, v in pairs(tbl) do
|
||||
if filter(v, k) then
|
||||
if array_insert then
|
||||
i = i + 1
|
||||
output[i] = v
|
||||
else
|
||||
output[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- Retrieve the value at the key, or insert the default value.
|
||||
--- @generic K, V
|
||||
--- @param table table<K, V>
|
||||
--- @param key K
|
||||
--- @param default_value V
|
||||
--- @return V
|
||||
function flib_table.get_or_insert(table, key, default_value)
|
||||
local value = table[key]
|
||||
if not value then
|
||||
table[key] = default_value
|
||||
return default_value
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
--- Invert the given table such that `[value] = key`, returning a new table.
|
||||
---
|
||||
--- Non-unique values are overwritten based on the ordering from `pairs()`.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {"foo", "bar", "baz", set = "baz"}
|
||||
--- local inverted = table.invert(tbl) -- {foo = 1, bar = 2, baz = "set"}
|
||||
--- ```
|
||||
--- @generic K, V
|
||||
--- @param tbl table<K, V>
|
||||
--- @return table<V, K>
|
||||
function flib_table.invert(tbl)
|
||||
local inverted = {}
|
||||
for k, v in pairs(tbl) do
|
||||
inverted[v] = k
|
||||
end
|
||||
return inverted
|
||||
end
|
||||
|
||||
--- Create a transformed table using the output of a mapper function.
|
||||
---
|
||||
--- Calls `mapper(value, key)` on each element in the table, using the return as the new value for the key.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {1, 2, 3, 4, 5}
|
||||
--- local tbl_times_ten = table.map(tbl, function(v) return v * 10 end) -- {10, 20, 30, 40, 50}
|
||||
--- ```
|
||||
--- @generic K, V, N
|
||||
--- @param tbl table<K, V>
|
||||
--- @param mapper fun(value: V, key: V):N?
|
||||
--- @return table<K, N>
|
||||
function flib_table.map(tbl, mapper)
|
||||
local output = {}
|
||||
for k, v in pairs(tbl) do
|
||||
output[k] = mapper(v, k)
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
local function default_comp(a, b)
|
||||
return a < b
|
||||
end
|
||||
|
||||
--- Partially sort an array.
|
||||
---
|
||||
--- This function utilizes [insertion sort](https://en.wikipedia.org/wiki/Insertion_sort), which is _extremely_ inefficient with large data sets. However, you can spread the sorting over multiple ticks, reducing the performance impact. Only use this function if `table.sort` is too slow.
|
||||
--- @generic V
|
||||
--- @param arr Array<V>
|
||||
--- @param from_index number? The index to start iteration at (inclusive). Pass `nil` or a number less than `2` to begin at the start of the array.
|
||||
--- @param iterations number The number of iterations to perform. Higher is more performance-heavy. This number should be adjusted based on the performance impact of the custom `comp` function (if any) and the size of the array.
|
||||
--- @param comp fun(a: V, b: V) A comparison function for sorting. Must return truthy if `a < b`.
|
||||
--- @return number? next_index The index to start the next iteration at, or `nil` if the end was reached.
|
||||
function flib_table.partial_sort(arr, from_index, iterations, comp)
|
||||
comp = comp or default_comp
|
||||
local start_index = (from_index and from_index > 2) and from_index or 2
|
||||
local end_index = start_index + (iterations - 1)
|
||||
|
||||
for j = start_index, end_index do
|
||||
local key = arr[j]
|
||||
if not key then
|
||||
return nil
|
||||
end
|
||||
local i = j - 1
|
||||
|
||||
while i > 0 and comp(key, arr[i]) do
|
||||
arr[i + 1] = arr[i]
|
||||
i = i - 1
|
||||
end
|
||||
|
||||
arr[i + 1] = key
|
||||
end
|
||||
|
||||
return end_index + 1
|
||||
end
|
||||
|
||||
--- "Reduce" a table's values into a single output value, using the results of a reducer function.
|
||||
---
|
||||
--- Calls `reducer(accumulator, value, key)` on each element in the table, returning a single accumulated output value.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local tbl = {10, 20, 30, 40, 50}
|
||||
--- local sum = table.reduce(tbl, function(acc, v) return acc + v end)
|
||||
--- local sum_minus_ten = table.reduce(tbl, function(acc, v) return acc + v end, -10)
|
||||
--- ```
|
||||
--- @generic K, V, R
|
||||
--- @param tbl table<K, V>
|
||||
--- @param reducer fun(acc: R, value: V, key: K):R
|
||||
--- @param initial_value R? The initial value for the accumulator. If not provided or is falsy, the first value in the table will be used as the initial `accumulator` value and skipped as `key`. Calling `reduce()` on an empty table without an `initial_value` will cause a crash.
|
||||
--- @return R
|
||||
function flib_table.reduce(tbl, reducer, initial_value)
|
||||
local accumulator = initial_value
|
||||
for key, value in pairs(tbl) do
|
||||
if accumulator then
|
||||
accumulator = reducer(accumulator, value, key)
|
||||
else
|
||||
accumulator = value
|
||||
end
|
||||
end
|
||||
return accumulator
|
||||
end
|
||||
|
||||
--- @deprecated use `table.remove`.
|
||||
flib_table.retrieve = flib_table.remove
|
||||
|
||||
--- Shallowly copy the contents of a table into a new table.
|
||||
---
|
||||
--- The parent table will have a new table reference, but any subtables within it will still have the same table
|
||||
--- reference.
|
||||
---
|
||||
--- Does not copy metatables.
|
||||
--- @generic T
|
||||
--- @param tbl T
|
||||
--- @param use_rawset boolean? Use rawset to set the values (ignores metamethods).
|
||||
--- @return T The copied table.
|
||||
function flib_table.shallow_copy(tbl, use_rawset)
|
||||
local output = {}
|
||||
for k, v in pairs(tbl) do
|
||||
if use_rawset then
|
||||
rawset(output, k, v)
|
||||
else
|
||||
output[k] = v
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- Shallowly merge two or more tables.
|
||||
--- Unlike `table.deep_merge`, this will only combine the top level of the tables.
|
||||
--- @param tables table[]
|
||||
--- @return table
|
||||
function flib_table.shallow_merge(tables)
|
||||
local output = {}
|
||||
for _, tbl in pairs(tables) do
|
||||
for key, value in pairs(tbl) do
|
||||
output[key] = value
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- Retrieve the size of a table.
|
||||
---
|
||||
--- Uses Factorio's built-in `table_size` function.
|
||||
--- @type fun(tbl: table):number
|
||||
flib_table.size = _ENV.table_size
|
||||
|
||||
--- Retrieve a shallow copy of a portion of an array, selected from `start` to `end` inclusive.
|
||||
---
|
||||
--- The original array **will not** be modified.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local arr = {10, 20, 30, 40, 50, 60, 70, 80, 90}
|
||||
--- local sliced = table.slice(arr, 3, 7) -- {30, 40, 50, 60, 70}
|
||||
--- log(serpent.line(arr)) -- {10, 20, 30, 40, 50, 60, 70, 80, 90} (unchanged)
|
||||
--- ```
|
||||
--- @generic V
|
||||
--- @param arr Array<V>
|
||||
--- @param start number? default: `1`
|
||||
--- @param stop number? Stop at this index. If zero or negative, will stop `n` items from the end of the array (default: `#arr`).
|
||||
--- @return Array<V> A new array with the copied values.
|
||||
function flib_table.slice(arr, start, stop)
|
||||
local output = {}
|
||||
local n = #arr
|
||||
|
||||
start = start or 1
|
||||
stop = stop or n
|
||||
stop = stop <= 0 and (n + stop) or stop
|
||||
|
||||
if start < 1 or start > n then
|
||||
return {}
|
||||
end
|
||||
|
||||
local k = 1
|
||||
for i = start, stop do
|
||||
output[k] = arr[i]
|
||||
k = k + 1
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- Extract a portion of an array, selected from `start` to `end` inclusive.
|
||||
--
|
||||
--- The original array **will** be modified.
|
||||
---
|
||||
--- # Examples
|
||||
---
|
||||
--- ```lua
|
||||
--- local arr = {10, 20, 30, 40, 50, 60, 70, 80, 90}
|
||||
--- local spliced = table.splice(arr, 3, 7) -- {30, 40, 50, 60, 70}
|
||||
--- log(serpent.line(arr)) -- {10, 20, 80, 90} (values were removed)
|
||||
--- ```
|
||||
--- @generic V
|
||||
--- @param arr Array<V>
|
||||
--- @param start number default: `1`
|
||||
--- @param stop number? Stop at this index. If zero or negative, will stop `n` items from the end of the array (default: `#arr`).
|
||||
--- @return Array<V> A new array with the extracted values.
|
||||
function flib_table.splice(arr, start, stop)
|
||||
local output = {}
|
||||
local n = #arr
|
||||
|
||||
start = start or 1
|
||||
stop = stop or n
|
||||
stop = stop <= 0 and (n + stop) or stop
|
||||
|
||||
if start < 1 or start > n then
|
||||
return {}
|
||||
end
|
||||
|
||||
local k = 1
|
||||
for _ = start, stop do
|
||||
output[k] = table.remove(arr, start)
|
||||
k = k + 1
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
--- @class Array<T>: { [integer]: T }
|
||||
|
||||
return flib_table
|
||||
BIN
.vscode/flib/thumbnail.png
vendored
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
130
.vscode/flib/train.lua
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
--- Functions for working with trains.
|
||||
--- @class flib_train
|
||||
local flib_train = {}
|
||||
|
||||
local table = table
|
||||
|
||||
--- Get the main locomotive in a given train.
|
||||
--- @param train LuaTrain
|
||||
--- @return LuaEntity? locomotive The primary locomotive entity or `nil` when no locomotive was found
|
||||
function flib_train.get_main_locomotive(train)
|
||||
if
|
||||
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
|
||||
|
||||
--- Get the backer_name of the main locomotive in a given train.
|
||||
--- @param train LuaTrain
|
||||
--- @return string? backer_name The backer_name of the primary locomotive or `nil` when no locomotive was found
|
||||
function flib_train.get_backer_name(train)
|
||||
local loco = flib_train.get_main_locomotive(train)
|
||||
return loco and loco.backer_name
|
||||
end
|
||||
|
||||
--- Rotate a single carriage of a train.
|
||||
--- @param entity LuaEntity
|
||||
--- @return boolean rotated `true` when rotation was successful.
|
||||
function flib_train.rotate_carriage(entity)
|
||||
local disconnected_back = entity.disconnect_rolling_stock(defines.rail_direction.back)
|
||||
local disconnected_front = entity.disconnect_rolling_stock(defines.rail_direction.front)
|
||||
entity.rotate()
|
||||
-- Only reconnect the side that was disconnected
|
||||
local reconnected_front = disconnected_front
|
||||
local reconnected_back = disconnected_back
|
||||
if disconnected_back then
|
||||
reconnected_back = entity.connect_rolling_stock(defines.rail_direction.front)
|
||||
end
|
||||
if disconnected_front then
|
||||
reconnected_front = entity.connect_rolling_stock(defines.rail_direction.back)
|
||||
end
|
||||
|
||||
if disconnected_front and not reconnected_front then
|
||||
return false
|
||||
end
|
||||
if disconnected_back and not reconnected_back then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Create a string representing train composition, and return a count of locomotives and wagons in the train.
|
||||
--- - `<L<`, `>L>`: Locomotives
|
||||
--- - `C`: Cargo wagon
|
||||
--- - `F`: Fluid wagon
|
||||
--- - `A`: Artillery wagon
|
||||
--- @param train LuaTrain
|
||||
--- @return string? composition The composition string, or `nil` if the train was invalid.
|
||||
--- @return TrainCompositionCounts?
|
||||
function flib_train.get_composition_string(train)
|
||||
if train and train.valid then
|
||||
local carriages = train.carriages
|
||||
local string_table = {}
|
||||
local count_wagons, count_loco_front, count_loco_back, i = 0, 0, 0, 0
|
||||
local locos_front = train.locomotives.front_movers
|
||||
for _, carriage in pairs(carriages) do
|
||||
i = i + 1
|
||||
if carriage.type == "locomotive" then
|
||||
local faces_forward = false
|
||||
for _, loco in pairs(locos_front) do
|
||||
if carriage.unit_number == loco.unit_number then
|
||||
faces_forward = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if faces_forward then
|
||||
string_table[i] = "<L<"
|
||||
count_loco_front = count_loco_front + 1
|
||||
else
|
||||
string_table[i] = ">L>"
|
||||
count_loco_back = count_loco_back + 1
|
||||
end
|
||||
elseif carriage.type == "cargo-wagon" then
|
||||
count_wagons = count_wagons + 1
|
||||
string_table[i] = "C"
|
||||
elseif carriage.type == "fluid-wagon" then
|
||||
count_wagons = count_wagons + 1
|
||||
string_table[i] = "F"
|
||||
elseif carriage.type == "artillery-wagon" then
|
||||
count_wagons = count_wagons + 1
|
||||
string_table[i] = "A"
|
||||
else
|
||||
count_wagons = count_wagons + 1
|
||||
string_table[i] = "?"
|
||||
end
|
||||
end
|
||||
return table.concat(string_table),
|
||||
{
|
||||
total = i,
|
||||
wagons = count_wagons,
|
||||
front_movers = count_loco_front,
|
||||
back_movers = count_loco_back,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Open train GUI for one player.
|
||||
--- @param player_index number
|
||||
--- @param train LuaTrain
|
||||
--- @return boolean `true` if the GUI was opened.
|
||||
function flib_train.open_gui(player_index, train)
|
||||
if train and train.valid and game.players[player_index] then
|
||||
local loco = flib_train.get_main_locomotive(train)
|
||||
if loco and loco.valid then
|
||||
game.players[player_index].opened = loco
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return flib_train
|
||||
|
||||
--- @class TrainCompositionCounts
|
||||
--- @field total number The total number of rolling stocks in the train.
|
||||
--- @field wagons number The number of wagons in the train.
|
||||
--- @field front_movers number The number of front-facing locomotives in the train.
|
||||
--- @field back_movers number The number of back-facing locomotives in the train.
|
||||
220
.vscode/flib/translation.lua
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
--- @diagnostic disable
|
||||
--- @deprecated use `dictionary` instead
|
||||
local flib_translation = {}
|
||||
|
||||
local table = require("__flib__/table")
|
||||
|
||||
local math = math
|
||||
local next = next
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.init()
|
||||
if not global.__flib then
|
||||
global.__flib = {}
|
||||
end
|
||||
global.__flib.translation = {
|
||||
players = {},
|
||||
translating_players_count = 0,
|
||||
}
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.iterate_batch(event_data)
|
||||
local __translation = global.__flib.translation
|
||||
if __translation.translating_players_count == 0 then
|
||||
return
|
||||
end
|
||||
local iterations = math.ceil(5 / __translation.translating_players_count)
|
||||
if iterations < 1 then
|
||||
iterations = 1
|
||||
end
|
||||
local current_tick = event_data.tick
|
||||
|
||||
for player_index, player_table in pairs(__translation.players) do
|
||||
local player = game.get_player(player_index)
|
||||
if player.connected then
|
||||
local request_translation = player.request_translation
|
||||
local i = 0
|
||||
local sort_data = player_table.sort
|
||||
local sort_strings = sort_data and sort_data.strings
|
||||
local translate_data = player_table.translate
|
||||
local translate_strings = translate_data.strings
|
||||
while i < iterations do
|
||||
if player_table.state == "sort" then
|
||||
local string_index = sort_data.next_index
|
||||
local string_data = sort_strings[string_index]
|
||||
if string_data then
|
||||
i = i + 1
|
||||
local serialised = flib_translation.serialise_localised_string(string_data.localised)
|
||||
local translation_data = translate_strings[serialised]
|
||||
if translation_data then
|
||||
local dictionary_names = translation_data.names[string_data.dictionary]
|
||||
if dictionary_names then
|
||||
dictionary_names[#dictionary_names + 1] = string_data.internal
|
||||
else
|
||||
translation_data.names[string_data.dictionary] = { string_data.internal }
|
||||
end
|
||||
else
|
||||
translate_strings[serialised] = {
|
||||
string = string_data.localised,
|
||||
names = { [string_data.dictionary] = { string_data.internal } },
|
||||
}
|
||||
translate_strings.__size = translate_strings.__size + 1
|
||||
end
|
||||
sort_data.next_index = string_index + 1
|
||||
sort_strings[string_index] = nil
|
||||
else
|
||||
player_table.state = "translate"
|
||||
player_table.sort = nil
|
||||
player_table.translate.next_key = next(player_table.translate.strings, "__size")
|
||||
end
|
||||
elseif player_table.state == "translate" then
|
||||
local current_key = translate_data.next_key
|
||||
local translation_data = translate_strings[current_key]
|
||||
if translation_data then
|
||||
i = i + 1
|
||||
request_translation(translation_data.string)
|
||||
translate_data.next_key = next(translate_strings, current_key)
|
||||
else
|
||||
player_table.state = "wait"
|
||||
player_table.translate.current_key = nil
|
||||
end
|
||||
elseif player_table.state == "wait" then
|
||||
local wait_tick = player_table.wait_tick
|
||||
if wait_tick then
|
||||
if wait_tick <= current_tick then
|
||||
-- if this player is still being iterated at this point, there are some unreceived translations
|
||||
-- see https://forums.factorio.com/84570
|
||||
player_table.state = "translate"
|
||||
player_table.translate.next_key = next(translate_strings, "__size")
|
||||
end
|
||||
else
|
||||
player_table.wait_tick = current_tick + 20
|
||||
end
|
||||
break -- only needs to run once per player per tick
|
||||
end
|
||||
end
|
||||
else
|
||||
flib_translation.cancel(player_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.process_result(event_data)
|
||||
local __translation = global.__flib.translation
|
||||
if __translation.translating_players_count == 0 then
|
||||
return
|
||||
end
|
||||
local player_table = __translation.players[event_data.player_index]
|
||||
if not player_table then
|
||||
return
|
||||
end
|
||||
if player_table.state == "sort" then
|
||||
return
|
||||
end
|
||||
|
||||
local serialised = flib_translation.serialise_localised_string(event_data.localised_string)
|
||||
local translate_strings = player_table.translate.strings
|
||||
|
||||
local translation_data = translate_strings[serialised]
|
||||
if translation_data then
|
||||
local names = translation_data.names
|
||||
translate_strings[serialised] = nil
|
||||
translate_strings.__size = translate_strings.__size - 1
|
||||
local finished = false
|
||||
if translate_strings.__size == 0 then
|
||||
flib_translation.cancel(event_data.player_index)
|
||||
finished = true
|
||||
end
|
||||
return names, finished
|
||||
end
|
||||
return nil, false
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.add_requests(player_index, strings)
|
||||
local __translation = global.__flib.translation
|
||||
local player_table = __translation.players[player_index]
|
||||
if player_table then
|
||||
player_table.state = "sort"
|
||||
if player_table.sort then
|
||||
local strings_to_sort = player_table.sort.strings
|
||||
for i = 1, #strings do
|
||||
strings_to_sort[#strings_to_sort + 1] = strings[i]
|
||||
end
|
||||
player_table.sort.next_index = 1
|
||||
else
|
||||
player_table.sort = {
|
||||
strings = table.shallow_copy(strings),
|
||||
next_index = 1,
|
||||
}
|
||||
end
|
||||
player_table.translate.next_key = nil
|
||||
else
|
||||
__translation.players[player_index] = {
|
||||
state = "sort",
|
||||
-- sort
|
||||
sort = {
|
||||
strings = table.shallow_copy(strings),
|
||||
next_index = 1,
|
||||
},
|
||||
-- translate
|
||||
translate = {
|
||||
strings = { __size = 0 },
|
||||
next_key = nil,
|
||||
},
|
||||
-- wait
|
||||
wait_tick = nil,
|
||||
}
|
||||
__translation.translating_players_count = __translation.translating_players_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.cancel(player_index)
|
||||
local __translation = global.__flib.translation
|
||||
local player_table = __translation.players[player_index]
|
||||
if not player_table then
|
||||
log("Tried to cancel translations for player [" .. player_index .. "] when no translations were running!")
|
||||
return
|
||||
end
|
||||
__translation.players[player_index] = nil
|
||||
__translation.translating_players_count = __translation.translating_players_count - 1
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.is_translating(player_index)
|
||||
return global.__flib.translation.players[player_index] and true or false
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.translating_players_count()
|
||||
return global.__flib.translation.translating_players_count
|
||||
end
|
||||
|
||||
--- @deprecated use `dictionary` instead
|
||||
function flib_translation.serialise_localised_string(localised_string)
|
||||
if type(localised_string) == "string" then
|
||||
return localised_string
|
||||
end
|
||||
local output = "{"
|
||||
local first = true
|
||||
for _, v in pairs(localised_string) do
|
||||
if not first then
|
||||
output = output .. ","
|
||||
end
|
||||
if type(v) == "table" then
|
||||
output = output .. flib_translation.serialise_localised_string(v)
|
||||
else
|
||||
output = output .. '"' .. tostring(v) .. '"'
|
||||
end
|
||||
first = false
|
||||
end
|
||||
output = output .. "}"
|
||||
return output
|
||||
end
|
||||
|
||||
return flib_translation
|
||||
66
.vscode/settings.json
vendored
@@ -1,30 +1,40 @@
|
||||
{
|
||||
"factorio.package.autoCommitAuthor": "Mami",
|
||||
"factorio.versions": [
|
||||
{
|
||||
"name": "Steam",
|
||||
"factorioPath": "~/.steam/steam/steamapps/common/Factorio/bin/x64/factorio",
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"mods",
|
||||
"table_size",
|
||||
"log",
|
||||
"localised_print",
|
||||
"serpent",
|
||||
"global",
|
||||
"__DebugAdapter",
|
||||
"__Profiler"
|
||||
],
|
||||
"Lua.runtime.version": "Lua 5.2",
|
||||
"Lua.workspace.library": [
|
||||
"~/.steam/steam/steamapps/common/Factorio/data",
|
||||
"~/.steam/steam/steamapps/common/Factorio/data/core/lualib",
|
||||
"./.vscode/factorio"
|
||||
],
|
||||
"Lua.workspace.userThirdParty": [
|
||||
"/home/mami/.config/Code/User/workspaceStorage/9536dbf0665a54126a4b0958ecd5829f/justarandomgeek.factoriomod-debug/sumneko-3rd",
|
||||
"/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd"
|
||||
]
|
||||
"factorio.package.autoCommitAuthor": "Mami",
|
||||
"factorio.versions": [
|
||||
{
|
||||
"name": "steam",
|
||||
"factorioPath": "/home/mami/.steam/steam/steamapps/common/Factorio/bin/x64/factorio",
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"Lua.runtime.version": "Lua 5.2",
|
||||
"Lua.workspace.library": [
|
||||
"~/.steam/steam/steamapps/common/Factorio/data",
|
||||
"~/.steam/steam/steamapps/common/Factorio/data/core/lualib",
|
||||
"./.vscode/factorio",
|
||||
"./.vscode/flib",
|
||||
"/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/library"
|
||||
],
|
||||
"Lua.workspace.userThirdParty": [
|
||||
"/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd",
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"__DebugAdapter",
|
||||
"__Profiler"
|
||||
],
|
||||
"Lua.runtime.builtin": {
|
||||
"io": "disable",
|
||||
"os": "disable",
|
||||
"math": "disable",
|
||||
"debug": "disable",
|
||||
"coroutine": "disable",
|
||||
"package": "disable"
|
||||
},
|
||||
"Lua.runtime.special": {
|
||||
"__object_name": "type"
|
||||
},
|
||||
"Lua.runtime.plugin": "/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/plugin.lua",
|
||||
"Lua.diagnostics.disable": [
|
||||
"lowercase-global"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Behold one of the most feature-rich and performant train logistics network mods Factorio has to offer. Named for [Project Cybersyn](https://en.wikipedia.org//wiki/Project_Cybersyn) of Allende's Chile, with just this mod you can coordinate the economic inputs and outputs of your entire megabase. Similar in functionality to the famous Logistics Train Network mod, but with a much broader scope.
|
||||
|
||||
[Join our discord!](https://discord.gg/zmgUBu95HX) This is the preferred place for players to report bugs, make suggestions, or keep up with development.
|
||||
|
||||

|
||||
|
||||
## Quick Start Guide
|
||||
@@ -23,7 +25,7 @@ Follow the above directions and you have set up a bare minimum Cybersyn network!
|
||||
|
||||

|
||||
|
||||
**Intuitive and easy to learn**, without sacrificing features. Eases the player into building a train logistics network using parallels between the robot logistics network. Configure your stations using just 3 virtual signals, a couple of combinator settings and the train stop's own train limit.
|
||||
**Minimalistic and intuitively designed**, without sacrificing features. Configure your stations using just 3 virtual signals, a couple of combinator settings and the train stop's own train limit.
|
||||
|
||||
### A whole suite of new and optional circuit network inputs and outputs to control your stations precisely
|
||||
* Natively read out all deliveries currently in progress for a station, not just the loading or unloading orders of the parked train.
|
||||
@@ -119,10 +121,12 @@ A network can be set to the "each" virtual signal. When in this mode, each virtu
|
||||
|
||||

|
||||
|
||||
If a station combinator receives a request threshold signal as input, a request order for the station will only be generated if the station is requesting a number of items exceeding the request threshold. In addition, there must be a station in the network which is providing at least as many items as the request threshold, and there must be a train in the network that has cargo capacity exceeding the request threshold. Through this logic all generated orders must be for a number of items greater than or equal to the request threshold. By setting high thresholds, the traffic on your network can be greatly reduced, at the cost of needing to maintain larger item buffers at each station. There is no "provide threshold" in this mod because by design there is almost no need for one. If desired a provide threshold can be simulated with a single decider combinator. The request threshold signal sets the request threshold "per-station" whereas the station control combinator can set or override the threshold per-item as well.
|
||||
If a station combinator receives a request threshold signal as input, a request order for the station will only be generated if the station is requesting a number of items exceeding the request threshold. In addition, there must be a station in the network which is providing at least as many items as the request threshold, and there must be a train in the network that has cargo capacity exceeding the request threshold. Therefore all generated orders must be for a number of items greater than or equal to the request threshold. By setting high thresholds, the traffic on your network can be greatly reduced, at the cost of needing to maintain larger item buffers at each station. The request threshold signal sets the request threshold "per-station" whereas the station control combinator can set or override the threshold per-item as well.
|
||||
|
||||
On station combinators there is a setting called "Stack thresholds". When set, any request threshold for this station will be multiplied by the stack size of any item it is being compared to. This applies to station control thresholds as well. Thus the request threshold can be specified based on total stack count rather than total item count. Fluids are unaffected by the "Stack thresholds" setting, they are always specified by total fluid count.
|
||||
|
||||
There is no "provide threshold" in this mod because by design there is no need for one. Because the request threshold is enforced on the provider station as well as the requester, it pulls double duty as both the request and provide threshold.
|
||||
|
||||
### Locked slots per cargo wagon
|
||||
|
||||
After an order has been generated, enough items will be subtracted from that order to ensure at least X number of slots in each cargo wagon can be left empty, where X is the "Locked slots per cargo wagon" signal being received by the station combinator. It is necessary for multi-item stations to function.
|
||||
|
||||
5
TODO
@@ -1,4 +1,5 @@
|
||||
bugs:
|
||||
https://github.com/mamoniot/project-cybersyn/issues/26
|
||||
https://mods.factorio.com/mod/cybersyn/discussion/63afff871c9e44028b2acab8
|
||||
https://mods.factorio.com/mod/cybersyn/discussion/63af6ed2f532e8870d639157
|
||||
|
||||
@@ -12,8 +13,10 @@ major:
|
||||
item count of available provides
|
||||
system log
|
||||
check out LTN addons that do this for inspiration
|
||||
per-station statistics gui
|
||||
move to an event based algorithm
|
||||
models & art
|
||||
dramatically improve the mods handling of invalid entities
|
||||
|
||||
minor:
|
||||
check if necessary entities can be destroyed without raising events
|
||||
@@ -24,9 +27,11 @@ minor:
|
||||
do not play close sound when a different gui is opened
|
||||
gui can desync if settings are changed outside of it while it is open
|
||||
give the combinators a sensible reaction to low or no power
|
||||
depot alerts when misconfigured
|
||||
|
||||
compat:
|
||||
train upgrader mod
|
||||
deadlocks signals
|
||||
railloader
|
||||
picker dollies?
|
||||
cargo ships
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 1.2.8
|
||||
Date: 2022-1-5
|
||||
Features:
|
||||
- Improved placeholder cybernetic combinator art
|
||||
- Added a wagon control setting to bar unfiltered slots in adjacent cargo wagons
|
||||
- Added a setting and keybind for toggling on or off the central planner
|
||||
Changes:
|
||||
- Sped up the rate at which copy-paste by blueprint will be noticed
|
||||
Bugfixes:
|
||||
- Fixed a bug with combinators sometimes failing to connect with train stops
|
||||
- Fixed wagon control combinators outputting wagon contents after inserters have already taken out items
|
||||
- Fixed a rare crash on world migration
|
||||
Scripting:
|
||||
- Added missing return values to some interface functions
|
||||
- Migrated to non-deprecated flib modules
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Version: 1.2.7
|
||||
Date: 2022-1-1
|
||||
Bugfixes:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
--By Mami
|
||||
--<goto p_continue> at line 310 jumps into the scope of local 'p_flag'
|
||||
require("scripts.constants")
|
||||
require("scripts.global")
|
||||
require("scripts.lib")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
--Credit to modo-lv for submitting the following code
|
||||
if mods["nullius"] then
|
||||
-- Place combinator in the same subgroup as the regular train stop
|
||||
data.raw["recipe"][COMBINATOR_NAME].subgroup = data.raw["train-stop"]["train-stop"].subgroup
|
||||
|
||||
@@ -21,4 +21,10 @@ data:extend({
|
||||
missing_train_icon,
|
||||
lost_train_icon,
|
||||
nonempty_train_icon,
|
||||
{
|
||||
type = "custom-input",
|
||||
name = "cybersyn-toggle-planner",
|
||||
key_sequence = "",
|
||||
consuming = "game-only"
|
||||
}
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 84 KiB |