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
This commit is contained in:
Monica Moniot
2023-01-06 19:24:24 -05:00
committed by GitHub
parent 9014c834f2
commit b144a06f1a
115 changed files with 6013 additions and 474 deletions

21
.vscode/flib/LICENSE vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
require("prototypes/sprite")
require("prototypes/style")

476
.vscode/flib/dictionary-lite.lua vendored Normal file
View 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
View 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
View 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

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

115
.vscode/flib/docs/gui-styles.md vendored Normal file
View 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:
![](https://raw.githubusercontent.com/factoriolib/flib/master/docs/assets/slot-style-examples.png)
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
View File

@@ -0,0 +1,9 @@
The following are sprites provided by flib for your use:
## Indicator sprites
![](https://raw.githubusercontent.com/factoriolib/flib/master/docs/assets/indicator-examples.png)
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
View 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
View 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
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
.vscode/flib/graphics/empty.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
.vscode/flib/graphics/indicators.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
.vscode/flib/graphics/planner.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
.vscode/flib/graphics/slots.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
.vscode/flib/graphics/slots.xcf vendored Normal file
View File

Binary file not shown.

BIN
.vscode/flib/graphics/subheader-line.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

2
.vscode/flib/gui-beta.lua vendored Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
locale-identifier=af
locale-name=Afrikaans

3
.vscode/flib/locale/ar/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=ar
locale-name=العَرَبِيَّة

2
.vscode/flib/locale/be/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=be
locale-name=Беларуская

2
.vscode/flib/locale/bg/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=bg
locale-name=български език

3
.vscode/flib/locale/ca/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=ca
locale-name=Català

3
.vscode/flib/locale/cs/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=cs
locale-name=Čeština

3
.vscode/flib/locale/da/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=da
locale-name=Dansk

3
.vscode/flib/locale/de/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=de
locale-name=Deutsch

10
.vscode/flib/locale/de/flib.cfg vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
locale-identifier=el
locale-name=Ελληνικά

2
.vscode/flib/locale/en/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=en
locale-name=English

11
.vscode/flib/locale/en/flib.cfg vendored Normal file
View 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
View File

@@ -0,0 +1,2 @@
locale-identifier=eo
locale-name=Esperanto

View File

@@ -0,0 +1,3 @@
locale-identifier=es-ES
locale-name=Español

2
.vscode/flib/locale/et/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=et
locale-name=Eesti

3
.vscode/flib/locale/fi/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=fi
locale-name=Suomi

3
.vscode/flib/locale/fr/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=fr
locale-name=Français

View File

@@ -0,0 +1,2 @@
locale-identifier=fy-NL
locale-name=Frisian

View File

@@ -0,0 +1,2 @@
locale-identifier=ga-IE
locale-name=Gaeilge

3
.vscode/flib/locale/he/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=he
locale-name=עברית

2
.vscode/flib/locale/hr/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=hr
locale-name=Hrvatski

3
.vscode/flib/locale/hu/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=hu
locale-name=Magyar

2
.vscode/flib/locale/id/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=id
locale-name=Bahasa Indonesia

3
.vscode/flib/locale/it/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=it
locale-name=Italiano

3
.vscode/flib/locale/ja/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=ja
locale-name=日本語

3
.vscode/flib/locale/ko/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=ko
locale-name=한국어

2
.vscode/flib/locale/lt/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=lt
locale-name=Lietuvių

2
.vscode/flib/locale/lv/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=lv
locale-name=Latviešu

3
.vscode/flib/locale/nl/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=nl
locale-name=Nederlands

3
.vscode/flib/locale/no/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=no
locale-name=Norsk

3
.vscode/flib/locale/pl/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=pl
locale-name=Polski

View File

@@ -0,0 +1,2 @@
locale-identifier=pt-BR
locale-name=Português, Brasil

View File

@@ -0,0 +1,3 @@
locale-identifier=pt-PT
locale-name=Português

3
.vscode/flib/locale/ro/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=ro
locale-name=Română

3
.vscode/flib/locale/ru/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=ru
locale-name=Русский

2
.vscode/flib/locale/sk/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=sk
locale-name=Slovenčina

2
.vscode/flib/locale/sl/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=sl
locale-name=Slovenščina

2
.vscode/flib/locale/sq/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=sq
locale-name=Shqip

3
.vscode/flib/locale/sr/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=sr
locale-name=Српски

View File

@@ -0,0 +1,3 @@
locale-identifier=sv-SE
locale-name=Svenska

2
.vscode/flib/locale/th/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,2 @@
locale-identifier=th
locale-name=ภาษาไทย

3
.vscode/flib/locale/tr/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=tr
locale-name=Türkçe

3
.vscode/flib/locale/uk/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=uk
locale-name=Українська

3
.vscode/flib/locale/vi/dictionary.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
locale-identifier=vi
locale-name=Tiếng Việt Nam

View File

@@ -0,0 +1,3 @@
locale-identifier=zh-CN
locale-name=简体中文

View File

@@ -0,0 +1,3 @@
locale-identifier=zh-TW
locale-name=繁體中文

190
.vscode/flib/math.lua vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

130
.vscode/flib/train.lua vendored Normal file
View 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
View 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