├── .github └── ISSUE_TEMPLATE │ └── bug-report.yml ├── .gitignore ├── .kakrc ├── .mailmap ├── LICENSE ├── README.md ├── bounding-box.lua ├── changelog.txt ├── crowdin.yml ├── data-util.lua ├── data.lua ├── dictionary.lua ├── direction.lua ├── docs ├── assets │ ├── indicator-examples.png │ └── slot-style-examples.png ├── gui-styles.md ├── index.html ├── sprites.md └── style.css ├── format.lua ├── gen-docs.sh ├── graphics ├── black.png ├── dark-red-button.png ├── empty.png ├── frame-action-icons.png ├── indicators.png ├── nav-backward-black.png ├── nav-backward-disabled.png ├── nav-backward-white.png ├── nav-forward-black.png ├── nav-forward-disabled.png ├── nav-forward-white.png ├── planner.png ├── slots.png ├── slots.xcf ├── subheader-line.png ├── technology-slots.aseprite └── technology-slots.png ├── gui-templates.lua ├── gui.lua ├── info.json ├── locale.lua ├── locale ├── af │ ├── dictionary.cfg │ └── flib.cfg ├── ar │ ├── dictionary.cfg │ └── flib.cfg ├── be │ └── dictionary.cfg ├── bg │ └── dictionary.cfg ├── ca │ ├── dictionary.cfg │ └── flib.cfg ├── cs │ ├── dictionary.cfg │ └── flib.cfg ├── da │ ├── dictionary.cfg │ └── flib.cfg ├── de │ ├── dictionary.cfg │ └── flib.cfg ├── el │ ├── dictionary.cfg │ └── flib.cfg ├── en │ ├── dictionary.cfg │ └── flib.cfg ├── eo │ └── dictionary.cfg ├── es-ES │ ├── dictionary.cfg │ └── flib.cfg ├── et │ ├── dictionary.cfg │ └── flib.cfg ├── fi │ ├── dictionary.cfg │ └── flib.cfg ├── fr │ ├── dictionary.cfg │ └── flib.cfg ├── fy-NL │ └── dictionary.cfg ├── ga-IE │ └── dictionary.cfg ├── he │ ├── dictionary.cfg │ └── flib.cfg ├── hr │ └── dictionary.cfg ├── hu │ ├── dictionary.cfg │ └── flib.cfg ├── id │ └── dictionary.cfg ├── it │ ├── dictionary.cfg │ └── flib.cfg ├── ja │ ├── dictionary.cfg │ └── flib.cfg ├── ko │ ├── dictionary.cfg │ └── flib.cfg ├── lt │ └── dictionary.cfg ├── lv │ └── dictionary.cfg ├── nl │ ├── dictionary.cfg │ └── flib.cfg ├── no │ ├── dictionary.cfg │ └── flib.cfg ├── pl │ ├── dictionary.cfg │ └── flib.cfg ├── pt-BR │ └── dictionary.cfg ├── pt-BZ │ ├── dictionary.cfg │ └── flib.cfg ├── pt-PT │ ├── dictionary.cfg │ └── flib.cfg ├── ro │ ├── dictionary.cfg │ └── flib.cfg ├── ru │ ├── dictionary.cfg │ └── flib.cfg ├── sk │ └── dictionary.cfg ├── sl │ └── dictionary.cfg ├── sq │ └── dictionary.cfg ├── sr │ ├── dictionary.cfg │ └── flib.cfg ├── sv-SE │ ├── dictionary.cfg │ └── flib.cfg ├── th │ └── dictionary.cfg ├── tr │ ├── dictionary.cfg │ └── flib.cfg ├── uk │ ├── dictionary.cfg │ └── flib.cfg ├── vi │ ├── dictionary.cfg │ └── flib.cfg ├── zh-CN │ ├── dictionary.cfg │ └── flib.cfg └── zh-TW │ ├── dictionary.cfg │ └── flib.cfg ├── math.lua ├── migration.lua ├── on-tick-n.lua ├── orientation.lua ├── position.lua ├── prototypes.lua ├── prototypes ├── sprite.lua ├── style.lua └── technology-slot-style.lua ├── queue.lua ├── reverse-defines.lua ├── stylua.toml ├── table.lua ├── technology.lua ├── tests ├── README.md ├── dictionary-lite.lua ├── factorio_luaunit.lua ├── gui-lite.lua ├── position.lua ├── slots.lua └── test_math.lua ├── thumbnail.png ├── train.lua └── typedefs.lua /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Description 7 | description: A brief description of the issue. Attach `factorio-current.log` by dragging the file into the area below. 8 | placeholder: | 9 | The mod EditorExtensions (2.0.0) caused an unrecoverable error... 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Reproduction 15 | description: Exact steps to reproduce the issue. Attach the save file used to reproduce the issue, or use a file sharing service. Alternatively, reproduce the issue on a fresh save file. 16 | placeholder: | 17 | 1. Load save file 18 | 2. Click on "..." 19 | 3. See error 20 | - type: markdown 21 | attributes: 22 | value: | 23 | **ISSUES MUST BE REPRODUCED ON THE LATEST VERSIONS OF FACTORIO AND ALL MODS.** 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | ./*.code-workspace 3 | *.code-workspace 4 | doc-html/ 5 | NOTES.txt 6 | workspace.html 7 | faketorio 8 | faketorio_path.lua 9 | 10 | # Compiled Lua sources 11 | luac.out 12 | 13 | # luarocks build files 14 | *.src.rock 15 | *.zip 16 | *.tar.gz 17 | 18 | # Object files 19 | *.o 20 | *.os 21 | *.ko 22 | *.obj 23 | *.elf 24 | 25 | # Precompiled Headers 26 | *.gch 27 | *.pch 28 | 29 | # Libraries 30 | *.lib 31 | *.a 32 | *.la 33 | *.lo 34 | *.def 35 | *.exp 36 | 37 | # Shared objects (inc. Windows DLLs) 38 | *.dll 39 | *.so 40 | *.so.* 41 | *.dylib 42 | 43 | # Executables 44 | *.exe 45 | *.out 46 | *.app 47 | *.i*86 48 | *.x86_64 49 | *.hex 50 | .luarc.json 51 | -------------------------------------------------------------------------------- /.kakrc: -------------------------------------------------------------------------------- 1 | hook global WinSetOption filetype=lua %{ 2 | hook window BufWritePre .* format 3 | } 4 | 5 | hook global WinSetOption filetype=(factorio-changelog|ini) %{ 6 | hook window BufWritePost .* spell 7 | } 8 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Caleb Heuer raiguard 2 | Caleb Heuer raiguard <3515394+raiguard@users.noreply.github.com> 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 raiguard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project has migrated to [Codeberg](https://codeberg.org/raiguard/flib). 2 | 3 | [![shield](https://img.shields.io/badge/Ko--fi-Donate%20-hotpink?logo=kofi&logoColor=white)](https://ko-fi.com/raiguard) 4 | [![shield](https://img.shields.io/badge/Crowdin-Translate-brightgreen)](https://crowdin.com/project/raiguards-factorio-mods) 5 | [![shield](https://img.shields.io/badge/dynamic/json?color=orange&label=Factorio&query=downloads_count&suffix=%20downloads&url=https%3A%2F%2Fmods.factorio.com%2Fapi%2Fmods%2Fflib)](https://mods.factorio.com/mod/flib) 6 | 7 | # Factorio Library 8 | 9 | The Factorio Library is a set of high-quality, commonly-used utilities for 10 | creating Factorio mods. 11 | 12 | ## Usage 13 | 14 | Download the latest release from the 15 | [mod portal](https://mods.factorio.com/mod/flib) unzip it, and put it in your 16 | mods directory. You can access libraries provided by flib with 17 | `require("__flib__.position")`, etc. 18 | 19 | Add the flib directory to your language server's library. We recommend 20 | installing the [Factorio modding 21 | toolkit](https://github.com/justarandomgeek/vscode-factoriomod-debug) and 22 | setting it up with the [Sumneko Lua language 23 | server](https://github.com/sumneko/lua-language-server) to get cross-mod 24 | autocomplete and type checking. 25 | 26 | You can view the online documentation 27 | [here](https://factoriolib.github.io/flib/index.html). This documentation is 28 | auto-generated by the Lua language server, and is heavily unpolished. It is 29 | recommended to use language server intellisense instead of the docs site 30 | wherever possible. 31 | 32 | ## Contributing 33 | 34 | Please use the [GitHub repository](https://github.com/factoriolib/flib) for 35 | questions, bug reports, or pull requests. 36 | 37 | For locale contributions, please use 38 | [Crowdin](https://crowdin.com/project/raiguards-factorio-mods). 39 | -------------------------------------------------------------------------------- /bounding-box.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.bounding-box" then 2 | return require("__flib__.bounding-box") 3 | end 4 | 5 | local flib_position = require("__flib__.position") 6 | 7 | --- Utilities for manipulating bounding boxes. All functions support both the shorthand and explicit syntaxes for boxes 8 | --- and positions, and will preserve the syntax that was passed in. Boxes are considered immutable; all functions will 9 | --- return new boxes. 10 | --- ```lua 11 | --- local flib_bounding_box = require("__flib__.bounding-box") 12 | --- ``` 13 | --- @class flib_bounding_box 14 | local flib_bounding_box = {} 15 | 16 | --- Return a new box expanded to the nearest tile edges. 17 | --- @param box BoundingBox 18 | --- @return BoundingBox 19 | function flib_bounding_box.ceil(box) 20 | if box.left_top then 21 | return { 22 | left_top = { x = math.floor(box.left_top.x), y = math.floor(box.left_top.y) }, 23 | right_bottom = { x = math.ceil(box.right_bottom.x), y = math.ceil(box.right_bottom.y) }, 24 | } 25 | else 26 | return { 27 | { math.floor(box[1][1]), math.floor(box[1][2]) }, 28 | { math.ceil(box[2][1]), math.ceil(box[2][2]) }, 29 | } 30 | end 31 | end 32 | 33 | --- Calculate the centerpoint of the box. 34 | --- @param box BoundingBox 35 | --- @return MapPosition 36 | function flib_bounding_box.center(box) 37 | if box.left_top then 38 | return { 39 | x = (box.left_top.x + box.right_bottom.x) / 2, 40 | y = (box.left_top.y + box.right_bottom.y) / 2, 41 | } 42 | else 43 | return { 44 | (box[1][1] + box[2][1]) / 2, 45 | (box[1][2] + box[2][2]) / 2, 46 | } 47 | end 48 | end 49 | 50 | --- Check if the first box contains the second box. 51 | --- @param box1 BoundingBox 52 | --- @param box2 BoundingBox 53 | --- @return boolean 54 | function flib_bounding_box.contains_box(box1, box2) 55 | local box1 = flib_bounding_box.ensure_explicit(box1) 56 | local box2 = flib_bounding_box.ensure_explicit(box2) 57 | 58 | return box1.left_top.x <= box2.left_top.x 59 | and box1.left_top.y <= box2.left_top.y 60 | and box1.right_bottom.x >= box2.right_bottom.x 61 | and box1.right_bottom.y >= box2.right_bottom.y 62 | end 63 | 64 | --- Check if the given box contains the given position. 65 | --- @param box BoundingBox 66 | --- @param pos MapPosition 67 | --- @return boolean 68 | function flib_bounding_box.contains_position(box, pos) 69 | local box = flib_bounding_box.ensure_explicit(box) 70 | local pos = flib_position.ensure_explicit(pos) 71 | return box.left_top.x <= pos.x 72 | and box.left_top.y <= pos.y 73 | and box.right_bottom.x >= pos.x 74 | and box.right_bottom.y >= pos.y 75 | end 76 | 77 | --- Return the box in explicit form. 78 | --- @param box BoundingBox 79 | --- @return BoundingBox 80 | function flib_bounding_box.ensure_explicit(box) 81 | return { 82 | left_top = flib_position.ensure_explicit(box.left_top or box[1]), 83 | right_bottom = flib_position.ensure_explicit(box.right_bottom or box[2]), 84 | } 85 | end 86 | 87 | --- Return the box in shorthand form. 88 | --- @param box BoundingBox 89 | --- @return BoundingBox 90 | function flib_bounding_box.ensure_short(box) 91 | return { 92 | flib_position.ensure_short(box.left_top or box[1]), 93 | flib_position.ensure_short(box.right_bottom or box[2]), 94 | } 95 | end 96 | 97 | --- Return a new box with initial dimensions box1, expanded to contain box2. 98 | --- @param box1 BoundingBox 99 | --- @param box2 BoundingBox 100 | --- @return BoundingBox 101 | function flib_bounding_box.expand_to_contain_box(box1, box2) 102 | local box2 = flib_bounding_box.ensure_explicit(box2) 103 | 104 | if box1.left_top then 105 | return { 106 | left_top = { 107 | x = math.min(box1.left_top.x, box2.left_top.x), 108 | y = math.min(box1.left_top.y, box2.left_top.y), 109 | }, 110 | right_bottom = { 111 | x = math.max(box1.right_bottom.x, box2.right_bottom.x), 112 | y = math.max(box1.right_bottom.y, box2.right_bottom.y), 113 | }, 114 | } 115 | else 116 | return { 117 | { 118 | math.min(box1[1][1], box2.left_top.x), 119 | math.min(box1[1][2], box2.left_top.y), 120 | }, 121 | { 122 | math.max(box1[2][1], box2.right_bottom.x), 123 | math.max(box1[2][2], box2.right_bottom.y), 124 | }, 125 | } 126 | end 127 | end 128 | 129 | --- Return a new box expanded to contain the given position. 130 | --- @param box BoundingBox 131 | --- @param pos MapPosition 132 | --- @return BoundingBox 133 | function flib_bounding_box.expand_to_contain_position(box, pos) 134 | pos = flib_position.ensure_explicit(pos) 135 | 136 | if box.left_top then 137 | return { 138 | left_top = { x = math.min(box.left_top.x, pos.x), y = math.min(box.left_top.y, pos.y) }, 139 | right_bottom = { x = math.max(box.right_bottom.x, pos.x), y = math.max(box.right_bottom.y, pos.y) }, 140 | } 141 | else 142 | return { 143 | { math.min(box[1][1], pos.x), math.min(box[1][2], pos.y) }, 144 | { math.max(box[2][1], pos.x), math.max(box[2][2], pos.y) }, 145 | } 146 | end 147 | end 148 | 149 | --- Return a new box shrunk to the nearest tile edges. 150 | --- @param box BoundingBox 151 | --- @return BoundingBox 152 | function flib_bounding_box.floor(box) 153 | if box.left_top then 154 | return { 155 | left_top = { x = math.ceil(box.left_top.x), y = math.ceil(box.left_top.y) }, 156 | right_bottom = { x = math.floor(box.right_bottom.x), y = math.floor(box.right_bottom.y) }, 157 | } 158 | else 159 | return { 160 | { math.ceil(box[1][1]), math.ceil(box[1][2]) }, 161 | { math.floor(box[2][1]), math.floor(box[2][2]) }, 162 | } 163 | end 164 | end 165 | 166 | --- Create a new box from a centerpoint and dimensions. 167 | --- @param center MapPosition 168 | --- @param width number 169 | --- @param height number 170 | --- @return BoundingBox 171 | function flib_bounding_box.from_dimensions(center, width, height) 172 | if center.x then 173 | return { 174 | left_top = { x = center.x - width / 2, y = center.y - height / 2 }, 175 | right_bottom = { x = center.x + width / 2, y = center.y + height / 2 }, 176 | } 177 | else 178 | return { 179 | { center[1] - width / 2, center[2] - height / 2 }, 180 | { center[1] + width / 2, center[2] + height / 2 }, 181 | } 182 | end 183 | end 184 | 185 | --- Create a 1x1 box from the given position, optionally snapped to the containing tile edges. 186 | --- @param pos MapPosition 187 | --- @param snap boolean? 188 | --- @return BoundingBox 189 | function flib_bounding_box.from_position(pos, snap) 190 | if snap then 191 | pos = flib_position.floor(pos) 192 | else 193 | pos = flib_position.sub(pos, { 0.5, 0.5 }) 194 | end 195 | local x = pos.x or pos[1] 196 | local y = pos.y or pos[2] 197 | if pos.x then 198 | return { 199 | left_top = { x = x, y = y }, 200 | right_bottom = { x = x + 1, y = y + 1 }, 201 | } 202 | else 203 | return { 204 | { x, y }, 205 | { x + 1, y + 1 }, 206 | } 207 | end 208 | end 209 | 210 | --- Calculate the height of the box. 211 | --- @param box BoundingBox 212 | --- @return number 213 | function flib_bounding_box.height(box) 214 | if box.left_top then 215 | return box.right_bottom.y - box.left_top.y 216 | else 217 | return box[2][2] - box[1][2] 218 | end 219 | end 220 | 221 | --- Check if the first box intersects (overlaps) the second box. 222 | --- @param box1 BoundingBox 223 | --- @param box2 BoundingBox 224 | --- @return boolean 225 | function flib_bounding_box.intersects_box(box1, box2) 226 | local box1 = flib_bounding_box.ensure_explicit(box1) 227 | local box2 = flib_bounding_box.ensure_explicit(box2) 228 | return box1.left_top.x < box2.right_bottom.x 229 | and box2.left_top.x < box1.right_bottom.x 230 | and box1.left_top.y < box2.right_bottom.y 231 | and box2.left_top.y < box1.right_bottom.y 232 | end 233 | 234 | --- Return a new box with the same dimensions, moved by the given delta. 235 | --- @param box BoundingBox 236 | --- @param delta MapPosition 237 | --- @return BoundingBox 238 | function flib_bounding_box.move(box, delta) 239 | local dx = delta.x or delta[1] 240 | local dy = delta.y or delta[2] 241 | if box.left_top then 242 | return { 243 | left_top = { x = box.left_top.x + dx, y = box.left_top.y + dy }, 244 | right_bottom = { x = box.right_bottom.x + dx, y = box.right_bottom.y + dy }, 245 | } 246 | else 247 | return { 248 | { box[1][1] + dx, box[1][2] + dy }, 249 | { box[2][1] + dx, box[2][2] + dy }, 250 | } 251 | end 252 | end 253 | 254 | --- Return a new box with the same dimensions centered on the given position. 255 | --- @param box BoundingBox 256 | --- @param pos MapPosition 257 | --- @return BoundingBox 258 | function flib_bounding_box.recenter_on(box, pos) 259 | local height = flib_bounding_box.height(box) 260 | local width = flib_bounding_box.width(box) 261 | 262 | local pos_x = pos.x or pos[1] 263 | local pos_y = pos.y or pos[2] 264 | 265 | if box.left_top then 266 | return { 267 | left_top = { x = pos_x - (width / 2), y = pos_y - (height / 2) }, 268 | right_bottom = { x = pos_x + (width / 2), y = pos_y + (height / 2) }, 269 | } 270 | else 271 | return { 272 | { pos_x - (width / 2), pos_y - (height / 2) }, 273 | { pos_x + (width / 2), pos_y + (height / 2) }, 274 | } 275 | end 276 | end 277 | 278 | --- Return a new box grown or shrunk by the given delta. A positive delta will grow the box, a negative delta will 279 | --- shrink it. 280 | --- @param box BoundingBox 281 | --- @param delta number 282 | --- @return BoundingBox 283 | function flib_bounding_box.resize(box, delta) 284 | if box.left_top then 285 | return { 286 | left_top = { x = box.left_top.x - delta, y = box.left_top.y - delta }, 287 | right_bottom = { x = box.right_bottom.x + delta, y = box.right_bottom.y + delta }, 288 | } 289 | else 290 | return { 291 | { box[1][1] - delta, box[1][2] - delta }, 292 | { box[2][1] + delta, box[2][2] + delta }, 293 | } 294 | end 295 | end 296 | 297 | --- Return a new box rotated 90 degrees about its center. 298 | --- @param box BoundingBox 299 | --- @return BoundingBox 300 | function flib_bounding_box.rotate(box) 301 | local center = flib_bounding_box.center(box) 302 | local radius_x = flib_bounding_box.width(box) / 2 303 | local radius_y = flib_bounding_box.height(box) / 2 304 | 305 | if box.left_top then 306 | return { 307 | left_top = { x = center.x - radius_y, y = center.y - radius_x }, 308 | right_bottom = { x = center.x + radius_y, y = center.y + radius_x }, 309 | } 310 | else 311 | return { 312 | { center.x - radius_y, center.y - radius_x }, 313 | { center.x + radius_y, center.y + radius_x }, 314 | } 315 | end 316 | end 317 | 318 | --- Return a new box expanded to create a square. 319 | --- @param box BoundingBox 320 | --- @return BoundingBox 321 | function flib_bounding_box.square(box) 322 | local radius = math.max(flib_bounding_box.width(box), flib_bounding_box.height(box)) 323 | return flib_bounding_box.from_dimensions(flib_bounding_box.center(box), radius, radius) 324 | end 325 | 326 | --- Calculate the width of the box. 327 | --- @param box BoundingBox 328 | --- @return number 329 | function flib_bounding_box.width(box) 330 | if box.left_top then 331 | return box.right_bottom.x - box.left_top.x 332 | else 333 | return box[2][1] - box[1][1] 334 | end 335 | end 336 | 337 | return flib_bounding_box 338 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | api_token_env: CROWDIN_TOKEN 2 | project_id_env: CROWDIN_FACTORIO_MODS_ID 3 | preserve_hierarchy: true 4 | 5 | files: 6 | - source: /locale/en/flib.cfg 7 | dest: flib/flib.ini 8 | translation: /locale/%two_letters_code%/%original_file_name% 9 | type: ini 10 | update_option: update_as_unapproved 11 | 12 | - source: /locale/en/dictionary.cfg 13 | dest: flib/dictionary.ini 14 | translation: /locale/%two_letters_code%/%original_file_name% 15 | type: ini 16 | update_option: update_as_unapproved 17 | -------------------------------------------------------------------------------- /data-util.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.data-util" then 2 | return require("__flib__.data-util") 3 | end 4 | 5 | --- Utilities for data stage prototype manipulation. 6 | --- ```lua 7 | --- local flib_data_util = require("__flib__.data-util") 8 | --- ``` 9 | --- @class flib_data_util 10 | local flib_data_util = {} 11 | 12 | --- Copy a prototype, assigning a new name and minable properties. 13 | --- @param prototype table 14 | --- @param new_name string string 15 | --- @param remove_icon? boolean 16 | --- @return table 17 | function flib_data_util.copy_prototype(prototype, new_name, remove_icon) 18 | if not prototype.type or not prototype.name then 19 | error("Invalid prototype: prototypes must have name and type properties.") 20 | return --- @diagnostic disable-line 21 | end 22 | local p = table.deepcopy(prototype) 23 | p.name = new_name 24 | if p.minable and p.minable.result then 25 | p.minable.result = new_name 26 | end 27 | if p.place_result then 28 | p.place_result = new_name 29 | end 30 | if p.result then 31 | p.result = new_name 32 | end 33 | if p.results then 34 | for _, result in pairs(p.results) do 35 | if result.name == prototype.name then 36 | result.name = new_name 37 | end 38 | end 39 | end 40 | if remove_icon then 41 | p.icon = nil 42 | p.icon_size = nil 43 | p.icons = nil 44 | end 45 | 46 | return p 47 | end 48 | 49 | --- Copy prototype.icon/icons to a new fully defined icons array, optionally adding new icon layers. 50 | --- 51 | --- Returns `nil` if the prototype's icons are incorrectly or incompletely defined. 52 | --- @param prototype table 53 | --- @param new_layers? data.IconData[] 54 | --- @return data.IconData[]|nil 55 | function flib_data_util.create_icons(prototype, new_layers) 56 | if new_layers then 57 | for _, new_layer in pairs(new_layers) do 58 | if not new_layer.icon or not new_layer.icon_size then 59 | return nil 60 | end 61 | end 62 | end 63 | 64 | if prototype.icons then 65 | local icons = {} 66 | for _, v in pairs(prototype.icons) do 67 | -- Over define as much as possible to minimize weirdness: https://forums.factorio.com/viewtopic.php?f=25&t=81980 68 | icons[#icons + 1] = { 69 | icon = v.icon, 70 | icon_size = v.icon_size or prototype.icon_size, 71 | tint = v.tint, 72 | scale = v.scale, 73 | shift = v.shift, 74 | } 75 | end 76 | if new_layers then 77 | for _, new_layer in pairs(new_layers) do 78 | icons[#icons + 1] = new_layer 79 | end 80 | end 81 | return icons 82 | elseif prototype.icon then 83 | local icons = { 84 | { 85 | icon = prototype.icon, 86 | icon_size = prototype.icon_size, 87 | tint = { r = 1, g = 1, b = 1, a = 1 }, 88 | }, 89 | } 90 | if new_layers then 91 | for _, new_layer in pairs(new_layers) do 92 | icons[#icons + 1] = new_layer 93 | end 94 | end 95 | return icons 96 | else 97 | return nil 98 | end 99 | end 100 | 101 | local exponent_multipliers = { 102 | ["q"] = 0.000000000000000000000000000001, 103 | ["r"] = 0.000000000000000000000000001, 104 | ["y"] = 0.000000000000000000000001, 105 | ["z"] = 0.000000000000000000001, 106 | ["a"] = 0.000000000000000001, 107 | ["f"] = 0.000000000000001, 108 | ["p"] = 0.000000000001, 109 | ["n"] = 0.000000001, 110 | ["u"] = 0.000001, -- μ is invalid 111 | ["m"] = 0.001, 112 | ["c"] = 0.01, 113 | ["d"] = 0.1, 114 | [""] = 1, 115 | ["da"] = 10, 116 | ["h"] = 100, 117 | ["k"] = 1000, 118 | ["M"] = 1000000, 119 | ["G"] = 1000000000, 120 | ["T"] = 1000000000000, 121 | ["P"] = 1000000000000000, 122 | ["E"] = 1000000000000000000, 123 | ["Z"] = 1000000000000000000000, 124 | ["Y"] = 1000000000000000000000000, 125 | ["R"] = 1000000000000000000000000000, 126 | ["Q"] = 1000000000000000000000000000000, 127 | } 128 | 129 | --- Convert an energy string to base unit value + suffix. 130 | --- 131 | --- Returns `nil` if `energy_string` is incorrectly formatted. 132 | --- @param energy_string string 133 | --- @return number? 134 | --- @return string? 135 | function flib_data_util.get_energy_value(energy_string) 136 | if type(energy_string) == "string" then 137 | local v, _, exp, unit = string.match(energy_string, "([%-+]?[0-9]*%.?[0-9]+)((%D*)([WJ]))") 138 | local value = tonumber(v) 139 | if value and exp and exponent_multipliers[exp] then 140 | value = value * exponent_multipliers[exp] 141 | return value, unit 142 | end 143 | end 144 | return nil 145 | end 146 | 147 | --- Build a sprite from constituent parts. 148 | --- @param name? string 149 | --- @param position? MapPosition 150 | --- @param filename? string 151 | --- @param size? Vector 152 | --- @param mods? table 153 | --- @return data.Sprite 154 | function flib_data_util.build_sprite(name, position, filename, size, mods) 155 | --- @type data.Sprite 156 | local def = { 157 | type = "sprite", 158 | name = name, 159 | filename = filename, 160 | position = position, 161 | size = size, 162 | flags = { "icon" }, 163 | } 164 | if mods then 165 | for k, v in pairs(mods) do 166 | def[k] = v 167 | end 168 | end 169 | return def 170 | end 171 | 172 | --- An empty image. This image is 8x8 to facilitate usage with GUI styles. 173 | flib_data_util.empty_image = "__flib__/graphics/empty.png" 174 | 175 | --- A black image, for use with tool backgrounds. This image is 1x1. 176 | flib_data_util.black_image = "__flib__/graphics/black.png" 177 | 178 | --- A desaturated planner image. Tint this sprite to easily add your own planners. 179 | flib_data_util.planner_base_image = "__flib__/graphics/planner.png" 180 | 181 | --- A dark red button tileset. Used for the `flib_tool_button_dark_red` style. 182 | flib_data_util.dark_red_button_tileset = "__flib__/graphics/dark-red-button.png" 183 | 184 | return flib_data_util 185 | -------------------------------------------------------------------------------- /data.lua: -------------------------------------------------------------------------------- 1 | require("prototypes.sprite") 2 | require("prototypes.style") 3 | require("prototypes.technology-slot-style") 4 | -------------------------------------------------------------------------------- /dictionary.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.dictionary" then 2 | return require("__flib__.dictionary") 3 | end 4 | 5 | local gui = require("__flib__.gui") 6 | local mod_gui = require("__core__.lualib.mod-gui") 7 | local table = require("__flib__.table") 8 | 9 | --- @class flib.DictionaryStorage 10 | --- @field init_ran boolean 11 | --- @field raw table 12 | --- @field raw_count integer 13 | --- @field to_translate string[] 14 | --- @field translated table?> 15 | --- @field wip flib.DictionaryWipData? 16 | 17 | --- @class flib.DictionaryWipData 18 | --- @field dict string 19 | --- @field dicts table 20 | --- @field finished boolean 21 | --- @field key string? 22 | --- @field last_batch_end flib.DictionaryTranslationRequest? 23 | --- @field language string 24 | --- @field received_count integer 25 | --- @field requests table 26 | --- @field request_tick uint 27 | --- @field translator LuaPlayer 28 | 29 | --- Utilities for creating dictionaries of localised string translations. 30 | --- ```lua 31 | --- local flib_dictionary = require("__flib__.dictionary") 32 | --- ``` 33 | --- @class flib_dictionary 34 | local flib_dictionary = {} 35 | 36 | local request_timeout_ticks = (60 * 5) 37 | 38 | --- @param init_only boolean? 39 | --- @return flib.DictionaryStorage 40 | local function get_data(init_only) 41 | if not storage.__flib or not storage.__flib.dictionary then 42 | error("Dictionary module was not properly initialized - ensure that all lifecycle events are handled.") 43 | end 44 | local data = storage.__flib.dictionary 45 | if init_only and data.init_ran then 46 | error("Dictionaries cannot be modified after initialization.") 47 | end 48 | return data 49 | end 50 | 51 | --- @param language string 52 | --- @return LuaPlayer? 53 | local function get_translator(language) 54 | for _, player in pairs(game.players) do 55 | if player.connected and player.locale == language then 56 | return player 57 | end 58 | end 59 | end 60 | 61 | --- @param data flib.DictionaryStorage 62 | local function update_gui(data) 63 | local wip = data.wip 64 | for _, player in pairs(game.players) do 65 | local frame_flow = mod_gui.get_frame_flow(player) 66 | local window = frame_flow.flib_translation_progress 67 | if wip then 68 | if not window then 69 | _, window = gui.add(frame_flow, { 70 | type = "frame", 71 | name = "flib_translation_progress", 72 | style = mod_gui.frame_style, 73 | direction = "vertical", 74 | { 75 | type = "label", 76 | style = "frame_title", 77 | caption = { "gui.flib-translating-dictionaries" }, 78 | tooltip = { "gui.flib-translating-dictionaries-description" }, 79 | }, 80 | { 81 | type = "frame", 82 | name = "pane", 83 | style = "inside_shallow_frame_with_padding", 84 | --- @diagnostic disable-next-line: missing-fields 85 | style_mods = { top_padding = 8 }, 86 | direction = "vertical", 87 | }, 88 | }) 89 | end 90 | local pane = window.pane --[[@as LuaGuiElement]] 91 | local mod_flow = pane[script.mod_name] 92 | if not mod_flow then 93 | _, mod_flow = gui.add(pane, { 94 | type = "flow", 95 | name = script.mod_name, 96 | style_mods = { vertical_align = "center", top_margin = 4, horizontal_spacing = 8 }, 97 | { 98 | type = "label", 99 | style = "caption_label", 100 | --- @diagnostic disable-next-line: missing-fields 101 | style_mods = { minimal_width = 130 }, 102 | caption = { "?", { "mod-name." .. script.mod_name }, script.mod_name }, 103 | ignored_by_interaction = true, 104 | }, 105 | { type = "empty-widget", style = "flib_horizontal_pusher" }, 106 | { type = "label", name = "language", style = "bold_label", ignored_by_interaction = true }, 107 | { 108 | type = "progressbar", 109 | name = "bar", 110 | --- @diagnostic disable-next-line: missing-fields 111 | style_mods = { top_margin = 1, width = 100 }, 112 | ignored_by_interaction = true, 113 | }, 114 | { 115 | type = "label", 116 | name = "percentage", 117 | style = "bold_label", 118 | --- @diagnostic disable-next-line: missing-fields 119 | style_mods = { width = 24, horizontal_align = "right" }, 120 | ignored_by_interaction = true, 121 | }, 122 | }) 123 | end 124 | local progress = wip.received_count / data.raw_count 125 | mod_flow.language.caption = wip.language 126 | mod_flow.bar.value = progress --[[@as double]] 127 | mod_flow.percentage.caption = tostring(math.min(math.floor(progress * 100), 99)) .. "%" 128 | mod_flow.tooltip = 129 | { "", (wip.dict or { "gui.flib-finishing" }), "\n" .. wip.received_count .. " / " .. data.raw_count } 130 | else 131 | if window then 132 | local mod_flow = window.pane[script.mod_name] 133 | if mod_flow then 134 | mod_flow.destroy() 135 | end 136 | if #window.pane.children == 0 then 137 | window.destroy() 138 | end 139 | end 140 | end 141 | end 142 | end 143 | 144 | --- @param data flib.DictionaryStorage 145 | --- @return boolean success 146 | local function request_next_batch(data) 147 | local raw = data.raw 148 | local wip = data.wip --[[@as flib.DictionaryWipData]] 149 | if wip.finished then 150 | wip.last_batch_end = nil 151 | return false 152 | end 153 | wip.last_batch_end = { language = wip.language, dict = wip.dict, key = wip.key } 154 | local requests, strings = {}, {} 155 | for i = 1, game.is_multiplayer() and 5 or 50 do 156 | local string 157 | repeat 158 | wip.key, string = next(raw[wip.dict], wip.key) 159 | if not wip.key then 160 | wip.dict = next(raw, wip.dict) 161 | if not wip.dict then 162 | -- We are done! 163 | wip.finished = true 164 | end 165 | end 166 | until string or wip.finished 167 | if wip.finished then 168 | break 169 | end 170 | local request = { dict = wip.dict, key = wip.key } 171 | requests[i] = request 172 | strings[i] = string 173 | end 174 | 175 | if not requests[1] then 176 | return false -- Finished 177 | end 178 | 179 | local translator = wip.translator 180 | if not translator.valid or not translator.connected or translator.locale ~= wip.language then 181 | local new_translator = get_translator(wip.language) 182 | if new_translator then 183 | wip.translator = new_translator 184 | else 185 | -- Cancel this translation 186 | data.wip = nil 187 | return false 188 | end 189 | end 190 | 191 | local ids = wip.translator.request_translations(strings) 192 | if not ids then 193 | return false 194 | end 195 | for i = 1, #ids do 196 | wip.requests[ids[i]] = requests[i] 197 | end 198 | --- @diagnostic disable-next-line: missing-fields 199 | wip.request_tick = game.tick 200 | 201 | update_gui(data) 202 | 203 | return true 204 | end 205 | 206 | --- @param data flib.DictionaryStorage 207 | local function handle_next_language(data) 208 | if not next(data.raw) then 209 | -- This can happen if handle_next_language is called during on_init or on_configuration_changed 210 | return 211 | end 212 | while not data.wip and #data.to_translate > 0 do 213 | local next_language = table.remove(data.to_translate, 1) 214 | if next_language then 215 | local translator = get_translator(next_language) 216 | if translator then 217 | -- Start translation 218 | local dicts = {} 219 | for name in pairs(data.raw) do 220 | dicts[name] = {} 221 | end 222 | --- @type flib.DictionaryWipData 223 | data.wip = { 224 | dict = next(data.raw), 225 | dicts = dicts, 226 | finished = false, 227 | --- @type string? 228 | key = nil, 229 | language = next_language, 230 | received_count = 0, 231 | --- @type table 232 | requests = {}, 233 | request_tick = 0, 234 | translator = translator, 235 | } 236 | request_next_batch(data) 237 | end 238 | end 239 | end 240 | end 241 | 242 | -- Events 243 | 244 | flib_dictionary.on_player_dictionaries_ready = script.generate_event_name() 245 | --- Called when a player's dictionaries are ready to be used. Handling this event is not required. 246 | --- @class flib.on_player_dictionaries_ready: EventData 247 | --- @field player_index uint 248 | 249 | -- Lifecycle handlers 250 | 251 | function flib_dictionary.on_init() 252 | if not storage.__flib then 253 | storage.__flib = {} 254 | end 255 | --- @type flib.DictionaryStorage 256 | storage.__flib.dictionary = { 257 | init_ran = false, 258 | player_language_requests = {}, 259 | player_languages = {}, 260 | raw = {}, 261 | raw_count = 0, 262 | to_translate = {}, 263 | translated = {}, 264 | wip = nil, 265 | } 266 | for player_index, player in pairs(game.players) do 267 | if player.connected then 268 | flib_dictionary.on_player_joined_game({ 269 | name = defines.events.on_player_joined_game, 270 | tick = game.tick, 271 | --- @cast player_index uint 272 | player_index = player_index, 273 | }) 274 | end 275 | end 276 | end 277 | 278 | flib_dictionary.on_configuration_changed = flib_dictionary.on_init 279 | 280 | function flib_dictionary.on_tick() 281 | local data = get_data() 282 | if not data.init_ran then 283 | data.init_ran = true 284 | end 285 | 286 | handle_next_language(data) 287 | 288 | local wip = data.wip 289 | if not wip then 290 | return 291 | end 292 | 293 | if game.tick - wip.request_tick > request_timeout_ticks then 294 | local request = wip.last_batch_end 295 | if not request then 296 | -- TODO: Remove WIP because we actually finished somehow? This should never happen I think 297 | error("We're screwed") 298 | end 299 | wip.dict = request.dict 300 | wip.finished = false 301 | wip.key = request.key 302 | wip.requests = {} 303 | request_next_batch(data) 304 | update_gui(data) 305 | end 306 | end 307 | 308 | --- @param e EventData.on_string_translated 309 | function flib_dictionary.on_string_translated(e) 310 | local data = get_data() 311 | local id = e.id 312 | 313 | handle_next_language(data) 314 | 315 | local wip = data.wip 316 | if not wip then 317 | return 318 | end 319 | 320 | local request = wip.requests[id] 321 | if request then 322 | wip.requests[id] = nil 323 | wip.received_count = wip.received_count + 1 324 | if e.translated then 325 | wip.dicts[request.dict][request.key] = e.result 326 | end 327 | end 328 | 329 | while wip and not next(wip.requests) and not request_next_batch(data) do 330 | if wip.finished then 331 | data.translated[wip.language] = wip.dicts 332 | data.wip = nil 333 | for player_index, player in pairs(game.players) do 334 | if player.locale == wip.language then 335 | script.raise_event(flib_dictionary.on_player_dictionaries_ready, { player_index = player_index }) 336 | end 337 | end 338 | end 339 | handle_next_language(data) 340 | update_gui(data) 341 | wip = data.wip 342 | end 343 | end 344 | 345 | --- @param e EventData.on_player_joined_game 346 | function flib_dictionary.on_player_joined_game(e) 347 | local player = game.get_player(e.player_index) --- @unwrap 348 | if not player then 349 | return 350 | end 351 | local language = player.locale 352 | local data = get_data() 353 | if data.translated[language] then 354 | script.raise_event(flib_dictionary.on_player_dictionaries_ready, { player_index = e.player_index }) 355 | return 356 | elseif data.wip and data.wip.language == language then 357 | return 358 | elseif table.find(data.to_translate, language) then 359 | return 360 | end 361 | table.insert(data.to_translate, language) 362 | handle_next_language(data) 363 | update_gui(data) 364 | end 365 | 366 | flib_dictionary.on_player_locale_changed = flib_dictionary.on_player_joined_game 367 | 368 | --- Handle all non-bootstrap events with default event handlers. Will not overwrite any existing handlers. If you have 369 | --- custom handlers for on_tick, on_string_translated, or on_player_joined_game, ensure that you call the corresponding 370 | --- module lifecycle handler.. 371 | function flib_dictionary.handle_events() 372 | for id, handler in pairs({ 373 | [defines.events.on_tick] = flib_dictionary.on_tick, 374 | [defines.events.on_string_translated] = flib_dictionary.on_string_translated, 375 | [defines.events.on_player_joined_game] = flib_dictionary.on_player_joined_game, 376 | }) do 377 | if 378 | not script.get_event_handler(id --[[@as uint]]) 379 | then 380 | script.on_event(id, handler) 381 | end 382 | end 383 | end 384 | 385 | --- For use with `__core__/lualib/event_handler`. Pass `flib_dictionary` into `handler.add_lib` to 386 | --- handle all relevant events automatically. 387 | flib_dictionary.events = { 388 | [defines.events.on_player_joined_game] = flib_dictionary.on_player_joined_game, 389 | [defines.events.on_player_locale_changed] = flib_dictionary.on_player_locale_changed, 390 | [defines.events.on_string_translated] = flib_dictionary.on_string_translated, 391 | [defines.events.on_tick] = flib_dictionary.on_tick, 392 | } 393 | 394 | -- Dictionary creation 395 | 396 | --- Create a new dictionary. The name must be unique. 397 | --- @param name string 398 | --- @param initial_strings flib.Dictionary? 399 | function flib_dictionary.new(name, initial_strings) 400 | local data = get_data(true) 401 | local raw = data.raw 402 | if raw[name] then 403 | error("Attempted to create dictionary '" .. name .. "' twice.") 404 | end 405 | raw[name] = initial_strings or {} 406 | if initial_strings then 407 | data.raw_count = data.raw_count + table_size(initial_strings) 408 | end 409 | end 410 | 411 | --- Add the given string to the dictionary. 412 | --- @param dict_name string 413 | --- @param key string 414 | --- @param localised LocalisedString 415 | function flib_dictionary.add(dict_name, key, localised) 416 | local data = get_data(true) 417 | local raw = data.raw[dict_name] 418 | if not raw then 419 | error("Dictionary '" .. dict_name .. "' does not exist.") 420 | end 421 | if not raw[key] then 422 | data.raw_count = data.raw_count + 1 423 | end 424 | raw[key] = localised 425 | end 426 | 427 | --- Get all dictionaries for the player. Will return `nil` if the player's language has not finished translating. 428 | --- @param player_index uint 429 | --- @return table? 430 | function flib_dictionary.get_all(player_index) 431 | local player = game.get_player(player_index) 432 | if not player then 433 | return 434 | end 435 | return get_data().translated[player.locale] 436 | end 437 | 438 | --- Get the specified dictionary for the player. Will return `nil` if the dictionary has not finished translating. 439 | --- @param player_index uint 440 | --- @param dict_name string 441 | --- @return flib.TranslatedDictionary? 442 | function flib_dictionary.get(player_index, dict_name) 443 | local data = get_data() 444 | if not data.raw[dict_name] then 445 | error("Dictionary '" .. dict_name .. "' does not exist.") 446 | end 447 | local language_dicts = flib_dictionary.get_all(player_index) or {} 448 | return language_dicts[dict_name] 449 | end 450 | 451 | --- @class flib.DictionaryLanguageRequest 452 | --- @field player LuaPlayer 453 | --- @field tick uint 454 | 455 | --- @class flib.DictionaryTranslationRequest 456 | --- @field language string 457 | --- @field dict string 458 | --- @field key string 459 | 460 | --- Localised strings identified by an internal key. Keys must be unique and language-agnostic. 461 | --- @alias flib.Dictionary table 462 | 463 | --- Translations are identified by their internal key. If the translation failed, then it will not be present. Locale 464 | --- fallback groups can be used if every key needs a guaranteed translation. 465 | --- @alias flib.TranslatedDictionary table 466 | 467 | return flib_dictionary 468 | -------------------------------------------------------------------------------- /direction.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.direction" then 2 | return require("__flib__.direction") 3 | end 4 | 5 | local flib_math = require("__flib__.math") 6 | 7 | --- Functions for working with directions. 8 | --- ```lua 9 | --- local flib_direction = require("__flib__.direction") 10 | --- ``` 11 | --- @class flib_direction 12 | local flib_direction = {} 13 | 14 | --- Calculate the opposite direction. 15 | --- @param direction defines.direction 16 | --- @return defines.direction 17 | function flib_direction.opposite(direction) 18 | return (direction + 8) % 16 --[[@as defines.direction]] 19 | end 20 | 21 | --- Calculate the next four-way or eight-way direction. 22 | --- @param direction defines.direction 23 | --- @param eight_way? boolean 24 | --- @return defines.direction 25 | function flib_direction.next(direction, eight_way) 26 | return (direction + (eight_way and 2 or 4)) % 16 --[[@as defines.direction]] 27 | end 28 | 29 | --- Calculate the previous four-way or eight-way direction. 30 | --- @param direction defines.direction 31 | --- @param eight_way? boolean 32 | --- @return defines.direction 33 | function flib_direction.previous(direction, eight_way) 34 | return (direction + (eight_way and -2 or -4)) % 16 --[[@as defines.direction]] 35 | end 36 | 37 | --- Calculate an orientation from a direction. 38 | --- @param direction defines.direction 39 | --- @return RealOrientation 40 | function flib_direction.to_orientation(direction) 41 | return direction / 16 --[[@as RealOrientation]] 42 | end 43 | 44 | --- Calculate a vector from a direction. 45 | --- @param direction defines.direction 46 | --- @param distance? number default: `1` 47 | --- @return MapPosition 48 | function flib_direction.to_vector(direction, distance) 49 | distance = distance or 1 50 | local x, y = 0, 0 51 | if direction == defines.direction.north then 52 | y = y - distance 53 | elseif direction == defines.direction.northeast then 54 | x, y = x + distance, y - distance 55 | elseif direction == defines.direction.east then 56 | x = x + distance 57 | elseif direction == defines.direction.southeast then 58 | x, y = x + distance, y + distance 59 | elseif direction == defines.direction.south then 60 | y = y + distance 61 | elseif direction == defines.direction.southwest then 62 | x, y = x - distance, y + distance 63 | elseif direction == defines.direction.west then 64 | x = x - distance 65 | elseif direction == defines.direction.northwest then 66 | x, y = x - distance, y - distance 67 | end 68 | return { x = x, y = y } 69 | end 70 | 71 | --- Calculate a two-dimensional vector from a cardinal direction. 72 | --- @param direction defines.direction 73 | --- @param longitudinal number Distance to move in the specified direction. 74 | --- @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. 75 | --- @return MapPosition? 76 | function flib_direction.to_vector_2d(direction, longitudinal, orthogonal) 77 | if direction == defines.direction.north then 78 | return { x = orthogonal, y = -longitudinal } 79 | elseif direction == defines.direction.south then 80 | return { x = -orthogonal, y = longitudinal } 81 | elseif direction == defines.direction.east then 82 | return { x = longitudinal, y = orthogonal } 83 | elseif direction == defines.direction.west then 84 | return { x = -longitudinal, y = -orthogonal } 85 | end 86 | end 87 | 88 | --- Calculate the direction of travel from the source to the target. 89 | --- @param source MapPosition 90 | --- @param target MapPosition 91 | --- @param round? boolean If true, round to the nearest `defines.direction`. 92 | --- @return defines.direction 93 | function flib_direction.from_positions(source, target, round) 94 | local deg = math.deg(math.atan2(target.y - source.y, target.x - source.x)) 95 | local direction = (deg + 90) / 22.5 96 | if direction < 0 then 97 | direction = direction + 16 98 | end 99 | if round then 100 | direction = flib_math.round(direction) 101 | end 102 | return direction --[[@as defines.direction]] 103 | end 104 | 105 | return flib_direction 106 | -------------------------------------------------------------------------------- /docs/assets/indicator-examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/docs/assets/indicator-examples.png -------------------------------------------------------------------------------- /docs/assets/slot-style-examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/docs/assets/slot-style-examples.png -------------------------------------------------------------------------------- /docs/gui-styles.md: -------------------------------------------------------------------------------- 1 | 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). 2 | 3 | **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. 4 | 5 | ### Button styles 6 | 7 | **flib_selected_frame_action_button** 8 | 9 | A "selected" frame action button. Use when a frame action button can "toggle" on and off. 10 | 11 | **flib_selected_tool_button** 12 | 13 | A "selected" tool button. Use when a tool button can "toggle" on and off. 14 | 15 | **flib_tool_button_light_green** 16 | 17 | 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. 18 | 19 | **flib_tool_button_dark_red** 20 | 21 | A dark red tool button, similar to the red shortcut button style. 22 | 23 | #### Slot styles 24 | 25 | flib includes a myriad of colored slot styles for use with `sprite-button`s: 26 | 27 | ![](https://raw.githubusercontent.com/factoriolib/flib/master/docs/assets/slot-style-examples.png) 28 | 29 | 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`. 30 | 31 | 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`. 32 | 33 | 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`). 34 | 35 | ### Empty widget styles 36 | 37 | **flib_dialog_footer_drag_handle** 38 | 39 | A drag handle suitable for placement in the footer of a **dialog** window. 40 | 41 | **flib_dialog_footer_drag_handle_no_right** 42 | 43 | A dialog footer drag handle with the right margin removed. Suitable for dialog windows without a `confirm` button. 44 | 45 | **flib_dialog_titlebar_drag_handle** 46 | 47 | A drag handle suitable for placement in the titlebar of a **dialog** window. Use inside of a `flib_titlebar_flow` flow. 48 | 49 | **flib_horizontal_pusher** 50 | 51 | An invisible element that has `horizontally_stretchable` set, thereby "pushing" everything to the right. 52 | 53 | **flib_titlebar_drag_handle** 54 | 55 | 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. 56 | 57 | **flib_vertical_pusher** 58 | 59 | An invisible element that has `vertically_stretchable` set, thereby "pushing" everything to the bottom. 60 | 61 | ### Flow styles 62 | 63 | **flib_indicator_flow** 64 | 65 | A flow designed for use with indicators (see below). 66 | 67 | **flib_titlebar_flow** 68 | 69 | A flow for use in a custom window titlebar. Identical to a regular horizontal flow, except for an increased horizontal spacing. 70 | 71 | ### Frame styles 72 | 73 | **flib_shallow_frame_in_shallow_frame** 74 | 75 | A shallow frame nested in another shallow frame. Use of this is generally recommended against, but can be useful in some specific situations. 76 | 77 | ### Image styles 78 | 79 | **flib_indicator** 80 | 81 | A 16x16 image style. Designed for use with flib's indicator sprites (see `sprites.md`). 82 | 83 | ### Scroll pane styles 84 | 85 | **flib_naked_scroll_pane** 86 | 87 | 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. 88 | 89 | **flib_naked_scroll_pane_under_tabs** 90 | 91 | 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. 92 | 93 | **flib_naked_scroll_pane_no_padding** 94 | 95 | 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. 96 | 97 | **flib_shallow_scroll_pane** 98 | 99 | A scroll pane that is inset from a shallow frame, instead of an outer frame. 100 | 101 | ### Tabbed pane styles 102 | 103 | **flib_tabbed_pane_with_no_padding** 104 | 105 | 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. 106 | 107 | ### Textfield styles 108 | 109 | **flib_widthless_textfield** 110 | 111 | A textfield with no width defined on it. The default textfield style has a width of 200, which can wreak havoc. 112 | 113 | **flib_widthless_invalid_textfield** 114 | 115 | A widthless textfield that has a red background. Suitable for situations where the content of the textfield is invalid in some way. 116 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flib documentation 6 | 7 | 8 |

Factorio Library documentation

9 |

This documentation is auto-generated by the Lua language server. 10 | These docs are greatly unpolished and lack any quality of life features 11 | whatsoever. This site will be replaced with a custom implementation in the 12 | future.

13 | 17 | {{docs}} 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/sprites.md: -------------------------------------------------------------------------------- 1 | The following are sprites provided by flib for your use: 2 | 3 | ## Indicator sprites 4 | 5 | ![](https://raw.githubusercontent.com/factoriolib/flib/master/docs/assets/indicator-examples.png) 6 | 7 | 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`). 8 | 9 | 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`). -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | title { display: none } 2 | 3 | body { 4 | background-color: #282828; 5 | color: #fff; 6 | font-family: sans-serif; 7 | margin: 0 auto; 8 | max-width: 1200px; 9 | padding: 1rem; 10 | width: auto; 11 | } 12 | 13 | h1 { 14 | font-size: 1.5rem; 15 | color: #ffe6c0; 16 | } 17 | 18 | h2 { 19 | font-size: 1.2rem; 20 | } 21 | 22 | h3 { 23 | font-size: 1rem; 24 | font-weight: 800; 25 | } 26 | 27 | pre { 28 | background-color: #353535; 29 | padding: 0.5rem; 30 | overflow: scroll; 31 | font-family: monospace; 32 | } 33 | 34 | a { 35 | color: #ff9f1c; 36 | } 37 | 38 | nav a { 39 | margin: 0 1rem; 40 | } 41 | -------------------------------------------------------------------------------- /format.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.format" then 2 | return require("__flib__.format") 3 | end 4 | 5 | --- Various string formatting functions. 6 | --- ```lua 7 | --- local flib_format = require("__flib__.format") 8 | --- ``` 9 | --- @class flib_format 10 | local flib_format = {} 11 | 12 | local suffix_list = { 13 | { "Q", 1e30 }, -- quetta 14 | { "R", 1e27 }, -- ronna 15 | { "Y", 1e24 }, -- yotta 16 | { "Z", 1e21 }, -- zetta 17 | { "E", 1e18 }, -- exa 18 | { "P", 1e15 }, -- peta 19 | { "T", 1e12 }, -- tera 20 | { "G", 1e9 }, -- giga 21 | { "M", 1e6 }, -- mega 22 | { "k", 1e3 }, -- kilo 23 | } 24 | 25 | --- Format a number for display, adding commas and an optional SI suffix. 26 | --- Specify `fixed_precision` to display the number with the given width, 27 | --- adjusting precision as necessary. 28 | --- @param amount number 29 | --- @param append_suffix boolean? 30 | --- @param fixed_precision number? 31 | --- @return string 32 | function flib_format.number(amount, append_suffix, fixed_precision) 33 | local suffix = "" 34 | if append_suffix then 35 | for _, data in ipairs(suffix_list) do 36 | if math.abs(amount) >= data[2] then 37 | amount = amount / data[2] 38 | suffix = " " .. data[1] 39 | break 40 | end 41 | end 42 | if not fixed_precision then 43 | amount = math.floor(amount * 10) / 10 44 | end 45 | end 46 | local formatted, k = tostring(amount), nil 47 | if fixed_precision then 48 | -- Show the number with fixed width precision 49 | local len_before = #tostring(math.floor(amount)) 50 | local len_after = math.max(0, fixed_precision - len_before - 1) 51 | formatted = string.format("%." .. len_after .. "f", amount) 52 | end 53 | -- Add commas to result 54 | while true do 55 | formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", "%1,%2") 56 | if k == 0 then 57 | break 58 | end 59 | end 60 | return formatted .. suffix 61 | end 62 | 63 | --- Convert the given tick or game.tick into "[hh:]mm:ss" format. 64 | --- @param tick uint? 65 | --- @param include_leading_zeroes boolean? 66 | --- @return string 67 | function flib_format.time(tick, include_leading_zeroes) 68 | local total_seconds = math.floor((tick or game.ticks_played) / 60) 69 | local seconds = total_seconds % 60 70 | local minutes = math.floor(total_seconds / 60) 71 | if minutes > 59 then 72 | minutes = minutes % 60 73 | local hours = math.floor(total_seconds / 3600) 74 | if include_leading_zeroes then 75 | return string.format("%02d:%02d:%02d", hours, minutes, seconds) 76 | else 77 | return string.format("%d:%02d:%02d", hours, minutes, seconds) 78 | end 79 | else 80 | if include_leading_zeroes then 81 | return string.format("%02d:%02d", minutes, seconds) 82 | else 83 | return string.format("%d:%02d", minutes, seconds) 84 | end 85 | end 86 | end 87 | 88 | return flib_format 89 | -------------------------------------------------------------------------------- /gen-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -d doc-html ]; then 6 | rm -rf doc-html 7 | fi 8 | mkdir doc-html 9 | 10 | docs=$(cmark < $(lua-language-server --doc=. | awk -F ] '/Markdown/ { print $1 }' | awk '{ print substr($2, 2, length($2)) }')) 11 | cat docs/index.html \ 12 | | awk -v docs="$docs" ' 13 | { flag = 1 } 14 | /{{docs}}/ { flag = 0; print docs } 15 | flag { print } 16 | ' > doc-html/index.html 17 | cp docs/style.css doc-html/style.css 18 | -------------------------------------------------------------------------------- /graphics/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/black.png -------------------------------------------------------------------------------- /graphics/dark-red-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/dark-red-button.png -------------------------------------------------------------------------------- /graphics/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/empty.png -------------------------------------------------------------------------------- /graphics/frame-action-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/frame-action-icons.png -------------------------------------------------------------------------------- /graphics/indicators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/indicators.png -------------------------------------------------------------------------------- /graphics/nav-backward-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/nav-backward-black.png -------------------------------------------------------------------------------- /graphics/nav-backward-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/nav-backward-disabled.png -------------------------------------------------------------------------------- /graphics/nav-backward-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/nav-backward-white.png -------------------------------------------------------------------------------- /graphics/nav-forward-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/nav-forward-black.png -------------------------------------------------------------------------------- /graphics/nav-forward-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/nav-forward-disabled.png -------------------------------------------------------------------------------- /graphics/nav-forward-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/nav-forward-white.png -------------------------------------------------------------------------------- /graphics/planner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/planner.png -------------------------------------------------------------------------------- /graphics/slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/slots.png -------------------------------------------------------------------------------- /graphics/slots.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/slots.xcf -------------------------------------------------------------------------------- /graphics/subheader-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/subheader-line.png -------------------------------------------------------------------------------- /graphics/technology-slots.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/technology-slots.aseprite -------------------------------------------------------------------------------- /graphics/technology-slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/graphics/technology-slots.png -------------------------------------------------------------------------------- /gui-templates.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.gui-templates" then 2 | return require("__flib__.gui-templates") 3 | end 4 | 5 | local flib_math = require("__flib__.math") 6 | local flib_gui = require("__flib__.gui") 7 | local flib_table = require("__flib__.table") 8 | local flib_technology = require("__flib__.technology") 9 | 10 | local flib_gui_templates = {} 11 | 12 | --- Create and return a technology slot. `on_click` must be a registered GUI handler through `gui-lite`. 13 | --- @param parent LuaGuiElement 14 | --- @param technology LuaTechnology 15 | --- @param level uint 16 | --- @param research_state TechnologyResearchState 17 | --- @param on_click flib.GuiElemHandler? 18 | --- @param tags Tags? 19 | --- @param index uint? 20 | --- @return LuaGuiElement 21 | function flib_gui_templates.technology_slot(parent, technology, level, research_state, on_click, tags, index) 22 | local technology_prototype = technology.prototype 23 | 24 | local is_multilevel = flib_technology.is_multilevel(technology) 25 | 26 | local research_state_str = flib_table.find(flib_technology.research_state, research_state) 27 | local style = "flib_technology_slot_" .. research_state_str 28 | if technology.upgrade or is_multilevel or technology_prototype.level > 1 then 29 | style = style .. "_multilevel" 30 | end 31 | 32 | local base = parent.add({ 33 | type = "sprite-button", 34 | style = style, 35 | elem_tooltip = { type = "technology", name = technology.name }, 36 | tags = tags, 37 | index = index, 38 | }) 39 | if on_click then 40 | base.tags = flib_gui.format_handlers({ [defines.events.on_gui_click] = on_click }, tags) 41 | end 42 | base 43 | .add({ type = "flow", name = "icon_flow", style = "flib_technology_slot_sprite_flow", ignored_by_interaction = true }) 44 | .add({ 45 | type = "sprite", 46 | name = "icon", 47 | style = "flib_technology_slot_sprite", 48 | sprite = "technology/" .. technology.name, 49 | }) 50 | 51 | if technology.upgrade or is_multilevel or technology_prototype.level > 1 then 52 | base.add({ 53 | type = "label", 54 | name = "level_label", 55 | style = "flib_technology_slot_level_label_" .. research_state_str, 56 | caption = level, 57 | ignored_by_interaction = true, 58 | }) 59 | end 60 | if is_multilevel then 61 | local max_level = technology_prototype.max_level 62 | local max_level_str = max_level == flib_math.max_uint and "[img=infinity]" or tostring(max_level) 63 | base.add({ 64 | type = "label", 65 | name = "level_range_label", 66 | style = "flib_technology_slot_level_range_label_" .. research_state_str, 67 | caption = technology_prototype.level .. " - " .. max_level_str, 68 | ignored_by_interaction = true, 69 | }) 70 | end 71 | 72 | local ingredients_flow = base.add({ 73 | type = "flow", 74 | style = "flib_technology_slot_ingredients_flow", 75 | ignored_by_interaction = true, 76 | }) 77 | 78 | local ingredients = technology.research_unit_ingredients 79 | local ingredients_len = #ingredients 80 | for i = 1, ingredients_len do 81 | local ingredient = ingredients[i] 82 | ingredients_flow.add({ 83 | type = "sprite", 84 | style = "flib_technology_slot_ingredient", 85 | sprite = "item/" .. ingredient.name, 86 | ignored_by_interaction = true, 87 | }) 88 | end 89 | ingredients_flow.style.horizontal_spacing = flib_math.clamp((68 - 16) / (ingredients_len - 1) - 16, -15, -5) 90 | 91 | local progress = flib_technology.get_research_progress(technology, level) 92 | 93 | base.add({ 94 | type = "progressbar", 95 | name = "progressbar", 96 | style = "flib_technology_slot_progressbar", 97 | value = progress, 98 | visible = progress > 0, 99 | ignored_by_interaction = true, 100 | }) 101 | 102 | return base 103 | end 104 | 105 | return flib_gui_templates 106 | -------------------------------------------------------------------------------- /gui.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.gui" then 2 | return require("__flib__.gui") 3 | end 4 | 5 | --- Utilities for building GUIs and handling GUI events. 6 | --- ```lua 7 | --- local flib_gui = require("__flib__.gui") 8 | --- ``` 9 | --- @class flib_gui 10 | local flib_gui = {} 11 | 12 | local handler_tag_key = "__" .. script.mod_name .. "_handler" 13 | 14 | --- @type table 15 | local handlers = {} 16 | --- @type table 17 | local handlers_lookup = {} 18 | 19 | --- Add a new child or children to the given GUI element. 20 | --- @param parent LuaGuiElement The parent GUI element. 21 | --- @param def flib.GuiElemDef|flib.GuiElemDef[] The element definition, or an array of element definitions. 22 | --- @param elems table? Optional initial `elems` table. 23 | --- @return table elems Elements with names will be collected into this table. 24 | --- @return LuaGuiElement first The element that was created first; the "top level" element. 25 | function flib_gui.add(parent, def, elems) 26 | if not parent or not parent.valid then 27 | error("Parent element is missing or invalid") 28 | end 29 | if not elems then 30 | elems = {} 31 | end 32 | -- If a single def was passed, wrap it in an array 33 | if def.type or (def.tab and def.content) then 34 | def = { def } 35 | end 36 | local first 37 | for i = 1, #def do 38 | local def = def[i] 39 | if def.type then 40 | -- Remove custom attributes from the def so the game doesn't serialize them 41 | local children = def.children 42 | local elem_mods = def.elem_mods 43 | local handler = def.handler 44 | local style_mods = def.style_mods 45 | local drag_target = def.drag_target 46 | -- If children were defined in the array portion, remove and collect them 47 | local has_array_children = false 48 | if def[1] then 49 | if children then 50 | error("Cannot define children in array portion and subtable simultaneously") 51 | end 52 | has_array_children = true 53 | children = {} 54 | for i = 1, #def do 55 | children[i] = def[i] 56 | def[i] = nil 57 | end 58 | end 59 | def.children = nil 60 | def.elem_mods = nil 61 | def.handler = nil 62 | def.style_mods = nil 63 | def.drag_target = nil 64 | 65 | local elem = parent.add(def) 66 | 67 | if not first then 68 | first = elem 69 | end 70 | if def.name then 71 | elems[def.name] = elem 72 | end 73 | if style_mods then 74 | for key, value in pairs(style_mods) do 75 | elem.style[key] = value 76 | end 77 | end 78 | if elem_mods then 79 | for key, value in pairs(elem_mods) do 80 | elem[key] = value 81 | end 82 | end 83 | if drag_target then 84 | local target = elems[drag_target] 85 | if not target then 86 | error("Drag target '" .. drag_target .. "' not found.") 87 | end 88 | elem.drag_target = target 89 | end 90 | if handler then 91 | local out 92 | if type(handler) == "table" then 93 | out = {} 94 | for name, handler in pairs(handler) do 95 | out[tostring(name)] = handlers[handler] 96 | end 97 | else 98 | out = handlers[handler] 99 | end 100 | local tags = elem.tags 101 | tags[handler_tag_key] = out 102 | elem.tags = tags 103 | end 104 | if children then 105 | flib_gui.add(elem, children, elems) 106 | end 107 | 108 | -- Re-add custom attributes 109 | if children and has_array_children then 110 | for i = 1, #children do 111 | def[i] = children[i] 112 | end 113 | else 114 | def.children = children 115 | end 116 | def.elem_mods = elem_mods 117 | def.handler = handler 118 | def.style_mods = style_mods 119 | def.drag_target = drag_target 120 | elseif def.tab and def.content then 121 | local _, tab = flib_gui.add(parent, def.tab, elems) 122 | local _, content = flib_gui.add(parent, def.content, elems) 123 | parent.add_tab(tab, content) 124 | end 125 | end 126 | return elems, first 127 | end 128 | 129 | --- Add the given handler functions to the registry for use with `flib_gui.add`. Each handler must have a unique name. If a 130 | --- `wrapper` function is provided, it will be called instead, and will receive the event data and handler. The wrapper 131 | --- can be used to execute logic or gather data common to all handler functions for this GUI. 132 | --- @param new_handlers table 133 | --- @param wrapper fun(e: flib.GuiEventData, handler: function)? 134 | --- @param prefix string? 135 | function flib_gui.add_handlers(new_handlers, wrapper, prefix) 136 | for name, handler in pairs(new_handlers) do 137 | if prefix then 138 | name = prefix .. "/" .. name 139 | end 140 | if type(handler) == "function" then 141 | if handlers_lookup[name] then 142 | error("Attempted to register two GUI event handlers with the same name: " .. name) 143 | end 144 | handlers[handler] = name 145 | if wrapper then 146 | handlers_lookup[name] = function(e) 147 | wrapper(e, handler) 148 | end 149 | else 150 | handlers_lookup[name] = handler 151 | end 152 | end 153 | end 154 | end 155 | 156 | --- Dispatch the handler associated with this event and GUI element. The handler must have been added using 157 | --- `flib_gui.add_handlers`. 158 | --- @param e flib.GuiEventData 159 | --- @return boolean handled True if an event handler was called. 160 | function flib_gui.dispatch(e) 161 | local elem = e.element 162 | if not elem then 163 | return false 164 | end 165 | local tags = elem.tags --[[@as Tags]] 166 | local handler_def = tags[handler_tag_key] 167 | if not handler_def then 168 | return false 169 | end 170 | local handler_type = type(handler_def) 171 | if handler_type == "table" then 172 | handler_def = handler_def[tostring(e.name)] 173 | end 174 | if handler_def then 175 | local handler = handlers_lookup[handler_def] 176 | if handler then 177 | handler(e) 178 | return true 179 | end 180 | end 181 | return false 182 | end 183 | 184 | --- For use with `__core__/lualib/event_handler`. Pass `flib_gui` into `handler.add_lib` to handle 185 | --- all GUI events automatically. 186 | flib_gui.events = {} 187 | for name, id in pairs(defines.events) do 188 | if string.find(name, "on_gui_") then 189 | flib_gui.events[id] = flib_gui.dispatch 190 | end 191 | end 192 | 193 | --- Handle all GUI events with `flib_gui.dispatch`. Will not overwrite any existing event handlers. 194 | function flib_gui.handle_events() 195 | for id in pairs(flib_gui.events) do 196 | if not script.get_event_handler(id) then 197 | script.on_event(id, flib_gui.dispatch) 198 | end 199 | end 200 | end 201 | 202 | --- Format the given handlers for use in a GUI element's tags. An alternative to using `flib_gui.add` if event handling 203 | --- is the only desired feature. 204 | --- 205 | --- ### Example 206 | --- 207 | --- ```lua 208 | --- --- @param e EventData.on_gui_click 209 | --- local function on_button_clicked(e) 210 | --- game.print("You clicked it!") 211 | --- end 212 | --- 213 | --- player.gui.screen.add({ 214 | --- type = "button", 215 | --- caption = "Click me!", 216 | --- tags = flib_gui.format_handlers({ [defines.events.on_gui_click] = on_button_clicked }), 217 | --- }) 218 | --- 219 | --- flib_gui.handle_events({ on_button_clicked = on_button_clicked }) 220 | --- ``` 221 | --- @param input flib.GuiElemHandler|table 222 | --- @param existing Tags? 223 | --- @return Tags 224 | function flib_gui.format_handlers(input, existing) 225 | local out 226 | if type(input) == "table" then 227 | out = {} 228 | for name, handler in pairs(input) do 229 | out[tostring(name)] = handlers[handler] 230 | end 231 | else 232 | out = handlers[input] 233 | end 234 | if existing then 235 | existing[handler_tag_key] = out 236 | return existing 237 | end 238 | return { [handler_tag_key] = out } 239 | end 240 | 241 | --- A GUI element definition. This extends `LuaGuiElement.add_param` with several new attributes. 242 | --- Children may be defined in the array portion as an alternative to the `children` subtable. 243 | --- @class flib.GuiElemDef: LuaGuiElement.add_param.button|LuaGuiElement.add_param.camera|LuaGuiElement.add_param.checkbox|LuaGuiElement.add_param.choose_elem_button|LuaGuiElement.add_param.drop_down|LuaGuiElement.add_param.flow|LuaGuiElement.add_param.frame|LuaGuiElement.add_param.line|LuaGuiElement.add_param.list_box|LuaGuiElement.add_param.minimap|LuaGuiElement.add_param.progressbar|LuaGuiElement.add_param.radiobutton|LuaGuiElement.add_param.scroll_pane|LuaGuiElement.add_param.slider|LuaGuiElement.add_param.sprite|LuaGuiElement.add_param.sprite_button|LuaGuiElement.add_param.switch|LuaGuiElement.add_param.tab|LuaGuiElement.add_param.table|LuaGuiElement.add_param.text_box|LuaGuiElement.add_param.textfield 244 | --- @field style_mods LuaStyle? Modifications to make to the element's style. 245 | --- @field elem_mods LuaGuiElement? Modifications to make to the element itself. 246 | --- @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. 247 | --- @field handler (flib.GuiElemHandler|table)? Handler(s) to assign to this element. If assigned to a function, that function will be called for any GUI event on this element. 248 | --- @field children flib.GuiElemDef[]? Children to add to this element. 249 | --- @field tab flib.GuiElemDef? To add a tab, specify `tab` and `content` and leave all other fields unset. 250 | --- @field content flib.GuiElemDef? To add a tab, specify `tab` and `content` and leave all other fields unset. 251 | 252 | --- A handler function to invoke when receiving GUI events for this element. 253 | --- @alias flib.GuiElemHandler fun(e: flib.GuiEventData) 254 | 255 | --- Aggregate type of all possible GUI events. 256 | --- @alias flib.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 257 | 258 | return flib_gui 259 | -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flib", 3 | "version": "0.16.4", 4 | "title": "Factorio Library", 5 | "author": "raiguard, Optera, justarandomgeek, Nexela", 6 | "contact": "https://github.com/factoriolib/flib", 7 | "homepage": "https://github.com/factoriolib/flib", 8 | "description": "A set of high-quality, commonly-used utilities for creating Factorio mods.", 9 | "factorio_version": "2.0", 10 | "dependencies": [ "base >= 2.0.0" ], 11 | "package": { 12 | "git_publish_branch": "master", 13 | "ignore": [ "crowdin.yml", "imgui.ini", "stylua.toml", "tests", "graphics/slots.xcf" ], 14 | "scripts": { 15 | "prepublish": "factorio-crowdin-sync" 16 | }, 17 | "sync_portal_details": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /locale.lua: -------------------------------------------------------------------------------- 1 | local flib_prototypes = require("__flib__.prototypes") 2 | 3 | --- Provides utilities for deducing the localised names of various prototypes in the prototype stage. 4 | --- Inspired by Rusty's Locale Utilities: https://github.com/theRustyKnife/rusty-locale 5 | --- @class flib_locale 6 | local flib_locale = {} 7 | 8 | --- Returns the localised name of the given prototype. 9 | --- @overload fun(prototype: data.PrototypeBase): data.LocalisedString 10 | --- @overload fun(base_type: string, name: string): data.LocalisedString 11 | function flib_locale.of(prototype, name) 12 | -- In this case, `prototype` is actually `base_type`. 13 | if type(prototype) == "string" then 14 | return flib_locale.of(flib_prototypes.get(prototype, name) --[[@as data.PrototypeBase]]) 15 | end 16 | if prototype.type == "recipe" then 17 | return flib_locale.of_recipe(prototype --[[@as data.RecipePrototype]]) 18 | elseif defines.prototypes.item[prototype.type] then 19 | return flib_locale.of_item(prototype --[[@as data.ItemPrototype]]) 20 | else 21 | return prototype.localised_name or { flib_prototypes.get_base_type(prototype.type) .. "-name." .. prototype.name } 22 | end 23 | end 24 | 25 | --- Returns the localised name of the given item. 26 | --- @param item data.ItemPrototype 27 | --- @return data.LocalisedString 28 | function flib_locale.of_item(item) 29 | if not defines.prototypes.item[item.type] then 30 | error("Given prototype is not an item: " .. serpent.block(item)) 31 | end 32 | if item.localised_name then 33 | return item.localised_name 34 | end 35 | local type_name = "item" 36 | --- @type data.PrototypeBase? 37 | local prototype 38 | if item.place_result then 39 | type_name = "entity" 40 | prototype = flib_prototypes.get("entity", item.place_result) --[[@as data.PrototypeBase]] 41 | elseif item.place_as_equipment_result then 42 | type_name = "equipment" 43 | prototype = flib_prototypes.get("equipment", item.place_as_equipment_result) --[[@as data.PrototypeBase]] 44 | elseif item.place_as_tile then 45 | local tile_prototype = data.raw.tile[item.place_as_tile.result] 46 | -- Tiles with variations don't have a localised name 47 | if tile_prototype and tile_prototype.localised_name then 48 | prototype = tile_prototype 49 | type_name = "tile" 50 | end 51 | end 52 | return prototype and prototype.localised_name or { type_name .. "-name." .. item.name } 53 | end 54 | 55 | --- Returns the localised name of the given recipe. 56 | --- @param recipe data.RecipePrototype 57 | --- @return data.LocalisedString 58 | function flib_locale.of_recipe(recipe) 59 | if recipe.type ~= "recipe" then 60 | error("Given prototype is not an recipe: " .. serpent.block(recipe)) 61 | end 62 | if recipe.localised_name then 63 | return recipe.localised_name 64 | end 65 | local main_product = recipe.main_product -- LuaLS gets confused if we don't assign to a local. 66 | if main_product == "" then 67 | return { "recipe-name." .. recipe.name } 68 | elseif main_product and main_product == recipe.name then 69 | return flib_locale.of_item(flib_prototypes.get("item", main_product)) 70 | end 71 | local results = recipe.results 72 | if results and #results == 1 and results[1].name == recipe.name then 73 | return flib_locale.of_item(flib_prototypes.get("item", results[1].name)) 74 | end 75 | return { "recipe-name." .. recipe.name } 76 | end 77 | 78 | return flib_locale 79 | -------------------------------------------------------------------------------- /locale/af/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=af 2 | locale-name=Afrikaans 3 | 4 | -------------------------------------------------------------------------------- /locale/af/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/ar/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ar 2 | locale-name=العَرَبِيَّة 3 | 4 | -------------------------------------------------------------------------------- /locale/ar/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-settings=الإعدادات 3 | 4 | [mod-name] 5 | flib=مكتبة Factorio 6 | 7 | -------------------------------------------------------------------------------- /locale/be/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=be 2 | locale-name=Беларуская 3 | -------------------------------------------------------------------------------- /locale/bg/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=bg 2 | locale-name=български език 3 | -------------------------------------------------------------------------------- /locale/ca/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ca 2 | locale-name=Català 3 | 4 | -------------------------------------------------------------------------------- /locale/ca/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Conserva obert 3 | flib-search-instruction=Cerca (__CONTROL__focus-search__) 4 | flib-settings=Configuració 5 | flib-finishing=Acabant... 6 | flib-translating-dictionaries=Traducció de diccionaris [img=info] 7 | flib-translating-dictionaries-description=Els mods que utilitzen el sistema de diccionari de la Llibreria Factorio necessiten traduccions al llarg del temps per a permetre-vos fer cerques de text en el vostre idioma. Aquesta interfície mostra el progrés de traducció de cada mod per a cada idioma, i desapareix quan totes les traduccions estiguin acabades. 8 | 9 | [mod-name] 10 | flib=Llibreria Factorio 11 | 12 | -------------------------------------------------------------------------------- /locale/cs/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=cs 2 | locale-name=Čeština 3 | 4 | -------------------------------------------------------------------------------- /locale/cs/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Ponechat otevřené 3 | flib-search-instruction=Hledat (__CONTROL__focus-search__) 4 | flib-settings=Nastavení 5 | flib-finishing=Dokončuji... 6 | flib-translating-dictionaries=Překlad slovníků [img=info] 7 | flib-translating-dictionaries-description=Mody, které používají Factorio Library's slovníkový systém vyžadoval překlady v průběhu času, aby bylo možné vyhledávat text ve vašem rodném jazyce. Toto GUI zobrazuje průběh překladů každého modu pro každý požadovaný jazyk. Toto GUI se automaticky odstraní po dokončení všech překladů. 8 | 9 | [mod-name] 10 | flib=Knihovna Factoria 11 | 12 | -------------------------------------------------------------------------------- /locale/da/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=da 2 | locale-name=Dansk 3 | 4 | -------------------------------------------------------------------------------- /locale/da/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/de/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=de 2 | locale-name=Deutsch 3 | 4 | -------------------------------------------------------------------------------- /locale/de/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Offen bleiben 3 | flib-search-instruction=Suiche (__CONTROL__focus-search__) 4 | flib-settings=Einstellungen 5 | flib-finishing=Abschließen... 6 | flib-translating-dictionaries=Übersetze Wörterbücher [img=info] 7 | flib-translating-dictionaries-description=Mods, die das Wörterbuchsystem der Factorio Library verwenden, fordern Übersetzungen im Laufe der Zeit an, um Suchen in deiner 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. 8 | 9 | [mod-name] 10 | flib=Factorio Library 11 | 12 | -------------------------------------------------------------------------------- /locale/el/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=el 2 | locale-name=Ελληνικά 3 | 4 | -------------------------------------------------------------------------------- /locale/el/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/en/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=en 2 | locale-name=English 3 | -------------------------------------------------------------------------------- /locale/en/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | # For use with the frame action button sprites 3 | flib-keep-open=Keep open 4 | flib-search-instruction=Search (__CONTROL__focus-search__) 5 | flib-settings=Settings 6 | # Translation progress GUI 7 | flib-finishing=Finishing... 8 | flib-translating-dictionaries=Translating dictionaries [img=info] 9 | 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. 10 | 11 | [mod-name] 12 | flib=Factorio Library 13 | -------------------------------------------------------------------------------- /locale/eo/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=eo 2 | locale-name=Esperanto 3 | -------------------------------------------------------------------------------- /locale/es-ES/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=es-ES 2 | locale-name=Español 3 | 4 | -------------------------------------------------------------------------------- /locale/es-ES/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Mantener abierto 3 | flib-search-instruction=Buscar (__CONTROL__focus-search__) 4 | flib-settings=Ajustes 5 | flib-finishing=Finalizando... 6 | flib-translating-dictionaries=Traduciendo diccionarios [img=info] 7 | flib-translating-dictionaries-description=Los mods que usan el sistema de diccionarios de la Librería de Factorio solicitan traducciones a lo largo del tiempo para permitir la búsqueda por texto en el idioma preferido. Esta ventana muestra el progreso de la traducción de cada mod para cada lenguaje requerido. La ventana se removerá automáticamente cuando todas las traducciones hayan sido finalizadas. 8 | 9 | [mod-name] 10 | flib=Librería de Factorio 11 | 12 | -------------------------------------------------------------------------------- /locale/et/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=et 2 | locale-name=Eesti keel 3 | 4 | -------------------------------------------------------------------------------- /locale/et/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Hoia avatud 3 | flib-search-instruction=Otsi (__CONTROL__focus-search__) 4 | flib-settings=Sätted 5 | flib-finishing=Lõpetab... 6 | flib-translating-dictionaries=Tõlgin sõnaraamatuid [img=info] 7 | flib-translating-dictionaries-description=Modidele, mis kasutavad Factorio Teekide sõnaraamatusüsteemi, tuleb pidevalt oma emakeeles tekstiotsinguks tõlkeid lisada. See kasutajaliides näitab igas vajatud keeles iga modi tõlgete kulge. See kasutajaliides eemaldub automaatselt, kui kõik tõlked on lõpule viidud. 8 | 9 | [mod-name] 10 | flib=Factorio Teek 11 | 12 | -------------------------------------------------------------------------------- /locale/fi/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=fi 2 | locale-name=Suomi 3 | 4 | -------------------------------------------------------------------------------- /locale/fi/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/fr/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=fr 2 | locale-name=Français 3 | 4 | -------------------------------------------------------------------------------- /locale/fr/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Garder ouvert 3 | flib-search-instruction=Rechercher (__CONTROL__focus-search__) 4 | flib-settings=Paramètres 5 | flib-finishing=Finalisation... 6 | flib-translating-dictionaries=Traduction des dictionnaires [img=info] 7 | flib-translating-dictionaries-description=Les mods qui utilisent le système de dictionnaire de "Factorio Library" demandent des traductions au fil du temps afin de permettre la recherche de texte dans votre langue maternelle. Cette interface montre la progression des traductions de chaque mod pour chaque langue requise. Cette interface se supprimera automatiquement une fois toutes les traductions terminées. 8 | 9 | [mod-name] 10 | flib=Factorio Library 11 | 12 | -------------------------------------------------------------------------------- /locale/fy-NL/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=fy-NL 2 | locale-name=Frisian 3 | -------------------------------------------------------------------------------- /locale/ga-IE/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ga-IE 2 | locale-name=Gaeilge 3 | -------------------------------------------------------------------------------- /locale/he/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=he 2 | locale-name=עברית 3 | 4 | -------------------------------------------------------------------------------- /locale/he/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/hr/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=hr 2 | locale-name=Hrvatski 3 | -------------------------------------------------------------------------------- /locale/hu/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=hu 2 | locale-name=Magyar 3 | 4 | -------------------------------------------------------------------------------- /locale/hu/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/id/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=id 2 | locale-name=Bahasa Indonesia 3 | -------------------------------------------------------------------------------- /locale/it/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=it 2 | locale-name=Italiano 3 | 4 | -------------------------------------------------------------------------------- /locale/it/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/ja/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ja 2 | locale-name=日本語 3 | 4 | -------------------------------------------------------------------------------- /locale/ja/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=開いたままにする 3 | flib-search-instruction=検索 (__CONTROL__focus-search__) 4 | flib-settings=設定 5 | flib-finishing=終了しています... 6 | flib-translating-dictionaries=辞書の翻訳中 [img=info] 7 | flib-translating-dictionaries-description=Factorio Libraryの辞書システムを使用するMODは、あなたの母語でのテキスト検索を可能にするために時間をかけて翻訳を要求します。このGUIは各MODの要求された各言語の翻訳の進捗を表示しています。このGUIはすべての翻訳が終了すると自動的に消滅します。 8 | 9 | [mod-name] 10 | flib=Factorio Library 11 | 12 | -------------------------------------------------------------------------------- /locale/ko/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ko 2 | locale-name=한국어 3 | 4 | -------------------------------------------------------------------------------- /locale/ko/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/lt/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=lt 2 | locale-name=Lietuvių 3 | -------------------------------------------------------------------------------- /locale/lv/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=lv 2 | locale-name=Latviešu 3 | -------------------------------------------------------------------------------- /locale/nl/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=nl 2 | locale-name=Nederlands 3 | 4 | -------------------------------------------------------------------------------- /locale/nl/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Houd open 3 | flib-search-instruction=Zoeken (__CONTROL__focus-search__) 4 | flib-settings=Instellingen 5 | flib-finishing=Bezig met afronden... 6 | flib-translating-dictionaries=Woordenboekvertaling uitvoeren [img=info] 7 | flib-translating-dictionaries-description=Mods die het woordenboekvertalingsysteem van de Factorio-bibliotheek gebruiken, vragen na verloop van tijd om vertalingen om tekstzoekopdrachten in uw moedertaal mogelijk te maken. Deze gebruikersinterface toont de voortgang van de vertalingen voor elke mod in elke vereiste taal. Deze gebruikersinterface verwijdert zichzelf automatisch zodra alle vertalingen voltooid zijn. 8 | 9 | [mod-name] 10 | 11 | -------------------------------------------------------------------------------- /locale/no/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=no 2 | locale-name=Norsk 3 | 4 | -------------------------------------------------------------------------------- /locale/no/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/pl/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=pl 2 | locale-name=Polski 3 | 4 | -------------------------------------------------------------------------------- /locale/pl/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Pozostaw otwarte 3 | flib-search-instruction=Szukaj (__CONTROL__focus-search__) 4 | flib-settings=Ustawienia 5 | flib-finishing=Kończenie... 6 | flib-translating-dictionaries=Tłumaczenie słowników [img=info] 7 | flib-translating-dictionaries-description=Modyfikacje, które korzystają z systemu słowników Biblioteki Factorio, wymagają czasu na wykonanie tłumaczeń, żeby umożliwiać wyszukiwanie tekstu w twoim ojczystym języku. To menu pokazuje postępy w tłumaczeniu każdego moda na każdy z potrzebnych języków. To menu zniknie automatycznie po zakończeniu wszystkich tłumaczeń. 8 | 9 | [mod-name] 10 | flib=Biblioteka Factorio 11 | 12 | -------------------------------------------------------------------------------- /locale/pt-BR/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=pt-BR 2 | locale-name=Português, Brasil 3 | -------------------------------------------------------------------------------- /locale/pt-BZ/dictionary.cfg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /locale/pt-BZ/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/pt-PT/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=pt-PT 2 | locale-name=Português 3 | 4 | -------------------------------------------------------------------------------- /locale/pt-PT/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Manter aberto 3 | flib-settings=Configurações 4 | 5 | [mod-name] 6 | 7 | -------------------------------------------------------------------------------- /locale/ro/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ro 2 | locale-name=Română 3 | 4 | -------------------------------------------------------------------------------- /locale/ro/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/ru/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=ru 2 | locale-name=Русский 3 | 4 | -------------------------------------------------------------------------------- /locale/ru/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Держать открытым 3 | flib-search-instruction=Поиск (__CONTROL__focus-search__) 4 | flib-settings=Настройки 5 | flib-finishing=Завершение... 6 | flib-translating-dictionaries=Перевод словарей [img=info] 7 | flib-translating-dictionaries-description=Моды, использующие словарь библиотеки Factorio, с течением времени запрашивают переводы, чтобы разрешить поиск текста на вашем родном языке. Этот графический интерфейс показывает прогресс перевода каждого мода для каждого требуемого языка. Этот интерфейс автоматически удалит себя, как только все переводы закончатся. 8 | 9 | [mod-name] 10 | flib=Factorio Library (Библиотека Factorio) 11 | 12 | -------------------------------------------------------------------------------- /locale/sk/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=sk 2 | locale-name=Slovenčina 3 | -------------------------------------------------------------------------------- /locale/sl/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=sl 2 | locale-name=Slovenščina 3 | -------------------------------------------------------------------------------- /locale/sq/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=sq 2 | locale-name=Shqip 3 | -------------------------------------------------------------------------------- /locale/sr/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=sr 2 | locale-name=Српски 3 | 4 | -------------------------------------------------------------------------------- /locale/sr/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/sv-SE/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=sv-SE 2 | locale-name=Svenska 3 | 4 | -------------------------------------------------------------------------------- /locale/sv-SE/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/th/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=th 2 | locale-name=ภาษาไทย 3 | -------------------------------------------------------------------------------- /locale/tr/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=tr 2 | locale-name=Türkçe 3 | 4 | -------------------------------------------------------------------------------- /locale/tr/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Açık bırakın 3 | flib-search-instruction=Ara (__CONTROL__focus-search__) 4 | flib-settings=Ayarlar 5 | flib-finishing=Sonlandırılıyor... 6 | flib-translating-dictionaries=Sözlükler çeviriliyor [img=info] 7 | flib-translating-dictionaries-description=Factiorio Library modunun sözlük sistemini kullanan modlar yazılı arama yapabilmek için zamanla çeviri isteği yollar. Bu Grafik Arayüzü yüklü olan her dil için çevirilen modların ilerlemesini görterir. Çevirim tamamlanınca bu arayüz kendi kendini kapatacaktır. 8 | 9 | [mod-name] 10 | flib=Factorio Kütüphanesi 11 | 12 | -------------------------------------------------------------------------------- /locale/uk/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=uk 2 | locale-name=Українська 3 | 4 | -------------------------------------------------------------------------------- /locale/uk/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=Залишити відкритими 3 | flib-search-instruction=Пошук (__CONTROL__focus-search__) 4 | flib-settings=Налаштування 5 | flib-finishing=Завершення... 6 | flib-translating-dictionaries=Переклад словників [img=info] 7 | flib-translating-dictionaries-description=Моди, які використовують словникову систему бібліотеки Factorio, з часом запитують переклади, щоб забезпечити пошук тексту рідною мовою. Цей графічний інтерфейс показує хід перекладу кожної модифікації для кожної необхідної мови. Цей графічний інтерфейс автоматично зникає після завершення всіх перекладів. 8 | 9 | [mod-name] 10 | flib=Бібліотека Factorio 11 | 12 | -------------------------------------------------------------------------------- /locale/vi/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=vi 2 | locale-name=Tiếng Việt Nam 3 | 4 | -------------------------------------------------------------------------------- /locale/vi/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /locale/zh-CN/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=zh-CN 2 | locale-name=简体中文 3 | 4 | -------------------------------------------------------------------------------- /locale/zh-CN/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | flib-keep-open=保持开启 3 | flib-search-instruction=搜索 (__CONTROL__focus-search__)  4 | flib-settings=设置 5 | flib-finishing=正在完成…… 6 | flib-translating-dictionaries=正在翻译字典[img=info] 7 | flib-translating-dictionaries-description=为允许以您的母语进行文本搜索,依赖异星工厂库字典系统的模组会发出翻译请求。该窗口会显示每个模组对每种所需语言的翻译进度。所有翻译完成后,窗口会自动移除。 8 | 9 | [mod-name] 10 | flib=异星工厂库 11 | 12 | -------------------------------------------------------------------------------- /locale/zh-TW/dictionary.cfg: -------------------------------------------------------------------------------- 1 | locale-identifier=zh-TW 2 | locale-name=繁體中文 3 | 4 | -------------------------------------------------------------------------------- /locale/zh-TW/flib.cfg: -------------------------------------------------------------------------------- 1 | [gui] 2 | 3 | [mod-name] 4 | 5 | -------------------------------------------------------------------------------- /math.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.math" then 2 | return require("__flib__.math") 3 | end 4 | 5 | --- Extension of the Lua 5.2 math library. 6 | --- ```lua 7 | --- local flib_math = require("__flib__.math") 8 | --- ``` 9 | --- @class flib_math: factorio.mathlib 10 | local flib_math = {} 11 | 12 | local unpack = table.unpack 13 | 14 | -- Import lua math functions 15 | for name, func in pairs(math) do 16 | flib_math[name] = func 17 | end 18 | 19 | --- Multiply by degrees to convert to radians. 20 | --- ```lua 21 | --- local rad = 1 x flib_math.deg_to_rad -- 0.0174533 22 | --- ``` 23 | flib_math.deg_to_rad = flib_math.pi / 180 --- @type number 24 | 25 | --- Multiply by radians to convert to degrees. 26 | --- 27 | --- ```lua 28 | --- local deg = 1 x flib_math.rad_to_deg -- 57.2958 29 | --- ``` 30 | flib_math.rad_to_deg = 180 / flib_math.pi --- @type number 31 | 32 | flib_math.max_double = 0X1.FFFFFFFFFFFFFP+1023 33 | flib_math.min_double = -0X1.FFFFFFFFFFFFFP+1023 34 | flib_math.max_int8 = 127 --- 127 35 | flib_math.min_int8 = -128 --- -128 36 | flib_math.max_uint8 = 255 --- 255 37 | flib_math.max_int16 = 32767 --- 32,767 38 | flib_math.min_int16 = -32768 --- -32,768 39 | flib_math.max_uint16 = 65535 --- 65,535 40 | flib_math.max_int = 2147483647 --- 2,147,483,647 41 | flib_math.min_int = -2147483648 --- -2,147,483,648 42 | flib_math.max_uint = 4294967295 --- 4,294,967,295 43 | flib_math.max_int53 = 0x1FFFFFFFFFFFFF --- 9,007,199,254,740,991 44 | flib_math.min_int53 = -0x20000000000000 --- -9,007,199,254,740,992 45 | 46 | --- Round a number to the nearest multiple of divisor. 47 | --- Defaults to nearest integer if divisor is not provided. 48 | --- 49 | --- From [lua-users.org](http://lua-users.org/wiki/SimpleRound). 50 | --- @param num number 51 | --- @param divisor? number `num` will be rounded to the nearest multiple of `divisor` (default: 1). 52 | --- @return number 53 | function flib_math.round(num, divisor) 54 | divisor = divisor or 1 55 | if num >= 0 then 56 | return flib_math.floor((num / divisor) + 0.5) * divisor 57 | else 58 | return flib_math.ceil((num / divisor) - 0.5) * divisor 59 | end 60 | end 61 | 62 | --- Ceil a number to the nearest multiple of divisor. 63 | --- @param num number 64 | --- @param divisor? number `num` will be ceiled to the nearest multiple of `divisor` (default: 1). 65 | function flib_math.ceiled(num, divisor) 66 | if divisor then 67 | return flib_math.ceil(num / divisor) * divisor 68 | end 69 | return flib_math.ceil(num) 70 | end 71 | 72 | --- Floor a number to the nearest multiple of divisor. 73 | --- @param num number 74 | --- @param divisor? number `num` will be floored to the nearest multiple of `divisor` (default: 1). 75 | function flib_math.floored(num, divisor) 76 | if divisor then 77 | return flib_math.floor(num / divisor) * divisor 78 | end 79 | return flib_math.floor(num) 80 | end 81 | 82 | --- Returns the argument with the maximum value from a set. 83 | --- @param set number[] 84 | --- @return number 85 | function flib_math.maximum(set) 86 | return flib_math.max(unpack(set)) 87 | end 88 | 89 | --- Returns the argument with the minimum value from a set. 90 | --- @param set number[] 91 | --- @return number 92 | function flib_math.minimum(set) 93 | return flib_math.min(unpack(set)) 94 | end 95 | 96 | --- Calculate the sum of a set of numbers. 97 | --- @param set number[] 98 | --- @return number 99 | function flib_math.sum(set) 100 | local sum = set[1] or 0 101 | for i = 2, #set do 102 | sum = sum + set[i] 103 | end 104 | return sum 105 | end 106 | 107 | --- Calculate the mean (average) of a set of numbers. 108 | --- @param set number[] 109 | --- @return number 110 | function flib_math.mean(set) 111 | return flib_math.sum(set) / #set 112 | end 113 | 114 | --- Calculate the mean of the largest and the smallest values in a set of numbers. 115 | --- @param set number[] 116 | --- @return number 117 | function flib_math.midrange(set) 118 | return 0.5 * (flib_math.minimum(set) + flib_math.maximum(set)) 119 | end 120 | 121 | --- Calculate the range in a set of numbers. 122 | --- @param set number[] 123 | --- @return number 124 | function flib_math.range(set) 125 | return flib_math.maximum(set) - flib_math.minimum(set) 126 | end 127 | 128 | --- Clamp a number between minimum and maximum values. 129 | --- @param x number 130 | --- @param min? number default 0 131 | --- @param max? number default 1 132 | --- @return number 133 | function flib_math.clamp(x, min, max) 134 | x = x == 0 and 0 or x -- Treat -0 as 0 135 | min, max = min or 0, max or 1 136 | return x < min and min or (x > max and max or x) 137 | end 138 | 139 | --- Return the signedness of a number as a multiplier. 140 | --- @param x number 141 | --- @return number 142 | function flib_math.sign(x) 143 | return (x >= 0 and 1) or -1 144 | end 145 | 146 | --- Linearly interpolate between `num1` and `num2` by `amount`. 147 | --- 148 | --- The parameter `amount` is clamped between `0` and `1`. 149 | --- 150 | --- When `amount = 0`, returns `num1`. 151 | --- 152 | --- When `amount = 1`, returns `num2`. 153 | --- 154 | --- When `amount = 0.5`, returns the midpoint of `num1` and `num2`. 155 | --- @param num1 number 156 | --- @param num2 number 157 | --- @param amount number 158 | --- @return number 159 | function flib_math.lerp(num1, num2, amount) 160 | return num1 + (num2 - num1) * flib_math.clamp(amount, 0, 1) 161 | end 162 | 163 | return flib_math 164 | -------------------------------------------------------------------------------- /migration.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.migration" then 2 | return require("__flib__.migration") 3 | end 4 | 5 | --- Mod migration and version comparison functions. 6 | --- ```lua 7 | --- local flib_migration = require("__flib__.migration") 8 | --- ``` 9 | --- @class flib_migration 10 | local flib_migration = {} 11 | 12 | local string = string 13 | local table = table 14 | 15 | local version_pattern = "%d+" 16 | local version_format = "%02d" 17 | 18 | --- Normalize version strings for easy comparison. 19 | --- 20 | --- ### Examples 21 | --- 22 | --- ```lua 23 | --- migration.format_version("1.10.1234", "%04d") 24 | --- migration.format_version("3", "%02d") 25 | --- ``` 26 | --- @param version string 27 | --- @param format string? default: `%02d` 28 | --- @return string? 29 | function flib_migration.format_version(version, format) 30 | if version then 31 | format = format or version_format 32 | local tbl = {} 33 | for v in string.gmatch(version, version_pattern) do 34 | tbl[#tbl + 1] = string.format(format, v) 35 | end 36 | if next(tbl) then 37 | return table.concat(tbl, ".") 38 | end 39 | end 40 | return nil 41 | end 42 | 43 | --- Check if current_version is newer than old_version. 44 | --- @param old_version string 45 | --- @param current_version string 46 | --- @param format string? default: `%02d` 47 | --- @return boolean? 48 | function flib_migration.is_newer_version(old_version, current_version, format) 49 | local v1 = flib_migration.format_version(old_version, format) 50 | local v2 = flib_migration.format_version(current_version, format) 51 | if v1 and v2 then 52 | if v2 > v1 then 53 | return true 54 | end 55 | return false 56 | end 57 | return nil 58 | end 59 | 60 | --- Run migrations against the given version. 61 | --- @param old_version string 62 | --- @param migrations MigrationsTable 63 | --- @param format? string default: `%02d` 64 | --- @param ... any All additional arguments will be passed to each function within `migrations`. 65 | function flib_migration.run(old_version, migrations, format, ...) 66 | local migrate = false 67 | for version, func in pairs(migrations) do 68 | if migrate or flib_migration.is_newer_version(old_version, version, format) then 69 | migrate = true 70 | func(...) 71 | end 72 | end 73 | end 74 | 75 | --- Determine if migrations need to be run for this mod, then run them if needed. 76 | --- 77 | --- ### Examples 78 | --- 79 | --- ```lua 80 | --- script.on_configuration_changed(function(e) 81 | --- if migration.on_config_changed(e, migrations) then 82 | --- -- Run generic (non-init) migrations 83 | --- rebuild_prototype_data() 84 | --- end 85 | --- end 86 | --- ``` 87 | --- @param e ConfigurationChangedData 88 | --- @param migrations? MigrationsTable 89 | --- @param mod_name? string The mod to check against. Defaults to the current mod. 90 | --- @param ... any All additional arguments will be passed to each function within `migrations`. 91 | --- @return boolean run_generic_micrations 92 | function flib_migration.on_config_changed(e, migrations, mod_name, ...) 93 | local changes = e.mod_changes[mod_name or script.mod_name] 94 | local old_version = changes and changes.old_version 95 | if old_version then 96 | if migrations then 97 | flib_migration.run(old_version, migrations, nil, ...) 98 | end 99 | return true 100 | end 101 | return false 102 | end 103 | 104 | --- Handle on_configuration_changed with the given generic and version-specific migrations. Will override any existing 105 | --- on_configuration_changed event handler. Both arguments are optional. 106 | --- @param version_migrations MigrationsTable? 107 | --- @param generic_handler fun(e: ConfigurationChangedData)? 108 | function flib_migration.handle_on_configuration_changed(version_migrations, generic_handler) 109 | script.on_configuration_changed(function(e) 110 | if flib_migration.on_config_changed(e, version_migrations) and generic_handler then 111 | generic_handler(e) 112 | end 113 | end) 114 | end 115 | 116 | return flib_migration 117 | 118 | --- Migration code to run for specific mod version. A given function will run if the previous mod version is less 119 | --- than the given version. 120 | --- 121 | --- # Example 122 | --- 123 | --- ```lua 124 | --- { 125 | --- ["1.0.1"] = function() 126 | --- storage.foo = nil 127 | --- for _, player_table in pairs(storage.players) do 128 | --- player_table.bar = "Lorem ipsum" 129 | --- end 130 | --- end, 131 | --- ["1.0.7"] = function() 132 | --- storage.bar = {} 133 | --- end 134 | --- ["1.1.0"] = function(arg) 135 | --- storage.foo = arg 136 | --- end 137 | --- } 138 | --- ``` 139 | --- 140 | --- 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. 141 | --- @alias MigrationsTable table 142 | -------------------------------------------------------------------------------- /on-tick-n.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.on-tick-n" then 2 | return require("__flib__.on-tick-n") 3 | end 4 | 5 | --- Schedule tasks to be executed later. 6 | --- ```lua 7 | --- local flib_on_tick_n = require("__flib__.on-tick-n") 8 | --- ``` 9 | --- @class flib_on_tick_n 10 | local on_tick_n = {} 11 | 12 | --- Initialize the module's script data table. 13 | --- 14 | --- Must be called at the **beginning** of `on_init`. Can also be used to delete all current tasks. 15 | function on_tick_n.init() 16 | if not storage.__flib then 17 | storage.__flib = {} 18 | end 19 | --- @type table 20 | storage.__flib.on_tick_n = {} 21 | end 22 | 23 | --- Retrieve the tasks for the given tick, if any. 24 | --- 25 | --- Must be called **during** `on_tick`. 26 | --- @param tick number 27 | --- @return flib.OnTickNTasks? 28 | function on_tick_n.retrieve(tick) 29 | -- Failsafe for rare cases where on_tick can fire before on_init 30 | if not storage.__flib or not storage.__flib.on_tick_n then 31 | return 32 | end 33 | local actions = storage.__flib.on_tick_n[tick] 34 | if actions then 35 | storage.__flib.on_tick_n[tick] = nil 36 | return actions 37 | end 38 | end 39 | 40 | --- Add a task to execute on the given tick. 41 | --- @param tick number 42 | --- @param task any The data representing this task. This can be anything except for a `function`. 43 | --- @return flib.OnTickNTaskID ident An identifier for the task. Save this if you might remove the task before execution. 44 | function on_tick_n.add(tick, task) 45 | local list = storage.__flib.on_tick_n 46 | local tick_list = list[tick] 47 | if tick_list then 48 | local index = #tick_list + 1 49 | tick_list[index] = task 50 | return { index = index, tick = tick } 51 | else 52 | list[tick] = { task } 53 | return { index = 1, tick = tick } 54 | end 55 | end 56 | 57 | --- Remove a scheduled task. 58 | --- @param ident flib.OnTickNTaskID The identifier object for the task, as returned from `on-tick-n.add`. 59 | function on_tick_n.remove(ident) 60 | local tick_list = storage.__flib.on_tick_n[ident.tick] 61 | if not tick_list or not tick_list[ident.index] then 62 | return false 63 | end 64 | 65 | tick_list[ident.index] = nil 66 | 67 | return true 68 | end 69 | 70 | --- A unique identifier for a previously added task, used in `on-tick-n.remove`. 71 | --- @class flib.OnTickNTaskID 72 | --- @field tick number The tick this task is scheduled for. 73 | --- @field index number The tasks' index in the tick's `Tasks` table. 74 | 75 | --- A table of tasks. 76 | --- 77 | --- Each task can be anything that is not a function, as specified in `on-tick-n.add`. 78 | --- 79 | --- **This is not an array, there may be gaps. Always use `pairs` to iterate this table.** 80 | --- 81 | --- # Example 82 | --- 83 | --- ```lua 84 | --- event.on_tick(function(e) 85 | --- for _, task in pairs(on_tick_n.retrieve(e.tick) or {}) do 86 | --- if task == "say_hi" then 87 | --- game.print("Hello there!") 88 | --- elseif task == "order_66" then 89 | --- for _, player in pairs(game.players) do 90 | --- player.die() 91 | --- end 92 | --- end 93 | --- end 94 | --- end) 95 | --- ``` 96 | --- @alias flib.OnTickNTasks table 97 | 98 | return on_tick_n 99 | -------------------------------------------------------------------------------- /orientation.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.orientation" then 2 | return require("__flib__.orientation") 3 | end 4 | 5 | --- Functions for working with orientations. 6 | --- ```lua 7 | --- local flib_orientation = require("__flib__.orientation") 8 | --- ``` 9 | --- @class flib_orientation 10 | local flib_orientation = {} 11 | 12 | flib_orientation.north = defines.direction.north / 16 13 | flib_orientation.east = defines.direction.east / 16 14 | flib_orientation.west = defines.direction.west / 16 15 | flib_orientation.south = defines.direction.south / 16 16 | flib_orientation.northeast = defines.direction.northeast / 16 17 | flib_orientation.northwest = defines.direction.northwest / 16 18 | flib_orientation.southeast = defines.direction.southeast / 16 19 | flib_orientation.southwest = defines.direction.southwest / 16 20 | 21 | --- Returns a 4way or 8way direction from an orientation. 22 | --- @param orientation number 23 | --- @param eight_way boolean 24 | --- @return defines.direction 25 | function flib_orientation.to_direction(orientation, eight_way) 26 | local ways = eight_way and 8 or 4 27 | local mod = eight_way and 1 or 2 28 | return math.floor(orientation * ways + 0.5) % ways * mod --[[@as defines.direction]] 29 | end 30 | 31 | --- Returns the opposite orientation. 32 | --- @param orientation number 33 | --- @return number 34 | function flib_orientation.opposite(orientation) 35 | return (orientation + 0.5) % 1 36 | end 37 | 38 | --- Add two orientations together. 39 | --- @param orientation1 number 40 | --- @param orientation2 number 41 | --- @return number the orientations added together 42 | function flib_orientation.add(orientation1, orientation2) 43 | return (orientation1 + orientation2) % 1 44 | end 45 | 46 | return flib_orientation 47 | -------------------------------------------------------------------------------- /position.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.position" then 2 | return require("__flib__.position") 3 | end 4 | 5 | local flib_math = require("__flib__.math") 6 | 7 | --- Utilities for manipulating positions. All functions support both the shorthand and explicit syntaxes and will 8 | --- preserve the syntax that was passed in. 9 | --- ```lua 10 | --- local flib_position = require("__flib__.position") 11 | --- ``` 12 | --- @class flib_position 13 | local flib_position = {} 14 | 15 | --- FIXME: Sumneko doesn't properly handle generics yet and throws a bunch of bogus warnings. 16 | --- @diagnostic disable 17 | 18 | --- Return the absolute value of the position's coordinates. 19 | --- @generic P 20 | --- @param pos P 21 | --- @return P 22 | function flib_position.abs(pos) 23 | if pos.x then 24 | return { x = math.abs(pos.x), y = math.abs(pos.y) } 25 | else 26 | return { math.abs(pos[1]), math.abs(pos[2]) } 27 | end 28 | end 29 | 30 | --- Add two positions. 31 | --- @generic P 32 | --- @param pos1 P 33 | --- @param pos2 P 34 | --- @return P 35 | function flib_position.add(pos1, pos2) 36 | local x1 = pos1.x or pos1[1] 37 | local y1 = pos1.y or pos1[2] 38 | local x2 = pos2.x or pos2[1] 39 | local y2 = pos2.y or pos2[2] 40 | if pos1.x then 41 | return { x = x1 + x2, y = y1 + y2 } 42 | else 43 | return { x1 + x2, y1 + y2 } 44 | end 45 | end 46 | 47 | --- Ceil the given position. 48 | --- @generic P 49 | --- @param pos P 50 | --- @return P 51 | function flib_position.ceil(pos) 52 | if pos.x then 53 | return { x = math.ceil(pos.x), y = math.ceil(pos.y) } 54 | else 55 | return { math.ceil(pos[1]), math.ceil(pos[2]) } 56 | end 57 | end 58 | 59 | --- Calculate the distance between two positions. 60 | --- @generic P 61 | --- @param pos1 P 62 | --- @param pos2 P 63 | --- @return number 64 | function flib_position.distance(pos1, pos2) 65 | local x1 = pos1.x or pos1[1] 66 | local y1 = pos1.y or pos1[2] 67 | local x2 = pos2.x or pos2[1] 68 | local y2 = pos2.y or pos2[2] 69 | return math.sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2) 70 | end 71 | 72 | --- Calculate the squared distance between two positions. 73 | --- @generic P 74 | --- @param pos1 P 75 | --- @param pos2 P 76 | --- @return number 77 | function flib_position.distance_squared(pos1, pos2) 78 | local x1 = pos1.x or pos1[1] 79 | local y1 = pos1.y or pos1[2] 80 | local x2 = pos2.x or pos2[1] 81 | local y2 = pos2.y or pos2[2] 82 | return (x1 - x2) ^ 2 + (y1 - y2) ^ 2 83 | end 84 | 85 | --- Divide two positions. 86 | --- @generic P 87 | --- @param pos1 P 88 | --- @param pos2 P 89 | --- @return P 90 | function flib_position.div(pos1, pos2) 91 | local x1 = pos1.x or pos1[1] 92 | local y1 = pos1.y or pos1[2] 93 | local x2 = pos2.x or pos2[1] 94 | local y2 = pos2.y or pos2[2] 95 | if pos1.x then 96 | return { x = x1 / x2, y = y1 / y2 } 97 | else 98 | return { x1 / x2, y1 / y2 } 99 | end 100 | end 101 | 102 | --- Return the position in explicit form. 103 | --- @generic P 104 | --- @param pos P 105 | --- @return P 106 | function flib_position.ensure_explicit(pos) 107 | if pos.x then 108 | return pos 109 | else 110 | return { x = pos[1], y = pos[2] } 111 | end 112 | end 113 | 114 | --- Return the position in shorthand form. 115 | --- @generic P 116 | --- @param pos P 117 | --- @return P 118 | function flib_position.ensure_short(pos) 119 | if pos.x then 120 | return { pos.x, pos.y } 121 | else 122 | return pos 123 | end 124 | end 125 | 126 | --- Test if two positions are equal. 127 | --- @generic P 128 | --- @param pos1 P 129 | --- @param pos2 P 130 | --- @return boolean 131 | function flib_position.eq(pos1, pos2) 132 | local x1 = pos1.x or pos1[1] 133 | local y1 = pos1.y or pos1[2] 134 | local x2 = pos2.x or pos2[1] 135 | local y2 = pos2.y or pos2[2] 136 | return x1 == x2 and y1 == y2 137 | end 138 | 139 | --- Floor the given position. 140 | --- @generic P 141 | --- @param pos P 142 | --- @return P 143 | function flib_position.floor(pos) 144 | if pos.x then 145 | return { x = math.floor(pos.x), y = math.floor(pos.y) } 146 | else 147 | return { math.floor(pos[1]), math.floor(pos[2]) } 148 | end 149 | end 150 | 151 | --- Convert a `ChunkPosition` into a `TilePosition` by multiplying by 32. 152 | --- @param pos ChunkPosition 153 | --- @return TilePosition 154 | function flib_position.from_chunk(pos) 155 | if pos.x then 156 | return { x = pos.x * 32, y = pos.y * 32 } 157 | else 158 | return { pos[1] * 32, pos[2] * 32 } 159 | end 160 | end 161 | 162 | --- Test if `pos1` is greater than or equal to `pos2`. 163 | --- @generic P 164 | --- @param pos1 P 165 | --- @param pos2 P 166 | --- @return boolean 167 | function flib_position.ge(pos1, pos2) 168 | local x1 = pos1.x or pos1[1] 169 | local y1 = pos1.y or pos1[2] 170 | local x2 = pos2.x or pos2[1] 171 | local y2 = pos2.y or pos2[2] 172 | return x1 >= x2 and y1 >= y2 173 | end 174 | 175 | --- Test if `pos1` is greater than `pos2`. 176 | --- @generic P 177 | --- @param pos1 P 178 | --- @param pos2 P 179 | --- @return boolean 180 | function flib_position.gt(pos1, pos2) 181 | local x1 = pos1.x or pos1[1] 182 | local y1 = pos1.y or pos1[2] 183 | local x2 = pos2.x or pos2[1] 184 | local y2 = pos2.y or pos2[2] 185 | return x1 > x2 and y1 > y2 186 | end 187 | 188 | --- Test if `pos1` is less than or equal to `pos2`. 189 | --- @generic P 190 | --- @param pos1 P 191 | --- @param pos2 P 192 | --- @return boolean 193 | function flib_position.le(pos1, pos2) 194 | local x1 = pos1.x or pos1[1] 195 | local y1 = pos1.y or pos1[2] 196 | local x2 = pos2.x or pos2[1] 197 | local y2 = pos2.y or pos2[2] 198 | return x1 <= x2 and y1 <= y2 199 | end 200 | 201 | --- Linearly interpolate between two positions. For example, an amount of 0.5 will return the midpoint. 202 | --- @generic P 203 | --- @param pos1 P 204 | --- @param pos2 P 205 | --- @param amount number 206 | --- @return P 207 | function flib_position.lerp(pos1, pos2, amount) 208 | if pos1.x then 209 | return { x = flib_math.lerp(pos1.x, pos2.x, amount), y = flib_math.lerp(pos1.y, pos2.y, amount) } 210 | else 211 | return { flib_math.lerp(pos1[1], pos2[1], amount), flib_math.lerp(pos1[2], pos2[2], amount) } 212 | end 213 | end 214 | 215 | --- Test if `pos1` is less than `pos2`. 216 | --- @generic P 217 | --- @param pos1 P 218 | --- @param pos2 P 219 | --- @return boolean 220 | function flib_position.lt(pos1, pos2) 221 | local x1 = pos1.x or pos1[1] 222 | local y1 = pos1.y or pos1[2] 223 | local x2 = pos2.x or pos2[1] 224 | local y2 = pos2.y or pos2[2] 225 | return x1 < x2 and y1 < y2 226 | end 227 | 228 | --- Take the remainder (modulus) of two positions. 229 | --- @generic P 230 | --- @param pos1 P 231 | --- @param pos2 P 232 | --- @return P 233 | function flib_position.mod(pos1, pos2) 234 | local x1 = pos1.x or pos1[1] 235 | local y1 = pos1.y or pos1[2] 236 | local x2 = pos2.x or pos2[1] 237 | local y2 = pos2.y or pos2[2] 238 | if pos1.x then 239 | return { x = x1 % x2, y = y1 % y2 } 240 | else 241 | return { x1 % x2, y1 % y2 } 242 | end 243 | end 244 | 245 | --- Multiply two positions. 246 | --- @generic P 247 | --- @param pos1 P 248 | --- @param pos2 P 249 | --- @return P 250 | function flib_position.mul(pos1, pos2) 251 | local x1 = pos1.x or pos1[1] 252 | local y1 = pos1.y or pos1[2] 253 | local x2 = pos2.x or pos2[1] 254 | local y2 = pos2.y or pos2[2] 255 | if pos1.x then 256 | return { x = x1 * x2, y = y1 * y2 } 257 | else 258 | return { x1 * x2, y1 * y2 } 259 | end 260 | end 261 | 262 | --- Subtract two positions. 263 | --- @generic P 264 | --- @param pos1 P 265 | --- @param pos2 P 266 | --- @return P 267 | function flib_position.sub(pos1, pos2) 268 | local x1 = pos1.x or pos1[1] 269 | local y1 = pos1.y or pos1[2] 270 | local x2 = pos2.x or pos2[1] 271 | local y2 = pos2.y or pos2[2] 272 | if pos1.x then 273 | return { x = x1 - x2, y = y1 - y2 } 274 | else 275 | return { x1 - x2, y1 - y2 } 276 | end 277 | end 278 | 279 | --- Take the power of two positions. `pos1^pos2`. 280 | --- @generic P 281 | --- @param pos1 P 282 | --- @param pos2 P 283 | --- @return P 284 | function flib_position.pow(pos1, pos2) 285 | local x1 = pos1.x or pos1[1] 286 | local y1 = pos1.y or pos1[2] 287 | local x2 = pos2.x or pos2[1] 288 | local y2 = pos2.y or pos2[2] 289 | if pos1.x then 290 | return { x = x1 ^ x2, y = y1 ^ y2 } 291 | else 292 | return { x1 ^ x2, y1 ^ y2 } 293 | end 294 | end 295 | 296 | --- Convert a `MapPosition` or `TilePosition` into a `ChunkPosition` by dividing by 32 and flooring. 297 | --- @param pos MapPosition|TilePosition 298 | --- @return ChunkPosition 299 | function flib_position.to_chunk(pos) 300 | if pos.x then 301 | return { x = math.floor(pos.x / 32), y = math.floor(pos.y / 32) } 302 | else 303 | return { math.floor(pos[1] / 32), math.floor(pos[2] / 32) } 304 | end 305 | end 306 | 307 | --- Convert a `MapPosition` into a `TilePosition` by flooring. 308 | --- @param pos MapPosition 309 | --- @return TilePosition 310 | function flib_position.to_tile(pos) 311 | if pos.x then 312 | return { x = math.floor(pos.x), y = math.floor(pos.y) } 313 | else 314 | return { math.floor(pos[1]), math.floor(pos[2]) } 315 | end 316 | end 317 | 318 | return flib_position 319 | -------------------------------------------------------------------------------- /prototypes.lua: -------------------------------------------------------------------------------- 1 | --- Provides utilities for locating and iterating prototypes in `data.raw`. 2 | --- @class flib_prototypes 3 | local flib_prototypes = {} 4 | 5 | --- Returns a list of all prototypes with the given `base_type`. 6 | --- @overload fun(base_type: "achievement"): data.AchievementPrototype[] 7 | --- @overload fun(base_type: "active-trigger"): data.ActiveTriggerPrototype[] 8 | --- @overload fun(base_type: "airborne-pollutant"): data.AirbornePollutantPrototype[] 9 | --- @overload fun(base_type: "ambient-sound"): data.AmbientSound[] 10 | --- @overload fun(base_type: "ammo-category"): data.AmmoCategory[] 11 | --- @overload fun(base_type: "animation"): data.AnimationPrototype[] 12 | --- @overload fun(base_type: "asteroid-chunk"): data.AsteroidChunkPrototype[] 13 | --- @overload fun(base_type: "autoplace-control"): data.AutoplaceSpecification[] 14 | --- @overload fun(base_type: "burner-usage"): data.BurnerUsagePrototype[] 15 | --- @overload fun(base_type: "collision-layer"): data.CollisionLayerPrototype[] 16 | --- @overload fun(base_type: "custom-event"): data.CustomEventPrototype[] 17 | --- @overload fun(base_type: "custom-input"): data.CustomInputPrototype[] 18 | --- @overload fun(base_type: "damage-type"): data.DamageType[] 19 | --- @overload fun(base_type: "decorative"): data.DecorativePrototype[] 20 | --- @overload fun(base_type: "deliver-category"): data.DeliverCategory[] 21 | --- @overload fun(base_type: "deliver-impact-combination"): data.DeliverImpactCombination[] 22 | --- @overload fun(base_type: "editor-controller"): data.EditorControllerPrototype[] 23 | --- @overload fun(base_type: "entity"): data.EntityPrototype[] 24 | --- @overload fun(base_type: "equipment"): data.EquipmentPrototype[] 25 | --- @overload fun(base_type: "equipment-category"): data.EquipmentCategory[] 26 | --- @overload fun(base_type: "equipment-grid"): data.EquipmentGridPrototype[] 27 | --- @overload fun(base_type: "fluid"): data.FluidPrototype[] 28 | --- @overload fun(base_type: "font"): data.FontPrototype[] 29 | --- @overload fun(base_type: "fuel-category"): data.FuelCategory[] 30 | --- @overload fun(base_type: "god-controller"): data.GodControllerPrototype[] 31 | --- @overload fun(base_type: "gui-style"): data.GuiStyle[] 32 | --- @overload fun(base_type: "impact-category"): data.ImpactCategory[] 33 | --- @overload fun(base_type: "item"): data.ItemPrototype[] 34 | --- @overload fun(base_type: "item-group"): data.ItemGroup[] 35 | --- @overload fun(base_type: "item-subgroup"): data.ItemSubGroup[] 36 | --- @overload fun(base_type: "map-gen-presets"): data.MapGenPresets[] 37 | --- @overload fun(base_type: "map-settings"): data.MapSettings[] 38 | --- @overload fun(base_type: "module-category"): data.ModuleCategory[] 39 | --- @overload fun(base_type: "mouse-cursor"): data.MouseCursor[] 40 | --- @overload fun(base_type: "noise-expression"): data.NamedNoiseExpression[] 41 | --- @overload fun(base_type: "noise-function"): data.NamedNoiseFunction[] 42 | --- @overload fun(base_type: "particle"): data.ParticlePrototype[] 43 | --- @overload fun(base_type: "procession"): data.ProcessionPrototype[] 44 | --- @overload fun(base_type: "procession-layer-inheritance-group"): data.ProcessionLayerInheritanceGroup[] 45 | --- @overload fun(base_type: "quality"): data.QualityPrototype[] 46 | --- @overload fun(base_type: "recipe"): data.RecipePrototype[] 47 | --- @overload fun(base_type: "recipe-category"): data.RecipeCategory[] 48 | --- @overload fun(base_type: "remote-controller"): data.RemoteControllerPrototype[] 49 | --- @overload fun(base_type: "resource-category"): data.ResourceCategory[] 50 | --- @overload fun(base_type: "shortcut"): data.ShortcutPrototype[] 51 | --- @overload fun(base_type: "sound"): data.SoundPrototype[] 52 | --- @overload fun(base_type: "space-connection"): data.SpaceConnectionPrototype[] 53 | --- @overload fun(base_type: "space-location"): data.SpaceLocationPrototype[] 54 | --- @overload fun(base_type: "spectator-controller"): data.SpectatorControllerPrototype[] 55 | --- @overload fun(base_type: "sprite"): data.SpritePrototype[] 56 | --- @overload fun(base_type: "surface"): data.SurfacePrototype[] 57 | --- @overload fun(base_type: "surface-property"): data.SurfacePropertyPrototype[] 58 | --- @overload fun(base_type: "technology"): data.TechnologyPrototype[] 59 | --- @overload fun(base_type: "tile"): data.TilePrototype[] 60 | --- @overload fun(base_type: "tile-effect"): data.TileEffectDefinition[] 61 | --- @overload fun(base_type: "tips-and-tricks-item"): data.TipsAndTricksItem[] 62 | --- @overload fun(base_type: "tips-and-tricks-item-category"): data.TipsAndTricksItemCategory[] 63 | --- @overload fun(base_type: "trigger-target-type"): data.TriggerTargetType[] 64 | --- @overload fun(base_type: "trivial-smoke"): data.TrivialSmokePrototype[] 65 | --- @overload fun(base_type: "tutorial"): data.TutorialDefinition[] 66 | --- @overload fun(base_type: "utility-constants"): data.UtilityConstants[] 67 | --- @overload fun(base_type: "utility-sounds"): data.UtilitySounds[] 68 | --- @overload fun(base_type: "utility-sprites"): data.UtilitySounds[] 69 | --- @overload fun(base_type: "virtual-signal"): data.VirtualSignalPrototype[] 70 | function flib_prototypes.all(base_type) 71 | if not base_type then 72 | error("Did not provide a base_type") 73 | end 74 | if type(base_type) ~= "string" then 75 | error("base_type must be a string") 76 | end 77 | if not defines.prototypes[base_type] then 78 | error("'" .. base_type .. "' is not a valid base prototype type") 79 | end 80 | 81 | local result = {} 82 | local result_len = 0 83 | for prototype_type in pairs(defines.prototypes[base_type]) do 84 | for _, prototype in pairs(data.raw[prototype_type] or {}) do 85 | result_len = result_len + 1 86 | result[result_len] = prototype 87 | end 88 | end 89 | return result 90 | end 91 | 92 | --- Returns the prototype with the given `base_type` and `name`, if it exists. 93 | --- @overload fun(base_type: "achievement", name: string): data.AchievementPrototype? 94 | --- @overload fun(base_type: "active-trigger", name: string): data.ActiveTriggerPrototype? 95 | --- @overload fun(base_type: "airborne-pollutant", name: string): data.AirbornePollutantPrototype? 96 | --- @overload fun(base_type: "ambient-sound", name: string): data.AmbientSound? 97 | --- @overload fun(base_type: "ammo-category", name: string): data.AmmoCategory? 98 | --- @overload fun(base_type: "animation", name: string): data.AnimationPrototype? 99 | --- @overload fun(base_type: "asteroid-chunk", name: string): data.AsteroidChunkPrototype? 100 | --- @overload fun(base_type: "autoplace-control", name: string): data.AutoplaceSpecification? 101 | --- @overload fun(base_type: "burner-usage", name: string): data.BurnerUsagePrototype? 102 | --- @overload fun(base_type: "collision-layer", name: string): data.CollisionLayerPrototype? 103 | --- @overload fun(base_type: "custom-event", name: string): data.CustomEventPrototype? 104 | --- @overload fun(base_type: "custom-input", name: string): data.CustomInputPrototype? 105 | --- @overload fun(base_type: "damage-type", name: string): data.DamageType? 106 | --- @overload fun(base_type: "decorative", name: string): data.DecorativePrototype? 107 | --- @overload fun(base_type: "deliver-category", name: string): data.DeliverCategory? 108 | --- @overload fun(base_type: "deliver-impact-combination", name: string): data.DeliverImpactCombination? 109 | --- @overload fun(base_type: "editor-controller", name: string): data.EditorControllerPrototype? 110 | --- @overload fun(base_type: "entity", name: string): data.EntityPrototype? 111 | --- @overload fun(base_type: "equipment", name: string): data.EquipmentPrototype? 112 | --- @overload fun(base_type: "equipment-category", name: string): data.EquipmentCategory? 113 | --- @overload fun(base_type: "equipment-grid", name: string): data.EquipmentGridPrototype? 114 | --- @overload fun(base_type: "fluid", name: string): data.FluidPrototype? 115 | --- @overload fun(base_type: "font", name: string): data.FontPrototype? 116 | --- @overload fun(base_type: "fuel-category", name: string): data.FuelCategory? 117 | --- @overload fun(base_type: "god-controller", name: string): data.GodControllerPrototype? 118 | --- @overload fun(base_type: "gui-style", name: string): data.GuiStyle? 119 | --- @overload fun(base_type: "impact-category", name: string): data.ImpactCategory? 120 | --- @overload fun(base_type: "item", name: string): data.ItemPrototype? 121 | --- @overload fun(base_type: "item-group", name: string): data.ItemGroup? 122 | --- @overload fun(base_type: "item-subgroup", name: string): data.ItemSubGroup? 123 | --- @overload fun(base_type: "map-gen-presets", name: string): data.MapGenPresets? 124 | --- @overload fun(base_type: "map-settings", name: string): data.MapSettings? 125 | --- @overload fun(base_type: "module-category", name: string): data.ModuleCategory? 126 | --- @overload fun(base_type: "mouse-cursor", name: string): data.MouseCursor? 127 | --- @overload fun(base_type: "noise-expression", name: string): data.NamedNoiseExpression? 128 | --- @overload fun(base_type: "noise-function", name: string): data.NamedNoiseFunction? 129 | --- @overload fun(base_type: "particle", name: string): data.ParticlePrototype? 130 | --- @overload fun(base_type: "procession", name: string): data.ProcessionPrototype? 131 | --- @overload fun(base_type: "procession-layer-inheritance-group", name: string): data.ProcessionLayerInheritanceGroup? 132 | --- @overload fun(base_type: "quality", name: string): data.QualityPrototype? 133 | --- @overload fun(base_type: "recipe", name: string): data.RecipePrototype? 134 | --- @overload fun(base_type: "recipe-category", name: string): data.RecipeCategory? 135 | --- @overload fun(base_type: "remote-controller", name: string): data.RemoteControllerPrototype? 136 | --- @overload fun(base_type: "resource-category", name: string): data.ResourceCategory? 137 | --- @overload fun(base_type: "shortcut", name: string): data.ShortcutPrototype? 138 | --- @overload fun(base_type: "sound", name: string): data.SoundPrototype? 139 | --- @overload fun(base_type: "space-connection", name: string): data.SpaceConnectionPrototype? 140 | --- @overload fun(base_type: "space-location", name: string): data.SpaceLocationPrototype? 141 | --- @overload fun(base_type: "spectator-controller", name: string): data.SpectatorControllerPrototype? 142 | --- @overload fun(base_type: "sprite", name: string): data.SpritePrototype? 143 | --- @overload fun(base_type: "surface", name: string): data.SurfacePrototype? 144 | --- @overload fun(base_type: "surface-property", name: string): data.SurfacePropertyPrototype? 145 | --- @overload fun(base_type: "technology", name: string): data.TechnologyPrototype? 146 | --- @overload fun(base_type: "tile", name: string): data.TilePrototype? 147 | --- @overload fun(base_type: "tile-effect", name: string): data.TileEffectDefinition? 148 | --- @overload fun(base_type: "tips-and-tricks-item", name: string): data.TipsAndTricksItem? 149 | --- @overload fun(base_type: "tips-and-tricks-item-category", name: string): data.TipsAndTricksItemCategory? 150 | --- @overload fun(base_type: "trigger-target-type", name: string): data.TriggerTargetType? 151 | --- @overload fun(base_type: "trivial-smoke", name: string): data.TrivialSmokePrototype? 152 | --- @overload fun(base_type: "tutorial", name: string): data.TutorialDefinition? 153 | --- @overload fun(base_type: "utility-constants", name: string): data.UtilityConstants? 154 | --- @overload fun(base_type: "utility-sounds", name: string): data.UtilitySounds? 155 | --- @overload fun(base_type: "utility-sprites", name: string): data.UtilitySounds? 156 | --- @overload fun(base_type: "virtual-signal", name: string): data.VirtualSignalPrototype? 157 | function flib_prototypes.find(base_type, name) 158 | if not base_type then 159 | error("Did not provide a base_type") 160 | end 161 | if type(base_type) ~= "string" then 162 | error("base_type must be a string") 163 | end 164 | if base_type == "" then 165 | error("base_type must be a non-empty string") 166 | end 167 | if not defines.prototypes[base_type] then 168 | error("'" .. base_type .. "' is not a valid base prototype type") 169 | end 170 | 171 | if not name then 172 | error("Did not provide a name") 173 | end 174 | if type(name) ~= "string" then 175 | error("name must be a string") 176 | end 177 | if name == "" then 178 | error("name must be a non-empty string") 179 | end 180 | 181 | for derived_type in pairs(defines.prototypes[base_type]) do 182 | local prototypes = data.raw[derived_type] 183 | if prototypes then 184 | local prototype = prototypes[name] 185 | if prototype then 186 | return prototype 187 | end 188 | end 189 | end 190 | end 191 | 192 | --- Returns the prototype with the given `base_type` and `name`, throwing an error if it doesn't exist. 193 | --- @overload fun(base_type: "achievement", name: string): data.AchievementPrototype 194 | --- @overload fun(base_type: "active-trigger", name: string): data.ActiveTriggerPrototype 195 | --- @overload fun(base_type: "airborne-pollutant", name: string): data.AirbornePollutantPrototype 196 | --- @overload fun(base_type: "ambient-sound", name: string): data.AmbientSound 197 | --- @overload fun(base_type: "ammo-category", name: string): data.AmmoCategory 198 | --- @overload fun(base_type: "animation", name: string): data.AnimationPrototype 199 | --- @overload fun(base_type: "asteroid-chunk", name: string): data.AsteroidChunkPrototype 200 | --- @overload fun(base_type: "autoplace-control", name: string): data.AutoplaceSpecification 201 | --- @overload fun(base_type: "burner-usage", name: string): data.BurnerUsagePrototype 202 | --- @overload fun(base_type: "collision-layer", name: string): data.CollisionLayerPrototype 203 | --- @overload fun(base_type: "custom-event", name: string): data.CustomEventPrototype 204 | --- @overload fun(base_type: "custom-input", name: string): data.CustomInputPrototype 205 | --- @overload fun(base_type: "damage-type", name: string): data.DamageType 206 | --- @overload fun(base_type: "decorative", name: string): data.DecorativePrototype 207 | --- @overload fun(base_type: "deliver-category", name: string): data.DeliverCategory 208 | --- @overload fun(base_type: "deliver-impact-combination", name: string): data.DeliverImpactCombination 209 | --- @overload fun(base_type: "editor-controller", name: string): data.EditorControllerPrototype 210 | --- @overload fun(base_type: "entity", name: string): data.EntityPrototype 211 | --- @overload fun(base_type: "equipment", name: string): data.EquipmentPrototype 212 | --- @overload fun(base_type: "equipment-category", name: string): data.EquipmentCategory 213 | --- @overload fun(base_type: "equipment-grid", name: string): data.EquipmentGridPrototype 214 | --- @overload fun(base_type: "fluid", name: string): data.FluidPrototype 215 | --- @overload fun(base_type: "font", name: string): data.FontPrototype 216 | --- @overload fun(base_type: "fuel-category", name: string): data.FuelCategory 217 | --- @overload fun(base_type: "god-controller", name: string): data.GodControllerPrototype 218 | --- @overload fun(base_type: "gui-style", name: string): data.GuiStyle 219 | --- @overload fun(base_type: "impact-category", name: string): data.ImpactCategory 220 | --- @overload fun(base_type: "item", name: string): data.ItemPrototype 221 | --- @overload fun(base_type: "item-group", name: string): data.ItemGroup 222 | --- @overload fun(base_type: "item-subgroup", name: string): data.ItemSubGroup 223 | --- @overload fun(base_type: "map-gen-presets", name: string): data.MapGenPresets 224 | --- @overload fun(base_type: "map-settings", name: string): data.MapSettings 225 | --- @overload fun(base_type: "module-category", name: string): data.ModuleCategory 226 | --- @overload fun(base_type: "mouse-cursor", name: string): data.MouseCursor 227 | --- @overload fun(base_type: "noise-expression", name: string): data.NamedNoiseExpression 228 | --- @overload fun(base_type: "noise-function", name: string): data.NamedNoiseFunction 229 | --- @overload fun(base_type: "particle", name: string): data.ParticlePrototype 230 | --- @overload fun(base_type: "procession", name: string): data.ProcessionPrototype 231 | --- @overload fun(base_type: "procession-layer-inheritance-group", name: string): data.ProcessionLayerInheritanceGroup 232 | --- @overload fun(base_type: "quality", name: string): data.QualityPrototype 233 | --- @overload fun(base_type: "recipe", name: string): data.RecipePrototype 234 | --- @overload fun(base_type: "recipe-category", name: string): data.RecipeCategory 235 | --- @overload fun(base_type: "remote-controller", name: string): data.RemoteControllerPrototype 236 | --- @overload fun(base_type: "resource-category", name: string): data.ResourceCategory 237 | --- @overload fun(base_type: "shortcut", name: string): data.ShortcutPrototype 238 | --- @overload fun(base_type: "sound", name: string): data.SoundPrototype 239 | --- @overload fun(base_type: "space-connection", name: string): data.SpaceConnectionPrototype 240 | --- @overload fun(base_type: "space-location", name: string): data.SpaceLocationPrototype 241 | --- @overload fun(base_type: "spectator-controller", name: string): data.SpectatorControllerPrototype 242 | --- @overload fun(base_type: "sprite", name: string): data.SpritePrototype 243 | --- @overload fun(base_type: "surface", name: string): data.SurfacePrototype 244 | --- @overload fun(base_type: "surface-property", name: string): data.SurfacePropertyPrototype 245 | --- @overload fun(base_type: "technology", name: string): data.TechnologyPrototype 246 | --- @overload fun(base_type: "tile", name: string): data.TilePrototype 247 | --- @overload fun(base_type: "tile-effect", name: string): data.TileEffectDefinition 248 | --- @overload fun(base_type: "tips-and-tricks-item", name: string): data.TipsAndTricksItem 249 | --- @overload fun(base_type: "tips-and-tricks-item-category", name: string): data.TipsAndTricksItemCategory 250 | --- @overload fun(base_type: "trigger-target-type", name: string): data.TriggerTargetType 251 | --- @overload fun(base_type: "trivial-smoke", name: string): data.TrivialSmokePrototype 252 | --- @overload fun(base_type: "tutorial", name: string): data.TutorialDefinition 253 | --- @overload fun(base_type: "utility-constants", name: string): data.UtilityConstants 254 | --- @overload fun(base_type: "utility-sounds", name: string): data.UtilitySounds 255 | --- @overload fun(base_type: "utility-sprites", name: string): data.UtilitySounds 256 | --- @overload fun(base_type: "virtual-signal", name: string): data.VirtualSignalPrototype 257 | function flib_prototypes.get(base_type, name) 258 | local result = flib_prototypes.find(base_type, name) 259 | if not result then 260 | error("Prototype '" .. base_type .. "/" .. name .. "' does not exist.") 261 | end 262 | return result 263 | end 264 | 265 | --- @type table 266 | local base_type_lookup = {} 267 | for base_type, derived_types in pairs(defines.prototypes) do 268 | for derived_type in pairs(derived_types) do 269 | base_type_lookup[derived_type] = base_type 270 | end 271 | end 272 | 273 | --- Returns the base type of the given prototype type. 274 | --- @param derived_type string 275 | --- @return string 276 | function flib_prototypes.get_base_type(derived_type) 277 | local base_type = base_type_lookup[derived_type] 278 | if not base_type then 279 | error("'" .. derived_type .. "' is not a valid prototype type.") 280 | end 281 | return base_type 282 | end 283 | 284 | return flib_prototypes 285 | -------------------------------------------------------------------------------- /prototypes/sprite.lua: -------------------------------------------------------------------------------- 1 | -- INDICATOR SPRITES 2 | 3 | local indicators = {} 4 | for i, color in ipairs({ "black", "white", "red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink" }) do 5 | indicators[i] = { 6 | type = "sprite", 7 | name = "flib_indicator_" .. color, 8 | filename = "__flib__/graphics/indicators.png", 9 | y = (i - 1) * 32, 10 | size = 32, 11 | flags = { "icon" }, 12 | } 13 | end 14 | data:extend(indicators) 15 | 16 | local fab = "__flib__/graphics/frame-action-icons.png" 17 | 18 | data:extend({ 19 | { type = "sprite", name = "flib_pin_black", filename = fab, position = { 0, 0 }, size = 32, flags = { "gui-icon" } }, 20 | { type = "sprite", name = "flib_pin_white", filename = fab, position = { 32, 0 }, size = 32, flags = { "gui-icon" } }, 21 | { 22 | type = "sprite", 23 | name = "flib_pin_disabled", 24 | filename = fab, 25 | position = { 64, 0 }, 26 | size = 32, 27 | flags = { "gui-icon" }, 28 | }, 29 | { 30 | type = "sprite", 31 | name = "flib_settings_black", 32 | filename = fab, 33 | position = { 0, 32 }, 34 | size = 32, 35 | flags = { "gui-icon" }, 36 | }, 37 | { 38 | type = "sprite", 39 | name = "flib_settings_white", 40 | filename = fab, 41 | position = { 32, 32 }, 42 | size = 32, 43 | flags = { "gui-icon" }, 44 | }, 45 | { 46 | type = "sprite", 47 | name = "flib_settings_disabled", 48 | filename = fab, 49 | position = { 64, 32 }, 50 | size = 32, 51 | flags = { "gui-icon" }, 52 | }, 53 | { 54 | type = "sprite", 55 | name = "flib_nav_backward_black", 56 | filename = "__flib__/graphics/nav-backward-black.png", 57 | size = 32, 58 | flags = { "gui-icon" }, 59 | }, 60 | { 61 | type = "sprite", 62 | name = "flib_nav_backward_white", 63 | filename = "__flib__/graphics/nav-backward-white.png", 64 | size = 32, 65 | flags = { "gui-icon" }, 66 | }, 67 | { 68 | type = "sprite", 69 | name = "flib_nav_backward_disabled", 70 | filename = "__flib__/graphics/nav-backward-disabled.png", 71 | size = 32, 72 | flags = { "gui-icon" }, 73 | }, 74 | { 75 | type = "sprite", 76 | name = "flib_nav_forward_black", 77 | filename = "__flib__/graphics/nav-forward-black.png", 78 | size = 32, 79 | flags = { "gui-icon" }, 80 | }, 81 | { 82 | type = "sprite", 83 | name = "flib_nav_forward_white", 84 | filename = "__flib__/graphics/nav-forward-white.png", 85 | size = 32, 86 | flags = { "gui-icon" }, 87 | }, 88 | { 89 | type = "sprite", 90 | name = "flib_nav_forward_disabled", 91 | filename = "__flib__/graphics/nav-forward-disabled.png", 92 | size = 32, 93 | flags = { "gui-icon" }, 94 | }, 95 | }) 96 | -------------------------------------------------------------------------------- /prototypes/style.lua: -------------------------------------------------------------------------------- 1 | local data_util = require("__flib__.data-util") 2 | 3 | local styles = data.raw["gui-style"].default 4 | 5 | -- SLOT BUTTON STYLES 6 | 7 | local slot_tileset = "__flib__/graphics/slots.png" 8 | 9 | local function gen_slot(x, y, default_offset) 10 | default_offset = default_offset or 0 11 | return { 12 | type = "button_style", 13 | parent = "slot", 14 | size = 40, 15 | clicked_vertical_offset = 0, 16 | default_graphical_set = { 17 | base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset }, 18 | }, 19 | hovered_graphical_set = { 20 | base = { border = 4, position = { x + 80, y }, size = 80, filename = slot_tileset }, 21 | }, 22 | clicked_graphical_set = { 23 | base = { border = 4, position = { x + 160, y }, size = 80, filename = slot_tileset }, 24 | }, 25 | disabled_graphical_set = { -- identical to default graphical set 26 | base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset }, 27 | }, 28 | } 29 | end 30 | 31 | local function gen_slot_button(x, y, default_offset, glow) 32 | default_offset = default_offset or 0 33 | return { 34 | type = "button_style", 35 | parent = "slot_button", 36 | size = 40, 37 | clicked_vertical_offset = 0, 38 | default_graphical_set = { 39 | base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset }, 40 | shadow = offset_by_2_rounded_corners_glow(default_dirt_color), 41 | }, 42 | hovered_graphical_set = { 43 | base = { border = 4, position = { x + 80, y }, size = 80, filename = slot_tileset }, 44 | shadow = offset_by_2_rounded_corners_glow(default_dirt_color), 45 | glow = offset_by_2_rounded_corners_glow(glow), 46 | }, 47 | clicked_graphical_set = { 48 | base = { border = 4, position = { x + 160, y }, size = 80, filename = slot_tileset }, 49 | shadow = offset_by_2_rounded_corners_glow(default_dirt_color), 50 | }, 51 | disabled_graphical_set = { -- identical to default graphical set 52 | base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset }, 53 | shadow = offset_by_2_rounded_corners_glow(default_dirt_color), 54 | }, 55 | } 56 | end 57 | 58 | local function gen_standalone_slot_button(x, y, default_offset) 59 | default_offset = default_offset or 0 60 | return { 61 | type = "button_style", 62 | parent = "slot_button", 63 | size = 40, 64 | clicked_vertical_offset = 0, 65 | default_graphical_set = { 66 | base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset }, 67 | shadow = offset_by_4_rounded_corners_shallow_inset, 68 | }, 69 | hovered_graphical_set = { 70 | base = { border = 4, position = { x + 80, y }, size = 80, filename = slot_tileset }, 71 | shadow = offset_by_4_rounded_corners_shallow_inset, 72 | }, 73 | clicked_graphical_set = { 74 | base = { border = 4, position = { x + 160, y }, size = 80, filename = slot_tileset }, 75 | shadow = offset_by_4_rounded_corners_shallow_inset, 76 | }, 77 | disabled_graphical_set = { -- identical to default graphical set 78 | base = { border = 4, position = { x + default_offset, y }, size = 80, filename = slot_tileset }, 79 | shadow = offset_by_4_rounded_corners_shallow_inset, 80 | }, 81 | } 82 | end 83 | 84 | local slot_data = { 85 | { name = "default", y = 0, glow = default_glow_color }, 86 | { name = "grey", y = 80, glow = default_glow_color }, 87 | { name = "red", y = 160, glow = { 230, 135, 135 } }, 88 | { name = "orange", y = 240, glow = { 216, 169, 122 } }, 89 | { name = "yellow", y = 320, glow = { 230, 218, 135 } }, 90 | { name = "green", y = 400, glow = { 153, 230, 135 } }, 91 | { name = "cyan", y = 480, glow = { 135, 230, 230 } }, 92 | { name = "blue", y = 560, glow = { 135, 186, 230 } }, 93 | { name = "purple", y = 640, glow = { 188, 135, 230 } }, 94 | { name = "pink", y = 720, glow = { 230, 135, 230 } }, 95 | } 96 | 97 | for _, data in pairs(slot_data) do 98 | styles["flib_slot_" .. data.name] = gen_slot(0, data.y) 99 | styles["flib_selected_slot_" .. data.name] = gen_slot(0, data.y, 80) 100 | styles["flib_slot_button_" .. data.name] = gen_slot_button(240, data.y, 0, data.glow) 101 | styles["flib_selected_slot_button_" .. data.name] = gen_slot_button(240, data.y, 80, data.glow) 102 | styles["flib_standalone_slot_button_" .. data.name] = gen_standalone_slot_button(240, data.y) 103 | styles["flib_selected_standalone_slot_button_" .. data.name] = gen_standalone_slot_button(240, data.y, 80) 104 | end 105 | 106 | -- BUTTON STYLES 107 | 108 | styles.flib_selected_frame_action_button = { 109 | type = "button_style", 110 | parent = "frame_action_button", 111 | default_font_color = button_hovered_font_color, 112 | default_graphical_set = { 113 | base = { position = { 225, 17 }, corner_size = 8 }, 114 | shadow = { position = { 440, 24 }, corner_size = 8, draw_type = "outer" }, 115 | }, 116 | hovered_font_color = button_hovered_font_color, 117 | hovered_graphical_set = { 118 | base = { position = { 369, 17 }, corner_size = 8 }, 119 | shadow = { position = { 440, 24 }, corner_size = 8, draw_type = "outer" }, 120 | }, 121 | clicked_font_color = button_hovered_font_color, 122 | clicked_graphical_set = { 123 | base = { position = { 352, 17 }, corner_size = 8 }, 124 | shadow = { position = { 440, 24 }, corner_size = 8, draw_type = "outer" }, 125 | }, 126 | -- Simulate clicked-vertical-offset 127 | top_padding = 1, 128 | bottom_padding = -1, 129 | clicked_vertical_offset = 0, 130 | } 131 | 132 | local btn = styles.button 133 | 134 | styles.flib_selected_tool_button = { 135 | type = "button_style", 136 | parent = "tool_button", 137 | default_font_color = btn.selected_font_color, 138 | default_graphical_set = btn.selected_graphical_set, 139 | hovered_font_color = btn.selected_hovered_font_color, 140 | hovered_graphical_set = btn.selected_hovered_graphical_set, 141 | clicked_font_color = btn.selected_clicked_font_color, 142 | clicked_graphical_set = btn.selected_clicked_graphical_set, 143 | -- Simulate clicked-vertical-offset 144 | top_padding = 1, 145 | bottom_padding = -1, 146 | clicked_vertical_offset = 0, 147 | } 148 | 149 | styles.flib_tool_button_light_green = { 150 | type = "button_style", 151 | parent = "item_and_count_select_confirm", 152 | padding = 2, 153 | top_margin = 0, 154 | tooltip = "", 155 | } 156 | 157 | styles.flib_tool_button_dark_red = { 158 | type = "button_style", 159 | parent = "tool_button", 160 | default_graphical_set = { 161 | base = { filename = data_util.dark_red_button_tileset, position = { 0, 0 }, corner_size = 8 }, 162 | shadow = default_dirt, 163 | }, 164 | hovered_graphical_set = { 165 | base = { filename = data_util.dark_red_button_tileset, position = { 17, 0 }, corner_size = 8 }, 166 | shadow = default_dirt, 167 | glow = default_glow({ 236, 130, 130, 127 }, 0.5), 168 | }, 169 | clicked_graphical_set = { 170 | base = { filename = data_util.dark_red_button_tileset, position = { 34, 0 }, corner_size = 8 }, 171 | shadow = default_dirt, 172 | }, 173 | } 174 | 175 | -- EMPTY-WIDGET STYLES 176 | 177 | styles.flib_dialog_footer_drag_handle = { 178 | type = "empty_widget_style", 179 | parent = "draggable_space", 180 | height = 32, 181 | horizontally_stretchable = "on", 182 | } 183 | 184 | styles.flib_dialog_footer_drag_handle_no_right = { 185 | type = "empty_widget_style", 186 | parent = "flib_dialog_footer_drag_handle", 187 | right_margin = 0, 188 | } 189 | 190 | styles.flib_dialog_titlebar_drag_handle = { 191 | type = "empty_widget_style", 192 | parent = "flib_titlebar_drag_handle", 193 | right_margin = 0, 194 | } 195 | 196 | styles.flib_horizontal_pusher = { 197 | type = "empty_widget_style", 198 | horizontally_stretchable = "on", 199 | } 200 | 201 | styles.flib_titlebar_drag_handle = { 202 | type = "empty_widget_style", 203 | parent = "draggable_space", 204 | left_margin = 4, 205 | right_margin = 4, 206 | height = 24, 207 | horizontally_stretchable = "on", 208 | } 209 | 210 | styles.flib_vertical_pusher = { 211 | type = "empty_widget_style", 212 | vertically_stretchable = "on", 213 | } 214 | 215 | -- FLOW STYLES 216 | 217 | styles.flib_indicator_flow = { 218 | type = "horizontal_flow_style", 219 | vertical_align = "center", 220 | } 221 | 222 | styles.flib_titlebar_flow = { 223 | type = "horizontal_flow_style", 224 | horizontal_spacing = 8, 225 | } 226 | 227 | -- FRAME STYLES 228 | 229 | styles.flib_shallow_frame_in_shallow_frame = { 230 | type = "frame_style", 231 | parent = "frame", 232 | padding = 0, 233 | graphical_set = { 234 | base = { 235 | position = { 85, 0 }, 236 | corner_size = 8, 237 | center = { position = { 76, 8 }, size = { 1, 1 } }, 238 | draw_type = "outer", 239 | }, 240 | shadow = default_inner_shadow, 241 | }, 242 | vertical_flow_style = { 243 | type = "vertical_flow_style", 244 | vertical_spacing = 0, 245 | }, 246 | } 247 | 248 | -- IMAGE STYLES 249 | 250 | styles.flib_indicator = { 251 | type = "image_style", 252 | size = 16, 253 | stretch_image_to_widget_size = true, 254 | } 255 | 256 | -- LABEL STYLES 257 | 258 | styles.flib_frame_title = { 259 | type = "label_style", 260 | parent = "frame_title", 261 | bottom_padding = 3, 262 | top_margin = -3, 263 | } 264 | 265 | -- LINE STYLES 266 | 267 | styles.flib_subheader_horizontal_line = { 268 | type = "line_style", 269 | horizontally_stretchable = "on", 270 | left_margin = -8, 271 | right_margin = -8, 272 | top_margin = -2, 273 | bottom_margin = -2, 274 | border = { 275 | border_width = 8, 276 | horizontal_line = { filename = "__flib__/graphics/subheader-line.png", size = { 1, 8 } }, 277 | }, 278 | } 279 | 280 | styles.flib_titlebar_separator_line = { 281 | type = "line_style", 282 | top_margin = -2, 283 | bottom_margin = 2, 284 | } 285 | 286 | -- SCROLL-PANE STYLES 287 | 288 | styles.flib_naked_scroll_pane = { 289 | type = "scroll_pane_style", 290 | extra_padding_when_activated = 0, 291 | padding = 12, 292 | graphical_set = { 293 | shadow = default_inner_shadow, 294 | }, 295 | } 296 | 297 | styles.flib_naked_scroll_pane_under_tabs = { 298 | type = "scroll_pane_style", 299 | parent = "flib_naked_scroll_pane", 300 | graphical_set = { 301 | base = { 302 | top = { position = { 93, 0 }, size = { 1, 8 } }, 303 | draw_type = "outer", 304 | }, 305 | shadow = default_inner_shadow, 306 | }, 307 | } 308 | 309 | styles.flib_naked_scroll_pane_no_padding = { 310 | type = "scroll_pane_style", 311 | parent = "flib_naked_scroll_pane", 312 | padding = 0, 313 | } 314 | 315 | styles.flib_shallow_scroll_pane = { 316 | type = "scroll_pane_style", 317 | padding = 0, 318 | graphical_set = { 319 | base = { position = { 85, 0 }, corner_size = 8, draw_type = "outer" }, 320 | shadow = default_inner_shadow, 321 | }, 322 | } 323 | 324 | -- TABBED PANE STYLES 325 | 326 | styles.flib_tabbed_pane_with_no_padding = { 327 | type = "tabbed_pane_style", 328 | tab_content_frame = { 329 | type = "frame_style", 330 | top_padding = 0, 331 | bottom_padding = 0, 332 | left_padding = 0, 333 | right_padding = 0, 334 | graphical_set = { 335 | base = { 336 | -- Same as tabbed_pane_graphical_set - but without bottom 337 | top = { position = { 76, 0 }, size = { 1, 8 } }, 338 | center = { position = { 76, 8 }, size = { 1, 1 } }, 339 | }, 340 | shadow = top_shadow, 341 | }, 342 | }, 343 | } 344 | 345 | -- TEXTFIELD STYLES 346 | 347 | styles.flib_widthless_textfield = { 348 | type = "textbox_style", 349 | width = 0, 350 | } 351 | 352 | styles.flib_widthless_invalid_textfield = { 353 | type = "textbox_style", 354 | parent = "invalid_value_textfield", 355 | width = 0, 356 | } 357 | 358 | styles.flib_titlebar_search_textfield = { 359 | type = "textbox_style", 360 | top_margin = -2, 361 | bottom_margin = 1, 362 | width = 150, 363 | } 364 | -------------------------------------------------------------------------------- /prototypes/technology-slot-style.lua: -------------------------------------------------------------------------------- 1 | local styles = data.raw["gui-style"]["default"] 2 | 3 | --- @param name string 4 | --- @param y number 5 | --- @param level_color Color 6 | --- @param level_range_color Color 7 | local function build_technology_slot(name, y, level_color, level_range_color) 8 | styles["flib_technology_slot_" .. name] = { 9 | type = "button_style", 10 | default_graphical_set = { 11 | base = { 12 | filename = "__flib__/graphics/technology-slots.png", 13 | position = { 0, y }, 14 | size = { 144, 200 }, 15 | }, 16 | shadow = default_shadow, 17 | }, 18 | hovered_graphical_set = { 19 | base = { 20 | filename = "__flib__/graphics/technology-slots.png", 21 | position = { 144, y }, 22 | size = { 144, 200 }, 23 | }, 24 | shadow = default_shadow, 25 | }, 26 | clicked_graphical_set = { 27 | base = { 28 | filename = "__flib__/graphics/technology-slots.png", 29 | position = { 144, y }, 30 | size = { 144, 200 }, 31 | }, 32 | shadow = default_shadow, 33 | }, 34 | selected_graphical_set = { 35 | base = { 36 | filename = "__flib__/graphics/technology-slots.png", 37 | position = { 288, y }, 38 | size = { 144, 200 }, 39 | }, 40 | shadow = default_shadow, 41 | }, 42 | selected_hovered_graphical_set = { 43 | base = { 44 | filename = "__flib__/graphics/technology-slots.png", 45 | position = { 432, y }, 46 | size = { 144, 200 }, 47 | }, 48 | shadow = default_shadow, 49 | }, 50 | selected_clicked_graphical_set = { 51 | base = { 52 | filename = "__flib__/graphics/technology-slots.png", 53 | position = { 432, y }, 54 | size = { 144, 200 }, 55 | }, 56 | shadow = default_shadow, 57 | }, 58 | padding = 0, 59 | size = { 72, 100 }, 60 | left_click_sound = { filename = "__core__/sound/gui-square-button-large.ogg", volume = 1 }, 61 | } 62 | 63 | styles["flib_technology_slot_" .. name .. "_multilevel"] = { 64 | type = "button_style", 65 | default_graphical_set = { 66 | base = { 67 | filename = "__flib__/graphics/technology-slots.png", 68 | position = { 576, y }, 69 | size = { 144, 200 }, 70 | }, 71 | shadow = default_shadow, 72 | }, 73 | hovered_graphical_set = { 74 | base = { 75 | filename = "__flib__/graphics/technology-slots.png", 76 | position = { 720, y }, 77 | size = { 144, 200 }, 78 | }, 79 | shadow = default_shadow, 80 | }, 81 | clicked_graphical_set = { 82 | base = { 83 | filename = "__flib__/graphics/technology-slots.png", 84 | position = { 720, y }, 85 | size = { 144, 200 }, 86 | }, 87 | shadow = default_shadow, 88 | }, 89 | selected_graphical_set = { 90 | base = { 91 | filename = "__flib__/graphics/technology-slots.png", 92 | position = { 864, y }, 93 | size = { 144, 200 }, 94 | }, 95 | shadow = default_shadow, 96 | }, 97 | selected_hovered_graphical_set = { 98 | base = { 99 | filename = "__flib__/graphics/technology-slots.png", 100 | position = { 1008, y }, 101 | size = { 144, 200 }, 102 | }, 103 | shadow = default_shadow, 104 | }, 105 | selected_clicked_graphical_set = { 106 | base = { 107 | filename = "__flib__/graphics/technology-slots.png", 108 | position = { 1008, y }, 109 | size = { 144, 200 }, 110 | }, 111 | shadow = default_shadow, 112 | }, 113 | padding = 0, 114 | size = { 72, 100 }, 115 | left_click_sound = { filename = "__core__/sound/gui-square-button-large.ogg", volume = 1 }, 116 | } 117 | 118 | styles["flib_technology_slot_level_label_" .. name] = { 119 | type = "label_style", 120 | font = "technology-slot-level-font", 121 | font_color = level_color, 122 | top_padding = 66, 123 | width = 26, 124 | horizontal_align = "center", 125 | } 126 | 127 | styles["flib_technology_slot_level_range_label_" .. name] = { 128 | type = "label_style", 129 | font = "technology-slot-level-font", 130 | font_color = level_range_color, 131 | top_padding = 66, 132 | right_padding = 4, 133 | width = 72, 134 | horizontal_align = "right", 135 | } 136 | end 137 | 138 | build_technology_slot("available", 0, { 77, 71, 48 }, { 255, 241, 183 }) 139 | build_technology_slot("conditionally_available", 200, { 95, 68, 32 }, { 255, 234, 206 }) 140 | build_technology_slot("not_available", 400, { 116, 34, 32 }, { 255, 214, 213 }) 141 | build_technology_slot("researched", 600, { 0, 84, 5 }, { 165, 255, 171 }) 142 | build_technology_slot("disabled", 800, { 132, 132, 132 }, { 132, 132, 132 }) 143 | 144 | styles.flib_technology_slot_sprite_flow = { 145 | type = "horizontal_flow_style", 146 | width = 72, 147 | height = 68, 148 | vertical_align = "center", 149 | horizontal_align = "center", 150 | } 151 | 152 | styles.flib_technology_slot_sprite = { 153 | type = "image_style", 154 | size = 64, 155 | stretch_image_to_widget_size = true, 156 | } 157 | 158 | styles.flib_technology_slot_ingredients_flow = { 159 | type = "horizontal_flow_style", 160 | top_padding = 82, 161 | left_padding = 2, 162 | } 163 | 164 | styles.flib_technology_slot_ingredient = { 165 | type = "image_style", 166 | size = 16, 167 | stretch_image_to_widget_size = true, 168 | } 169 | 170 | styles.flib_technology_slot_progressbar = { 171 | type = "progressbar_style", 172 | bar = { position = { 305, 39 }, corner_size = 4 }, 173 | bar_shadow = { 174 | base = { position = { 296, 39 }, corner_size = 4 }, 175 | shadow = { 176 | left = { position = { 456, 152 }, size = { 16, 1 } }, 177 | center = { position = { 472, 152 }, size = { 1, 1 } }, 178 | right = { position = { 473, 152 }, size = { 16, 1 } }, 179 | }, 180 | }, 181 | bar_width = 4, 182 | color = { g = 1 }, 183 | width = 72, 184 | } 185 | -------------------------------------------------------------------------------- /queue.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.queue" then 2 | return require("__flib__.queue") 3 | end 4 | 5 | --- Lua queue implementation. 6 | --- 7 | --- Based on "Queues and Double Queues" from [Programming in Lua](http://www.lua.org/pil/11.4.html). 8 | --- ```lua 9 | --- local flib_queue = require("__flib__.queue") 10 | --- ``` 11 | --- @class flib_queue 12 | local flib_queue = {} 13 | 14 | ---@class flib.Queue: { [integer]: T, first: integer, last: integer } 15 | 16 | --- Create a new queue. 17 | --- @return flib.Queue 18 | function flib_queue.new() 19 | return { first = 0, last = -1 } 20 | end 21 | 22 | --- Push an element into the front of the queue. 23 | --- @generic T 24 | --- @param self flib.Queue 25 | --- @param value T 26 | function flib_queue.push_front(self, value) 27 | local first = self.first - 1 28 | self.first = first 29 | self[first] = value 30 | end 31 | 32 | --- Push an element into the back of the queue. 33 | --- @generic T 34 | --- @param self flib.Queue 35 | --- @param value `T` 36 | function flib_queue.push_back(self, value) 37 | local last = self.last + 1 38 | self.last = last 39 | self[last] = value 40 | end 41 | 42 | --- Retrieve an element from the front of the queue. 43 | --- @generic T 44 | --- @param self flib.Queue 45 | --- @return T? 46 | function flib_queue.pop_front(self) 47 | local first = self.first 48 | if first > self.last then 49 | return 50 | end 51 | local value = self[first] 52 | self[first] = nil 53 | self.first = first + 1 54 | return value 55 | end 56 | 57 | --- Retrieve an element from the back of the queue. 58 | --- @generic T 59 | --- @param self flib.Queue 60 | --- @return T? 61 | function flib_queue.pop_back(self) 62 | local last = self.last 63 | if self.first > last then 64 | return 65 | end 66 | local value = self[last] 67 | self[last] = nil 68 | self.last = last - 1 69 | return value 70 | end 71 | 72 | --- Iterate over a queue's elements from the beginning to the end. 73 | --- 74 | --- # Example 75 | --- 76 | --- ```lua 77 | --- local my_queue = queue.new() 78 | --- for i = 1, 10 do 79 | --- queue.push_back(my_queue, 1) 80 | --- end 81 | --- 82 | --- -- 1 2 3 4 5 6 7 8 9 10 83 | --- for num in queue.iter(my_queue) do 84 | --- log(i) 85 | --- end 86 | --- ``` 87 | --- @generic T 88 | --- @param self flib.Queue 89 | --- @return fun(self: flib.Queue, index: integer): T 90 | function flib_queue.iter(self) 91 | local i = self.first - 1 92 | return function() 93 | if i < self.last then 94 | i = i + 1 95 | return i, self[i] 96 | end 97 | end 98 | end 99 | 100 | --- Iterate over a queue's elements from the end to the beginning. 101 | --- 102 | --- # Example 103 | --- 104 | --- ```lua 105 | --- local my_queue = queue.new() 106 | --- for i = 1, 10 do 107 | --- queue.push_back(my_queue, 1) 108 | --- end 109 | --- 110 | --- -- 10 9 8 7 6 5 4 3 2 1 111 | --- for num in queue.iter_rev(my_queue) do 112 | --- log(i) 113 | --- end 114 | --- ``` 115 | --- @generic T 116 | --- @param self flib.Queue 117 | --- @return fun(self: flib.Queue, index: integer): T 118 | function flib_queue.iter_rev(self) 119 | local i = self.last + 1 120 | return function() 121 | if i > self.first then 122 | i = i - 1 123 | return i, self[i] 124 | end 125 | end 126 | end 127 | 128 | --- Get the length of the queue. 129 | --- @generic T 130 | --- @param self flib.Queue 131 | --- @return number 132 | function flib_queue.length(self) 133 | return math.abs(self.last - self.first + 1) 134 | end 135 | 136 | return flib_queue 137 | -------------------------------------------------------------------------------- /reverse-defines.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.reverse-defines" then 2 | return require("__flib__.reverse-defines") 3 | end 4 | 5 | --- Defines reverse lookup table. 6 | --- 7 | --- NOTE: Type intellisense simply does not work for this module, and there is no easy way to fix 8 | --- it. Use of this module is discouraged. 9 | --- 10 | --- # Example 11 | --- 12 | --- ```lua 13 | --- event.on_built_entity(function(e) 14 | --- local player = game.get_player(e.player_index) 15 | --- local controller_name = reverse_defines.controllers[player.controller_type] 16 | --- end) 17 | --- ``` 18 | local flib_reverse_defines = {} 19 | 20 | local function build_reverse_defines(lookup_table, base_table) 21 | lookup_table = lookup_table or {} 22 | for k, v in pairs(base_table) do 23 | if type(v) == "table" then 24 | lookup_table[k] = {} 25 | build_reverse_defines(lookup_table[k], v) 26 | else 27 | lookup_table[v] = k 28 | end 29 | end 30 | end 31 | 32 | build_reverse_defines(flib_reverse_defines, defines) 33 | 34 | return flib_reverse_defines 35 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | -------------------------------------------------------------------------------- /table.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.table" then 2 | return require("__flib__.table") 3 | end 4 | 5 | --- Extension of the Lua 5.2 table library. 6 | --- 7 | --- **NOTE:** Several functions in this module will only work with [arrays](https://www.lua.org/pil/11.1.html), 8 | --- which are tables with sequentially numbered keys. All table functions will work with arrays as well, but 9 | --- array functions **will not** work with tables. 10 | --- ```lua 11 | --- local flib_table: = require("__flib__.table") 12 | --- ``` 13 | --- @class flib_table: tablelib 14 | local flib_table = {} 15 | 16 | -- Import lua table functions 17 | for name, func in pairs(table) do 18 | flib_table[name] = func 19 | end 20 | 21 | --- Shallow copy an array's values into a new array. 22 | --- 23 | --- This function is optimized specifically for arrays, and should be used in place of `table.shallow_copy` for arrays. 24 | --- @param arr flib.Array 25 | --- @return flib.Array 26 | function flib_table.array_copy(arr) 27 | local new_arr = {} 28 | for i = 1, #arr do 29 | new_arr[i] = arr[i] 30 | end 31 | return new_arr 32 | end 33 | 34 | --- Merge all of the given arrays into a single array. 35 | --- @param arrays flib.Array An array of arrays to merge. 36 | --- @return flib.Array 37 | function flib_table.array_merge(arrays) 38 | local output = {} 39 | local i = 0 40 | for j = 1, #arrays do 41 | local arr = arrays[j] 42 | for k = 1, #arr do 43 | i = i + 1 44 | output[i] = arr[k] 45 | end 46 | end 47 | return output 48 | end 49 | 50 | --- Perform a binary search of the array using the given comparator function. The array must be sorted 51 | --- in a manner compatible with the comparator and must not be modified during the search. The 52 | --- comparator should return `0` if the element matches the target, a negative number if the element 53 | --- precedes the target, or a positive number if the element succeeds the target. 54 | --- 55 | --- ### Example 56 | --- 57 | --- ```lua 58 | --- local nums = {1, 3, 4, 8, 20, 69} 59 | --- local looking_for = 20 60 | --- local i, match = flib_table.binary_search(nums, function(elem) return looking_for - elem end) 61 | --- assert(i == 5) 62 | --- assert(match == looking_for) 63 | --- ``` 64 | --- @generic T 65 | --- @param array T[] 66 | --- @param comparator fun(elem: T): integer 67 | --- @return integer? The index of the matched element. 68 | --- @return T? The matched element. 69 | function flib_table.binary_search(array, comparator) 70 | local low, high = 1, #array 71 | assert(high, "Invalid array was passed to binary search.") 72 | while low < high do 73 | local i = low + math.floor((high - low) / 2) 74 | local elem = array[i] 75 | assert(elem, "Found a nil element during binary search; array was modified or is invalid.") 76 | local res = comparator(elem) 77 | if res < 0 then 78 | high = i - 1 79 | elseif res > 0 then 80 | low = i + 1 81 | else 82 | return i, array[i] 83 | end 84 | end 85 | end 86 | 87 | --- Recursively compare two tables for inner equality. 88 | --- 89 | --- Does not compare metatables. 90 | --- @param tbl1 table 91 | --- @param tbl2 table 92 | --- @return boolean 93 | function flib_table.deep_compare(tbl1, tbl2) 94 | if tbl1 == tbl2 then 95 | return true 96 | end 97 | for k, v in pairs(tbl1) do 98 | if type(v) == "table" and type(tbl2[k]) == "table" then 99 | if not flib_table.deep_compare(v, tbl2[k]) then 100 | return false 101 | end 102 | else 103 | if v ~= tbl2[k] then 104 | return false 105 | end 106 | end 107 | end 108 | for k in pairs(tbl2) do 109 | if tbl1[k] == nil then 110 | return false 111 | end 112 | end 113 | return true 114 | end 115 | 116 | --- Recursively copy the contents of a table into a new table. 117 | --- 118 | --- Does not create new copies of Factorio objects. 119 | --- @generic T 120 | --- @param tbl T The table to make a copy of. 121 | --- @return T 122 | function flib_table.deep_copy(tbl) 123 | local lookup_table = {} 124 | local function _copy(tbl) 125 | if type(tbl) ~= "table" then 126 | return tbl 127 | elseif lookup_table[tbl] then 128 | return lookup_table[tbl] 129 | end 130 | local new_table = {} 131 | lookup_table[tbl] = new_table 132 | for index, value in pairs(tbl) do 133 | new_table[_copy(index)] = _copy(value) 134 | end 135 | return setmetatable(new_table, getmetatable(tbl)) 136 | end 137 | return _copy(tbl) 138 | end 139 | 140 | --- Recursively merge two or more tables. 141 | --- 142 | --- Values from earlier tables are overwritten by values from later tables, unless both values are tables, in which case 143 | --- they are recursively merged. 144 | --- 145 | --- Non-merged tables are deep-copied, so the result is brand-new. 146 | --- 147 | --- ### Examples 148 | --- 149 | --- ```lua 150 | --- local tbl = {foo = "bar"} 151 | --- log(tbl.foo) -- logs "bar" 152 | --- log (tbl.bar) -- errors (key is nil) 153 | --- tbl = table.merge{tbl, {foo = "baz", set = 3}} 154 | --- log(tbl.foo) -- logs "baz" 155 | --- log(tbl.set) -- logs "3" 156 | --- ``` 157 | --- @param tables flib.Array An array of tables to merge. 158 | --- @return table 159 | function flib_table.deep_merge(tables) 160 | local output = {} 161 | for _, tbl in ipairs(tables) do 162 | for k, v in pairs(tbl) do 163 | if type(v) == "table" then 164 | if type(output[k] or false) == "table" then 165 | output[k] = flib_table.deep_merge({ output[k], v }) 166 | else 167 | output[k] = flib_table.deep_copy(v) 168 | end 169 | else 170 | output[k] = v 171 | end 172 | end 173 | end 174 | return output 175 | end 176 | 177 | --- Find and return the first key containing the given value. 178 | --- 179 | --- ### Examples 180 | --- 181 | --- ```lua 182 | --- local tbl = {"foo", "bar"} 183 | --- local key_of_foo = table.find(tbl, "foo") -- 1 184 | --- local key_of_baz = table.find(tbl, "baz") -- nil 185 | --- ``` 186 | --- @generic K, V 187 | --- @param tbl table The table to search. 188 | --- @param value V The value to match. Must have an `eq` metamethod set, otherwise will error. 189 | --- @return K? key The first key corresponding to `value`, if any. 190 | function flib_table.find(tbl, value) 191 | for k, v in pairs(tbl) do 192 | if v == value then 193 | return k 194 | end 195 | end 196 | end 197 | 198 | --- Call the given function for each item in the table, and abort if the function returns truthy. 199 | --- 200 | --- Calls `callback(value, key)` for each item in the table, and immediately ceases iteration if the callback returns truthy. 201 | --- 202 | --- ### Examples 203 | --- 204 | --- ```lua 205 | --- local tbl = {1, 2, 3, 4, 5} 206 | --- -- Run a function for each item (identical to a standard FOR loop) 207 | --- table.for_each(tbl, function(v) game.print(v) end) 208 | --- -- Determine if any value in the table passes the test 209 | --- local value_is_even = table.for_each(tbl, function(v) return v % 2 == 0 end) 210 | --- -- Determine if ALL values in the table pass the test (invert the test result and function return) 211 | --- local all_values_less_than_six = not table.for_each(tbl, function(v) return not (v < 6) end) 212 | --- ``` 213 | --- @generic K, V 214 | --- @param tbl table 215 | --- @param callback fun(value: V, key: K): boolean Receives `value` and `key` as parameters. 216 | --- @return boolean Whether the callback returned truthy for any one item, and thus halted iteration. 217 | function flib_table.for_each(tbl, callback) 218 | for k, v in pairs(tbl) do 219 | if callback(v, k) then 220 | return true 221 | end 222 | end 223 | return false 224 | end 225 | 226 | --- Call the given function on a set number of items in a table, returning the next starting key. 227 | --- 228 | --- Calls `callback(value, key)` over `n` items from `tbl` or until the end is reached, starting after `from_k`. 229 | --- 230 | --- The first return value of each invocation of `callback` will be collected and returned in a table keyed by the 231 | --- current item's key. 232 | --- 233 | --- The second return value of `callback` is a flag requesting deletion of the current item. 234 | --- 235 | --- The third return value of `callback` is a flag requesting that the iteration be immediately aborted. Use this flag to 236 | --- early return on some condition in `callback`. When aborted, `for_n_of` will return the previous key as `from_k`, so 237 | --- the next call to `for_n_of` will restart on the key that was aborted (unless it was also deleted). 238 | --- 239 | --- **DO NOT** delete entires from `tbl` from within `callback`, this will break the iteration. Use the deletion flag 240 | --- instead. 241 | --- 242 | --- ### Examples 243 | --- 244 | --- ```lua 245 | --- local extremely_large_table = { 246 | --- [1000] = 1, 247 | --- [999] = 2, 248 | --- [998] = 3, 249 | --- ..., 250 | --- [2] = 999, 251 | --- [1] = 1000, 252 | --- } 253 | --- event.on_tick(function() 254 | --- storage.from_k = table.for_n_of(extremely_large_table, storage.from_k, 10, function(v) game.print(v) end) 255 | --- end) 256 | --- ``` 257 | --- @generic K, V, C 258 | --- @param tbl table The table to iterate over. 259 | --- @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. 260 | --- @param n number The number of items to iterate. 261 | --- @param callback fun(value: V, key: K):C,boolean,boolean Receives `value` and `key` as parameters. 262 | --- @param _next? fun(tbl: table, from_k: K):K,V A custom `next()` function. If not provided, the default `next()` will be used. 263 | --- @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`. 264 | --- @return table results The results compiled from the first return of `callback`. 265 | --- @return boolean reached_end Whether or not the end of the table was reached on this iteration. 266 | function flib_table.for_n_of(tbl, from_k, n, callback, _next) 267 | -- Bypass if a custom `next` function was provided 268 | if not _next then 269 | -- Verify start key exists, else start from scratch 270 | if from_k and not tbl[from_k] then 271 | from_k = nil 272 | end 273 | -- Use default `next` 274 | _next = next 275 | end 276 | 277 | local delete 278 | local prev 279 | local abort 280 | local result = {} 281 | 282 | -- Run `n` times 283 | for _ = 1, n, 1 do 284 | local v 285 | if not delete then 286 | prev = from_k 287 | end 288 | from_k, v = _next(tbl, from_k) 289 | if delete then 290 | tbl[delete] = nil 291 | end 292 | 293 | if from_k then 294 | result[from_k], delete, abort = callback(v, from_k) 295 | if delete then 296 | delete = from_k 297 | end 298 | if abort then 299 | break 300 | end 301 | else 302 | return from_k, result, true 303 | end 304 | end 305 | 306 | if delete then 307 | tbl[delete] = nil 308 | from_k = prev 309 | elseif abort then 310 | from_k = prev 311 | end 312 | return from_k, result, false 313 | end 314 | 315 | -- TODO: Remove array_insert, create separate array_filter function 316 | 317 | --- Create a filtered version of a table based on the results of a filter function. 318 | --- 319 | --- Calls `filter(value, key)` on each element in the table, returning a new table with only pairs for which 320 | --- `filter` returned a truthy value. 321 | --- 322 | --- ### Examples 323 | --- 324 | --- ```lua 325 | --- local tbl = {1, 2, 3, 4, 5, 6} 326 | --- local just_evens = table.filter(tbl, function(v) return v % 2 == 0 end) -- {[2] = 2, [4] = 4, [6] = 6} 327 | --- local just_evens_arr = table.filter(tbl, function(v) return v % 2 == 0 end, true) -- {2, 4, 6} 328 | --- ``` 329 | --- @generic K, V 330 | --- @param tbl table 331 | --- @param filter fun(value: V, key: K): boolean 332 | --- @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. 333 | --- @return table 334 | function flib_table.filter(tbl, filter, array_insert) 335 | local output = {} 336 | local i = 0 337 | for k, v in pairs(tbl) do 338 | if filter(v, k) then 339 | if array_insert then 340 | i = i + 1 341 | output[i] = v 342 | else 343 | output[k] = v 344 | end 345 | end 346 | end 347 | return output 348 | end 349 | 350 | --- Retrieve the value at the key, or insert the default value. 351 | --- @generic K, V 352 | --- @param table table 353 | --- @param key K 354 | --- @param default_value V 355 | --- @return V 356 | function flib_table.get_or_insert(table, key, default_value) 357 | local value = table[key] 358 | if not value then 359 | table[key] = default_value 360 | return default_value 361 | end 362 | return value 363 | end 364 | 365 | --- Invert the given table such that `[value] = key`, returning a new table. 366 | --- 367 | --- Non-unique values are overwritten based on the ordering from `pairs()`. 368 | --- 369 | --- ### Examples 370 | --- 371 | --- ```lua 372 | --- local tbl = {"foo", "bar", "baz", set = "baz"} 373 | --- local inverted = table.invert(tbl) -- {foo = 1, bar = 2, baz = "set"} 374 | --- ``` 375 | --- @generic K, V 376 | --- @param tbl table 377 | --- @return table 378 | function flib_table.invert(tbl) 379 | local inverted = {} 380 | for k, v in pairs(tbl) do 381 | inverted[v] = k 382 | end 383 | return inverted 384 | end 385 | 386 | --- Create a transformed table using the output of a mapper function. 387 | --- 388 | --- Calls `mapper(value, key)` on each element in the table, using the return as the new value for the key. 389 | --- 390 | --- ### Examples 391 | --- 392 | --- ```lua 393 | --- local tbl = {1, 2, 3, 4, 5} 394 | --- local tbl_times_ten = table.map(tbl, function(v) return v * 10 end) -- {10, 20, 30, 40, 50} 395 | --- ``` 396 | --- @generic K, V, N 397 | --- @param tbl table 398 | --- @param mapper fun(value: V, key: V):N? 399 | --- @return table 400 | function flib_table.map(tbl, mapper) 401 | local output = {} 402 | for k, v in pairs(tbl) do 403 | output[k] = mapper(v, k) 404 | end 405 | return output 406 | end 407 | 408 | local function default_comp(a, b) 409 | return a < b 410 | end 411 | 412 | --- Partially sort an array. 413 | --- 414 | --- 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. 415 | --- @generic V 416 | --- @param arr flib.Array 417 | --- @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. 418 | --- @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. 419 | --- @param comp fun(a: V, b: V) A comparison function for sorting. Must return truthy if `a < b`. 420 | --- @return number? next_index The index to start the next iteration at, or `nil` if the end was reached. 421 | function flib_table.partial_sort(arr, from_index, iterations, comp) 422 | comp = comp or default_comp 423 | local start_index = (from_index and from_index > 2) and from_index or 2 424 | local end_index = start_index + (iterations - 1) 425 | 426 | for j = start_index, end_index do 427 | local key = arr[j] 428 | if not key then 429 | return nil 430 | end 431 | local i = j - 1 432 | 433 | while i > 0 and comp(key, arr[i]) do 434 | arr[i + 1] = arr[i] 435 | i = i - 1 436 | end 437 | 438 | arr[i + 1] = key 439 | end 440 | 441 | return end_index + 1 442 | end 443 | 444 | --- "Reduce" a table's values into a single output value, using the results of a reducer function. 445 | --- 446 | --- Calls `reducer(accumulator, value, key)` on each element in the table, returning a single accumulated output value. 447 | --- 448 | --- ### Examples 449 | --- 450 | --- ```lua 451 | --- local tbl = {10, 20, 30, 40, 50} 452 | --- local sum = table.reduce(tbl, function(acc, v) return acc + v end) 453 | --- local sum_minus_ten = table.reduce(tbl, function(acc, v) return acc + v end, -10) 454 | --- ``` 455 | --- @generic K, V, R 456 | --- @param tbl table 457 | --- @param reducer fun(acc: R, value: V, key: K):R 458 | --- @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. 459 | --- @return R 460 | function flib_table.reduce(tbl, reducer, initial_value) 461 | local accumulator = initial_value 462 | for key, value in pairs(tbl) do 463 | if accumulator then 464 | accumulator = reducer(accumulator, value, key) 465 | else 466 | accumulator = value 467 | end 468 | end 469 | return accumulator 470 | end 471 | 472 | --- Shallowly copy the contents of a table into a new table. 473 | --- 474 | --- The parent table will have a new table reference, but any subtables within it will still have the same table 475 | --- reference. 476 | --- 477 | --- Does not copy metatables. 478 | --- @generic T 479 | --- @param tbl T 480 | --- @param use_rawset boolean? Use rawset to set the values (ignores metamethods). 481 | --- @return T The copied table. 482 | function flib_table.shallow_copy(tbl, use_rawset) 483 | local output = {} 484 | for k, v in pairs(tbl) do 485 | if use_rawset then 486 | rawset(output, k, v) 487 | else 488 | output[k] = v 489 | end 490 | end 491 | return output 492 | end 493 | 494 | --- Shallowly merge two or more tables. 495 | --- Unlike `table.deep_merge`, this will only combine the top level of the tables. 496 | --- @param tables table[] 497 | --- @return table 498 | function flib_table.shallow_merge(tables) 499 | local output = {} 500 | for _, tbl in pairs(tables) do 501 | for key, value in pairs(tbl) do 502 | output[key] = value 503 | end 504 | end 505 | return output 506 | end 507 | 508 | --- Retrieve the size of a table. 509 | --- 510 | --- Uses Factorio's built-in `table_size` function. 511 | --- @type fun(tbl: table):number 512 | flib_table.size = _ENV.table_size 513 | 514 | --- Retrieve a shallow copy of a portion of an array, selected from `start` to `end` inclusive. 515 | --- 516 | --- The original array **will not** be modified. 517 | --- 518 | --- ### Examples 519 | --- 520 | --- ```lua 521 | --- local arr = {10, 20, 30, 40, 50, 60, 70, 80, 90} 522 | --- local sliced = table.slice(arr, 3, 7) -- {30, 40, 50, 60, 70} 523 | --- log(serpent.line(arr)) -- {10, 20, 30, 40, 50, 60, 70, 80, 90} (unchanged) 524 | --- ``` 525 | --- @generic V 526 | --- @param arr flib.Array 527 | --- @param start number? default: `1` 528 | --- @param stop number? Stop at this index. If zero or negative, will stop `n` items from the end of the array (default: `#arr`). 529 | --- @return flib.Array A new array with the copied values. 530 | function flib_table.slice(arr, start, stop) 531 | local output = {} 532 | local n = #arr 533 | 534 | start = start or 1 535 | stop = stop or n 536 | stop = stop <= 0 and (n + stop) or stop 537 | 538 | if start < 1 or start > n then 539 | return {} 540 | end 541 | 542 | local k = 1 543 | for i = start, stop do 544 | output[k] = arr[i] 545 | k = k + 1 546 | end 547 | return output 548 | end 549 | 550 | --- Extract a portion of an array, selected from `start` to `end` inclusive. 551 | -- 552 | --- The original array **will** be modified. 553 | --- 554 | --- ### Examples 555 | --- 556 | --- ```lua 557 | --- local arr = {10, 20, 30, 40, 50, 60, 70, 80, 90} 558 | --- local spliced = table.splice(arr, 3, 7) -- {30, 40, 50, 60, 70} 559 | --- log(serpent.line(arr)) -- {10, 20, 80, 90} (values were removed) 560 | --- ``` 561 | --- @generic V 562 | --- @param arr flib.Array 563 | --- @param start number default: `1` 564 | --- @param stop number? Stop at this index. If zero or negative, will stop `n` items from the end of the array (default: `#arr`). 565 | --- @return flib.Array A new array with the extracted values. 566 | function flib_table.splice(arr, start, stop) 567 | local output = {} 568 | local n = #arr 569 | 570 | start = start or 1 571 | stop = stop or n 572 | stop = stop <= 0 and (n + stop) or stop 573 | 574 | if start < 1 or start > n then 575 | return {} 576 | end 577 | 578 | local k = 1 579 | for _ = start, stop do 580 | output[k] = table.remove(arr, start) 581 | k = k + 1 582 | end 583 | return output 584 | end 585 | 586 | --- @class flib.Array: { [integer]: T } 587 | 588 | return flib_table 589 | -------------------------------------------------------------------------------- /technology.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.technology" then 2 | return require("__flib__.technology") 3 | end 4 | 5 | --- @class flib_technology 6 | local flib_technology = {} 7 | 8 | --- Gets the active or saved research progress for the given technology. 9 | --- @param technology LuaTechnology 10 | --- @param level uint 11 | --- @return double 12 | function flib_technology.get_research_progress(technology, level) 13 | local force = technology.force 14 | local current_research = force.current_research 15 | if current_research and current_research.name == technology.name then 16 | if technology.level == level or not flib_technology.is_multilevel(technology) then 17 | return force.research_progress 18 | else 19 | return 0 20 | end 21 | elseif technology.level == level then 22 | return technology.saved_progress 23 | else 24 | return 0 25 | end 26 | end 27 | 28 | --- Gets the research unit count for the given technology. 29 | --- @param technology LuaTechnology 30 | --- @param level uint? 31 | --- @return uint 32 | function flib_technology.get_research_unit_count(technology, level) 33 | local formula = technology.research_unit_count_formula 34 | if formula then 35 | local level = level or technology.level 36 | return math.floor(helpers.evaluate_expression(formula, { l = level, L = level })) 37 | end 38 | return math.floor(technology.research_unit_count) 39 | end 40 | 41 | --- Returns whether the technology has multiple levels. 42 | --- @param technology LuaTechnology|LuaTechnologyPrototype 43 | --- @return boolean 44 | function flib_technology.is_multilevel(technology) 45 | if technology.object_name == "LuaTechnology" then 46 | technology = technology.prototype 47 | end 48 | return technology.level ~= technology.max_level 49 | end 50 | 51 | --- Returns `true` if the first technology should be ordered before the second technology. For use in `table.sort`. 52 | --- @param tech_a LuaTechnologyPrototype 53 | --- @param tech_b LuaTechnologyPrototype 54 | --- @return boolean 55 | function flib_technology.sort_predicate(tech_a, tech_b) 56 | local ingredients_a = tech_a.research_unit_ingredients 57 | local ingredients_b = tech_b.research_unit_ingredients 58 | local len_a = #ingredients_a 59 | local len_b = #ingredients_b 60 | -- Always put technologies with zero ingredients at the front 61 | if (len_a == 0) ~= (len_b == 0) then 62 | return len_a == 0 63 | end 64 | if #ingredients_a > 0 then 65 | -- Compare ingredient order strings 66 | -- Check the most expensive packs first, and sort based on the first difference 67 | for i = 0, math.min(len_a, len_b) - 1 do 68 | local ingredient_a = ingredients_a[len_a - i] 69 | local ingredient_b = ingredients_b[len_b - i] 70 | local order_a = prototypes.item[ingredient_a.name].order 71 | local order_b = prototypes.item[ingredient_b.name].order 72 | -- Cheaper pack goes in front 73 | if order_a ~= order_b then 74 | return order_a < order_b 75 | end 76 | end 77 | -- Sort the technology with fewer ingredients in front 78 | if len_a ~= len_b then 79 | return len_a < len_b 80 | end 81 | end 82 | -- Compare technology order strings 83 | local order_a = tech_a.order 84 | local order_b = tech_b.order 85 | if order_a ~= order_b then 86 | return order_a < order_b 87 | end 88 | -- Compare prototype names 89 | return tech_a.name < tech_b.name 90 | end 91 | 92 | --- Returns the technology's prototype name with the level suffix stripped. 93 | --- @param technology LuaTechnology|LuaTechnologyPrototype 94 | --- @return string 95 | function flib_technology.get_base_name(technology) 96 | local result = string.gsub(technology.name, "%-%d*$", "") 97 | return result 98 | end 99 | 100 | --- If the technology is multi-level, returns the technology's base name with that level appended, otherwise returns the technology name. 101 | --- @param technology LuaTechnology 102 | --- @param level uint 103 | --- @return string 104 | function flib_technology.get_leveled_name(technology, level) 105 | if flib_technology.is_multilevel(technology) then 106 | return flib_technology.get_base_name(technology) .. "-" .. level 107 | else 108 | return technology.name 109 | end 110 | end 111 | 112 | --- @enum TechnologyResearchState 113 | flib_technology.research_state = { 114 | available = 1, 115 | conditionally_available = 2, 116 | not_available = 3, 117 | researched = 4, 118 | disabled = 5, 119 | } 120 | 121 | return flib_technology 122 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | To run tests with vscode test explorer or lua. 2 | 3 | Adds package.searcher for `__modname__` requires. 4 | Adds some package.path(s) because path explosion will make it find the correct file. 5 | This framework sets up the following. 6 | Adds generic versions of global factorio functions (defines, log, table_size). 7 | 8 | Test Explorer extension, search for tests, run tests (optionally debug tests) 9 | Code Runner (.run) extension, just run the file 10 | Sumneko vscode: run/debug should work. 11 | Lua: From the mod root run `lua tests/test_name.lua` 12 | -------------------------------------------------------------------------------- /tests/dictionary-lite.lua: -------------------------------------------------------------------------------- 1 | local flib_dictionary = require("__flib__.dictionary-lite") 2 | 3 | -- Build demo dictionaries for various kinds of prototypes. Dictionaries don't need to be sorted like this, they can 4 | -- contain anything and everything. For example, in many of my mods I will make a "search" dictionary containing all 5 | -- of the things I need to search, prefixed by their type (i.e. `fluid/crude-oil`, `item/iron-plate`, etc). 6 | local function build_dictionaries() 7 | for type, prototypes in pairs({ 8 | entity = prototypes.entity, 9 | equipment = prototypes.equipment, 10 | equipment_category = prototypes.equipment_category, 11 | fluid = prototypes.fluid, 12 | fuel_category = prototypes.fuel_category, 13 | item = prototypes.item, 14 | item_group = prototypes.item_group, 15 | recipe = prototypes.recipe, 16 | recipe_category = prototypes.recipe_category, 17 | resource_category = prototypes.resource_category, 18 | technology = prototypes.technology, 19 | }) do 20 | flib_dictionary.new(type) 21 | for name, prototype in pairs(prototypes) do 22 | -- Fall back to the internal name if the localised name is invalid. If you don't want to include invalid strings 23 | -- then simply pass `prototype.localised_name` and the resulting dictionary will only include valid translations. 24 | flib_dictionary.add(type, name, { "?", prototype.localised_name, name }) 25 | end 26 | end 27 | end 28 | 29 | -- Handle all relevant events automatically. Be sure to add this line before defining any of your own event handlers. 30 | -- `dictionary-lite` has handlers for `on_init`, `on_configuration_changed`, `on_tick`, `on_string_translated`, and `on_player_joined_game`. 31 | flib_dictionary.handle_events() 32 | 33 | script.on_init(function() 34 | -- If you override any handlers for the events listed above, you will need to manually call the requisite functions. 35 | flib_dictionary.on_init() 36 | build_dictionaries() 37 | end) 38 | 39 | script.on_configuration_changed(function() 40 | flib_dictionary.on_configuration_changed() 41 | -- Dictionaries should be built both during `on_init` and `on_configuration_changed`. 42 | build_dictionaries() 43 | end) 44 | 45 | -- `on_player_dictionaries_ready` is raised when a given player's dictionaries are... ready. Shocking! 46 | --- @param e flib.on_player_dictionaries_ready 47 | script.on_event(flib_dictionary.on_player_dictionaries_ready, function(e) 48 | -- Alternatively, you can get specific dictionaries with `flib_dictionary.get(e.player_index, "item")` et al. 49 | -- For the aforementioned "search" dictionary, I do not handle this event, and instead call `get()` during my 50 | -- search logic and use it if it exists, otherwise falling back to the internal prototype names. 51 | local dicts = flib_dictionary.get_all(e.player_index) 52 | if not dicts then 53 | return 54 | end 55 | if __DebugAdapter then 56 | __DebugAdapter.print(dicts) 57 | else 58 | log(serpent.block(dicts)) 59 | end 60 | end) 61 | -------------------------------------------------------------------------------- /tests/gui-lite.lua: -------------------------------------------------------------------------------- 1 | -- TodoMVC implementation using gui-lite. 2 | -- The following is how I (raiguard) prefer to structure GUIs, but it is not the only way. 3 | 4 | -- GUI 5 | 6 | local flib_gui = require("__flib__.gui-lite") 7 | local mod_gui = require("__core__.lualib.mod-gui") 8 | 9 | --- @alias flib_test.TestGuiMode 10 | --- | "all" 11 | --- | "active" 12 | --- | "completed" 13 | 14 | --- @class flib_test.TestGui 15 | --- @field elems table 16 | --- @field player LuaPlayer 17 | --- @field completed_count integer 18 | --- @field items_left integer 19 | --- @field mode flib_test.TestGuiMode 20 | --- @field pinned boolean 21 | 22 | --- @class flib.TestGuiBase 23 | local gui = {} 24 | 25 | --- @param name string 26 | --- @param sprite string 27 | --- @param tooltip LocalisedString 28 | --- @param handler function 29 | local function frame_action_button(name, sprite, tooltip, handler) 30 | return { 31 | type = "sprite-button", 32 | name = name, 33 | style = "frame_action_button", 34 | sprite = sprite .. "_white", 35 | hovered_sprite = sprite .. "_black", 36 | clicked_sprite = sprite .. "_black", 37 | tooltip = tooltip, 38 | handler = handler, 39 | } 40 | end 41 | 42 | --- Build the GUI for the given player. 43 | --- @param player LuaPlayer 44 | function gui.build(player) 45 | -- `elems` is a table consisting of all GUI elements that were given names, keyed by their name. 46 | -- `window` is the GUI element that was created first, which in this case, was the top-level frame. 47 | -- The second argument can be a single element or an array of elements. Here we pass a single element. 48 | local elems = flib_gui.add(player.gui.screen, { 49 | type = "frame", 50 | name = "flib_todo_window", 51 | direction = "vertical", 52 | -- Use `elem_mods` to make modifications to the GUI element after creation. 53 | --- @diagnostic disable-next-line: missing-fields 54 | elem_mods = { auto_center = true }, 55 | -- If `handler` is a function, it will call that function for any GUI event on this element. 56 | -- If it is a dictioanry of event -> function, it will call the corresponding function for the corresponding event. 57 | handler = { [defines.events.on_gui_closed] = gui.on_window_closed }, 58 | -- Children can be defined as array members of an element. 59 | { 60 | type = "flow", 61 | style = "flib_titlebar_flow", 62 | -- The string must be the name of an element that is present in the `elems` table. To set drag_target to a 63 | -- LuaGuiElement reference, do so inside of the `elem_mods` table. 64 | drag_target = "flib_todo_window", 65 | -- For a real mod, you would want to use localised strings for the captions. They are omitted here to keep this 66 | -- demo in one file. 67 | { type = "label", style = "frame_title", caption = "TodoMVC", ignored_by_interaction = true }, 68 | { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true }, 69 | -- You can use helper functions for repetitive elements. 70 | frame_action_button("pin_button", "flib_pin", { "gui.flib-keep-open" }, gui.toggle_pinned), 71 | frame_action_button("close_button", "utility/close", { "gui.close-instruction" }, gui.hide), 72 | }, 73 | { 74 | type = "frame", 75 | style = "inside_shallow_frame", 76 | -- Use `style_mods` to make modifications to the element's style. 77 | --- @diagnostic disable-next-line: missing-fields 78 | style_mods = { width = 500 }, 79 | direction = "vertical", 80 | { 81 | type = "frame", 82 | style = "subheader_frame", 83 | { 84 | type = "textfield", 85 | name = "textfield", 86 | style = "flib_widthless_textfield", 87 | style_mods = { horizontally_stretchable = true }, 88 | handler = { 89 | -- Multiple different event handlers 90 | [defines.events.on_gui_confirmed] = gui.on_textfield_confirmed, 91 | [defines.events.on_gui_text_changed] = gui.on_textfield_text_changed, 92 | }, 93 | { 94 | type = "label", 95 | name = "placeholder", 96 | style_mods = { font_color = { a = 0.4 } }, 97 | caption = "What needs to be done?", 98 | ignored_by_interaction = true, 99 | }, 100 | }, 101 | }, 102 | { 103 | type = "scroll-pane", 104 | style = "flib_naked_scroll_pane_no_padding", 105 | style_mods = { maximal_height = 400 }, 106 | -- We use a flow here to allow customizing the vertical spacing with style_mods. Normally you want to use a data 107 | -- stage style on the scroll-pane for this. 108 | { 109 | type = "flow", 110 | name = "todos_flow", 111 | style_mods = { vertical_spacing = 8, padding = 12 }, 112 | direction = "vertical", 113 | }, 114 | }, 115 | { 116 | type = "frame", 117 | name = "subfooter", 118 | style = "subfooter_frame", 119 | { 120 | type = "flow", 121 | style = "centering_horizontal_flow", 122 | { type = "label", name = "count_label", style_mods = { left_margin = 8 }, caption = "0 items left" }, 123 | { type = "empty-widget", style = "flib_horizontal_pusher" }, 124 | { 125 | type = "flow", 126 | style_mods = { horizontal_spacing = 8 }, 127 | { 128 | type = "radiobutton", 129 | name = "all_radio", 130 | caption = "All", 131 | state = true, 132 | -- Element tags can be specified like this. 133 | tags = { mode = "all" }, 134 | handler = { [defines.events.on_gui_checked_state_changed] = gui.change_mode }, 135 | }, 136 | { 137 | type = "radiobutton", 138 | name = "active_radio", 139 | caption = "Active", 140 | state = false, 141 | tags = { mode = "active" }, 142 | handler = { [defines.events.on_gui_checked_state_changed] = gui.change_mode }, 143 | }, 144 | { 145 | type = "radiobutton", 146 | name = "completed_radio", 147 | caption = "Completed", 148 | state = false, 149 | tags = { mode = "completed" }, 150 | handler = { [defines.events.on_gui_checked_state_changed] = gui.change_mode }, 151 | }, 152 | }, 153 | { type = "empty-widget", style = "flib_horizontal_pusher" }, 154 | { 155 | type = "button", 156 | name = "clear_completed_button", 157 | caption = "Clear completed", 158 | enabled = false, 159 | -- Because on_gui_click is the only event related to buttons, we can take a shortcut. 160 | handler = gui.clear_completed, 161 | }, 162 | }, 163 | }, 164 | }, 165 | }) 166 | 167 | -- In a real mod, you would want to initially hide the GUI and not set opened until the player opens it. 168 | player.opened = elems.flib_todo_window 169 | 170 | --- @type flib_test.TestGui 171 | storage.guis[player.index] = { 172 | elems = elems, 173 | player = player, 174 | -- State variables 175 | completed_count = 0, 176 | items_left = 0, 177 | mode = "all", 178 | pinned = false, 179 | } 180 | end 181 | 182 | --- @param e EventData.on_gui_confirmed 183 | function gui.on_textfield_text_changed(_, e) 184 | if #e.element.text == 0 then 185 | e.element.placeholder.visible = true 186 | else 187 | e.element.placeholder.visible = false 188 | end 189 | end 190 | 191 | --- @param self flib_test.TestGui 192 | --- @param e EventData.on_gui_checked_state_changed 193 | function gui.change_mode(self, e) 194 | local mode = e.element.tags.mode --[[@as flib_test.TestGuiMode]] 195 | self.mode = mode 196 | self.elems.all_radio.state = mode == "all" 197 | self.elems.active_radio.state = mode == "active" 198 | self.elems.completed_radio.state = mode == "completed" 199 | -- Adjust checkbox visibility 200 | for _, checkbox in pairs(self.elems.todos_flow.children) do 201 | checkbox.visible = (checkbox.state and mode ~= "active") or (not checkbox.state and mode ~= "completed") 202 | end 203 | end 204 | 205 | --- @param self flib_test.TestGui 206 | function gui.clear_completed(self) 207 | for _, checkbox in pairs(self.elems.todos_flow.children) do 208 | if checkbox.state then 209 | checkbox.destroy() 210 | end 211 | end 212 | self.completed_count = 0 213 | gui.update_footer(self) 214 | end 215 | 216 | --- @param self flib_test.TestGui 217 | function gui.hide(self) 218 | self.elems.flib_todo_window.visible = false 219 | end 220 | 221 | --- @param self flib_test.TestGui 222 | --- @param e EventData.on_gui_checked_state_changed 223 | function gui.on_todo_toggled(self, e) 224 | local checkbox = e.element 225 | if checkbox.state then 226 | -- Hide this item if needed 227 | if self.mode == "active" then 228 | checkbox.visible = false 229 | end 230 | -- Decrement items left counter 231 | self.items_left = self.items_left - 1 232 | self.completed_count = self.completed_count + 1 233 | else 234 | self.items_left = self.items_left + 1 235 | self.completed_count = self.completed_count - 1 236 | end 237 | gui.update_footer(self) 238 | end 239 | 240 | --- @param self flib_test.TestGui 241 | --- @param e EventData.on_gui_confirmed 242 | function gui.on_textfield_confirmed(self, e) 243 | local title = e.element.text 244 | if #title == 0 then 245 | self.player.play_sound({ path = "utility/cannot_build" }) 246 | return 247 | end 248 | local todos_flow = self.elems.todos_flow 249 | flib_gui.add(todos_flow, { 250 | type = "checkbox", 251 | --- @diagnostic disable-next-line: missing-fields 252 | style_mods = { horizontally_stretchable = true }, 253 | caption = title, 254 | state = false, 255 | visible = self.mode ~= "completed", 256 | handler = { [defines.events.on_gui_checked_state_changed] = gui.on_todo_toggled }, 257 | }) 258 | self.items_left = self.items_left + 1 259 | e.element.text = "" 260 | -- The above line doesn't fire the on_gui_text_changed event, so call its handler manually. 261 | -- The event table isn't actually the right type, but it has the same info, so it's fine. 262 | gui.on_textfield_text_changed(self, e) 263 | gui.update_footer(self) 264 | end 265 | 266 | --- @param self flib_test.TestGui 267 | function gui.on_window_closed(self) 268 | -- Don't close when enabling the pin 269 | if self.pinned then 270 | return 271 | end 272 | gui.hide(self) 273 | end 274 | 275 | --- @param self flib_test.TestGui 276 | function gui.show(self) 277 | self.elems.flib_todo_window.visible = true 278 | self.elems.textfield.focus() 279 | if not self.pinned then 280 | self.player.opened = self.elems.flib_todo_window 281 | end 282 | end 283 | 284 | --- @param self flib_test.TestGui 285 | function gui.toggle_pinned(self) 286 | -- "Pinning" the GUI will remove it from player.opened, allowing it to coexist with other windows. 287 | -- I highly recommend implementing this for your GUIs. flib includes the requisite sprites and locale for the button. 288 | self.pinned = not self.pinned 289 | if self.pinned then 290 | self.elems.close_button.tooltip = { "gui.close" } 291 | self.elems.pin_button.sprite = "flib_pin_black" 292 | self.elems.pin_button.style = "flib_selected_frame_action_button" 293 | if self.player.opened == self.elems.flib_todo_window then 294 | self.player.opened = nil 295 | end 296 | else 297 | self.elems.close_button.tooltip = { "gui.close-instruction" } 298 | self.elems.pin_button.sprite = "flib_pin_white" 299 | self.elems.pin_button.style = "frame_action_button" 300 | self.player.opened = self.elems.flib_todo_window 301 | end 302 | end 303 | 304 | --- @param self flib_test.TestGui 305 | function gui.toggle_visible(self) 306 | if self.elems.flib_todo_window.visible then 307 | gui.hide(self) 308 | else 309 | gui.show(self) 310 | end 311 | end 312 | 313 | --- @param self flib_test.TestGui 314 | function gui.update_footer(self) 315 | self.elems.count_label.caption = self.items_left .. " items left" 316 | self.elems.clear_completed_button.enabled = self.completed_count > 0 317 | end 318 | 319 | -- Add all functions in the `gui` table as callable handlers. This is required in order for functions in `gui.add` to 320 | -- work. For convenience, flib will ignore any value that isn't a function. 321 | -- The second argument is an optional wrapper function that will be called in lieu of the specified handler of an 322 | -- element. It is used in this case to get the GUI table for the corresponding player before calling the handler. 323 | flib_gui.add_handlers(gui, function(e, handler) 324 | local self = storage.guis[e.player_index] 325 | if self then 326 | handler(self, e) 327 | end 328 | end) 329 | 330 | -- BOOTSTRAP 331 | 332 | -- Handle all 'on_gui_*' events with `flib_gui.dispatch`. If you don't call this, then your element handlers won't work! 333 | -- If you wish to have custom logic for a specific GUI event, you can call `flib_gui.dispatch` yourself in your main 334 | -- event handler. `handle_events` will not override any existing event handlers. 335 | flib_gui.handle_events() 336 | 337 | -- Initalize guis table 338 | script.on_init(function() 339 | storage.guis = {} 340 | end) 341 | 342 | -- Create the GUI when a player is created 343 | script.on_event(defines.events.on_player_created, function(e) 344 | local player = game.get_player(e.player_index) --[[@as LuaPlayer]] 345 | gui.build(player) 346 | -- Add mod_gui button 347 | local button_flow = mod_gui.get_button_flow(player) --[[@as LuaGuiElement]] 348 | flib_gui.add(button_flow, { 349 | type = "button", 350 | style = mod_gui.button_style, 351 | caption = "TodoMVC", 352 | handler = gui.toggle_visible, 353 | }) 354 | end) 355 | 356 | -- For a real mod, you would also want to handle on_configuration_changed to rebuild your GUIs, and on_player_removed 357 | -- to remove the GUI table from storage. You would also want to ensure that the GUI is valid before running methods. 358 | -- For the sake of brevity, these things were not covered in this demo. 359 | -------------------------------------------------------------------------------- /tests/position.lua: -------------------------------------------------------------------------------- 1 | local position = require("__flib__.position") 2 | 3 | local res = position.add({ 1, 3 }, { -2, 1 }) 4 | assert(res[1] == -1) 5 | assert(res[2] == 4) 6 | 7 | local res = position.ceil({ 1.2, 1.1 }) 8 | assert(res[1] == 2) 9 | assert(res[2] == 2) 10 | 11 | local res = position.from_chunk({ 1, 3 }) 12 | assert(res[1] == 32) 13 | assert(res[2] == 96) 14 | 15 | assert(position.distance({ 1, 3 }, { 2, 4 }) == math.sqrt(2)) 16 | assert(position.distance_squared({ 1, 3 }, { 2, 4 }) == 2) 17 | 18 | local res = position.div({ 1, 2 }, { 5, 3 }) 19 | assert(res[1] == 0.2) 20 | assert(res[2] == (2 / 3)) 21 | local res = position.div({ x = 1, y = 2 }, { x = 5, y = 3 }) 22 | assert(res.x == 0.2) 23 | assert(res.y == (2 / 3)) 24 | 25 | local res = position.ensure_explicit({ x = 1, y = 2 }) 26 | assert(res.x == 1) 27 | assert(res.y == 2) 28 | local res = position.ensure_explicit({ 1, 2 }) 29 | assert(res.x == 1) 30 | assert(res.y == 2) 31 | 32 | local res = position.ensure_short({ x = 1, y = 2 }) 33 | assert(res[1] == 1) 34 | assert(res[2] == 2) 35 | local res = position.ensure_short({ 1, 2 }) 36 | assert(res[1] == 1) 37 | assert(res[2] == 2) 38 | 39 | assert(position.eq({ 1, 1 }, { 1, 1 })) 40 | assert(not position.eq({ 1, 3 }, { 1, 1 })) 41 | assert(not position.eq({ 3, 3 }, { 1, 1 })) 42 | 43 | local res = position.floor({ 1.1, 2.2 }) 44 | assert(res[1] == 1) 45 | assert(res[2] == 2) 46 | 47 | local res = position.from_chunk({ 1, 2 }) 48 | assert(res[1] == 32) 49 | assert(res[2] == 64) 50 | 51 | assert(position.le({ 1, 1 }, { 1, 1 })) 52 | assert(position.le({ 1, 0 }, { 1, 1 })) 53 | assert(position.le({ 0, 0 }, { 1, 1 })) 54 | assert(not position.le({ 2, 1 }, { 1, 1 })) 55 | assert(not position.le({ 2, 2 }, { 1, 1 })) 56 | 57 | assert(position.lt({ 0, 0 }, { 1, 1 })) 58 | assert(not position.lt({ 1, 1 }, { 1, 1 })) 59 | assert(not position.lt({ 1, 0 }, { 1, 1 })) 60 | assert(not position.lt({ 2, 1 }, { 1, 1 })) 61 | assert(not position.lt({ 2, 2 }, { 1, 1 })) 62 | 63 | local res = position.mod({ 4.5, 3 }, { 3, 3 }) 64 | assert(res[1] == 1.5) 65 | assert(res[2] == 0) 66 | 67 | local res = position.mul({ 4.5, 3 }, { 3, 3 }) 68 | assert(res[1] == 13.5) 69 | assert(res[2] == 9) 70 | 71 | local res = position.sub({ 4.5, 3 }, { 3, 3 }) 72 | assert(res[1] == 1.5) 73 | assert(res[2] == 0) 74 | 75 | local res = position.pow({ 3, 3 }, { 3, 2 }) 76 | assert(res[1] == 27) 77 | assert(res[2] == 9) 78 | 79 | local res = position.to_chunk({ 100, 150 }) 80 | assert(res[1] == 3) 81 | assert(res[2] == 4) 82 | 83 | local res = position.to_tile({ 3.2, 3.5 }) 84 | assert(res[1] == 3) 85 | assert(res[2] == 3) 86 | -------------------------------------------------------------------------------- /tests/slots.lua: -------------------------------------------------------------------------------- 1 | --- @param pane LuaGuiElement 2 | --- @param prefix string 3 | --- @param inset boolean 4 | local function make_row(pane, prefix, inset) 5 | local panel 6 | if inset then 7 | panel = pane.add({ type = "frame", style = "slot_button_deep_frame" }) 8 | else 9 | panel = pane.add({ type = "flow" }) 10 | end 11 | panel.style.top_margin = 12 12 | local colors = { "default", "grey", "red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink" } 13 | for _, color in pairs(colors) do 14 | panel.add({ type = "sprite-button", style = prefix .. color, sprite = "item/stone-brick" }) 15 | end 16 | end 17 | 18 | script.on_event(defines.events.on_player_created, function(e) 19 | local player = game.get_player(e.player_index) 20 | if not player then 21 | return 22 | end 23 | local frame = player.gui.screen.add({ type = "frame", name = "flib_test_frame", caption = "Slots" }) 24 | frame.auto_center = true 25 | 26 | local inner = frame.add({ type = "frame", style = "inside_shallow_frame_with_padding", direction = "vertical" }) 27 | inner.style.top_padding = 0 28 | 29 | make_row(inner, "flib_slot_", false) 30 | make_row(inner, "flib_selected_slot_", false) 31 | make_row(inner, "flib_slot_button_", true) 32 | make_row(inner, "flib_selected_slot_button_", true) 33 | make_row(inner, "flib_standalone_slot_button_", false) 34 | make_row(inner, "flib_selected_standalone_slot_button_", false) 35 | end) 36 | -------------------------------------------------------------------------------- /tests/test_math.lua: -------------------------------------------------------------------------------- 1 | local Test = require("tests.factorio_luaunit") 2 | local math = require("__flib__.math") 3 | -- FIXME: Use Factorio testing framework 4 | --- @diagnostic disable-next-line 5 | math.randomseed(os.clock()) 6 | 7 | --- @diagnostic disable: undefined-field 8 | 9 | function Test_radians() 10 | for i = 1, 90 do 11 | Test.assertAlmostEquals(i * math.deg_to_rad, math.rad(i), 0.1) 12 | end 13 | end 14 | 15 | function Test_degrees() 16 | for i = 1, 90 do 17 | Test.assertAlmostEquals(i * math.rad_to_deg, math.deg(i), 0.1) 18 | end 19 | end 20 | 21 | function Test_round() 22 | Test.assertEquals(math.round(3.51), 4) 23 | Test.assertEquals(math.round(3.51, 0.1), 3.5) 24 | end 25 | 26 | function Test_ceiled() 27 | Test.assertEquals(math.ceiled(3.7), 4) 28 | Test.assertEquals(math.ceiled(3.7), math.ceil(3.7)) 29 | Test.assertEquals(math.ceiled(-3.7), -3) 30 | Test.assertEquals(math.ceiled(-3.7), math.ceil(-3.7)) 31 | Test.assertAlmostEquals(math.ceiled(-3.75, 0.1), -3.8, 0.1) 32 | end 33 | 34 | function Test_floored() 35 | Test.assertEquals(math.floored(3.7), 3) 36 | Test.assertEquals(math.floored(3.7), math.floor(3.7)) 37 | Test.assertEquals(math.floored(-3.7), -4) 38 | Test.assertEquals(math.floored(-3.7), math.floor(-3.7)) 39 | Test.assertAlmostEquals(math.floored(-3.75, 0.1), -3.8, 0.1) 40 | end 41 | 42 | function Test_clamp() 43 | Test.assertEquals(math.clamp(0, 1, 2), 1) 44 | Test.assertEquals(math.clamp(0, 0, 2), 0) 45 | Test.assertEquals(math.clamp(0, 0, 0), 0) 46 | 47 | Test.assertEquals(math.clamp(0, -10, 10), 0) 48 | Test.assertEquals(math.clamp(-100, -10, 10), -10) 49 | Test.assertEquals(math.clamp(100, -10, 10), 10) 50 | 51 | Test.assertEquals(math.clamp(-2), 0) 52 | Test.assertEquals(math.clamp(0.5), 0.5) 53 | Test.assertEquals(math.clamp(3), 1) 54 | 55 | --- Max is smaller than min 56 | Test.assertEquals(math.clamp(0, 1, 0), 1) 57 | Test.assertEquals(math.clamp(2, 1, 0), 0) 58 | Test.assertEquals(math.clamp(1, 1, 0), 0) 59 | end 60 | 61 | local values1 = { 25, 25, 25, 25 } 62 | local values2 = { 10, 25, 40, 45, 50 } 63 | local values3 = { 10, 25, 40, -50, -45 } 64 | local values4 = { -23, -12, -50, -10, -33 } 65 | 66 | function Test_maximum() 67 | Test.assertEquals(math.maximum(values1), 25) 68 | Test.assertEquals(math.maximum(values2), 50) 69 | Test.assertEquals(math.maximum(values3), 40) 70 | Test.assertEquals(math.maximum(values4), -10) 71 | for _ = 1, 5 do 72 | local rando = {} 73 | while #rando < 11 do 74 | rando[#rando + 1] = math.random(-50, 50) 75 | end 76 | Test.assertEquals(math.maximum(rando), math.max(table.unpack(rando))) 77 | end 78 | end 79 | 80 | function Test_minimum() 81 | Test.assertEquals(math.minimum(values1), 25) 82 | Test.assertEquals(math.minimum(values2), 10) 83 | Test.assertEquals(math.minimum(values3), -50) 84 | Test.assertEquals(math.minimum(values4), -50) 85 | for _ = 1, 5 do 86 | local rando = {} 87 | while #rando < 11 do 88 | rando[#rando + 1] = math.random(-50, 50) 89 | end 90 | Test.assertEquals(math.minimum(rando), math.min(table.unpack(rando))) 91 | end 92 | end 93 | 94 | function Test_sum() 95 | Test.assertEquals(math.sum(values1), 100) 96 | Test.assertEquals(math.sum(values2), 170) 97 | Test.assertEquals(math.sum(values3), -20) 98 | Test.assertEquals(math.sum(values4), -128) 99 | end 100 | 101 | function Test_mean() 102 | Test.assertEquals(math.mean(values1), 25) 103 | Test.assertEquals(math.mean(values2), 34) 104 | Test.assertEquals(math.mean(values3), -4) 105 | Test.assertEquals(math.mean(values4), -25.6) 106 | end 107 | 108 | function Test_midrange() 109 | Test.assertEquals(math.midrange(values1), 25) 110 | Test.assertEquals(math.midrange(values2), 30) 111 | Test.assertEquals(math.midrange(values3), -5) 112 | Test.assertEquals(math.midrange(values4), -30) 113 | end 114 | 115 | function Test_range() 116 | Test.assertEquals(math.range(values1), 0) 117 | Test.assertEquals(math.range(values2), 40) 118 | Test.assertEquals(math.range(values3), 90) 119 | Test.assertEquals(math.range(values4), 40) 120 | end 121 | 122 | function Test_sign() 123 | Test.assertEquals(math.sign(0), 1) 124 | Test.assertEquals(math.sign(1), 1) 125 | Test.assertEquals(math.sign(-1), -1) 126 | end 127 | 128 | function Test_lerp() 129 | Test.assertEquals(math.lerp(1, 2, 0.5), 1.5) 130 | Test.assertEquals(math.lerp(0, 10, 0.25), 2.5) 131 | end 132 | 133 | Test.Run() 134 | -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factoriolib/flib/7d82439abad8770a6eefdf5e1a7d2a60a222d026/thumbnail.png -------------------------------------------------------------------------------- /train.lua: -------------------------------------------------------------------------------- 1 | if ... ~= "__flib__.train" then 2 | return require("__flib__.train") 3 | end 4 | 5 | --- Functions for working with trains. 6 | --- ```lua 7 | --- local flib_train = require("__flib__.train") 8 | --- ``` 9 | --- @class flib_train 10 | local flib_train = {} 11 | 12 | local table = table 13 | 14 | --- Get the main locomotive in a given train. 15 | --- @param train LuaTrain 16 | --- @return LuaEntity? locomotive The primary locomotive entity or `nil` when no locomotive was found 17 | function flib_train.get_main_locomotive(train) 18 | if 19 | train.valid 20 | and train.locomotives 21 | and (#train.locomotives.front_movers > 0 or #train.locomotives.back_movers > 0) 22 | then 23 | return train.locomotives.front_movers and train.locomotives.front_movers[1] or train.locomotives.back_movers[1] 24 | end 25 | end 26 | 27 | --- Get the backer_name of the main locomotive in a given train. 28 | --- @param train LuaTrain 29 | --- @return string? backer_name The backer_name of the primary locomotive or `nil` when no locomotive was found 30 | function flib_train.get_backer_name(train) 31 | local loco = flib_train.get_main_locomotive(train) 32 | return loco and loco.backer_name 33 | end 34 | 35 | --- Rotate a single carriage of a train. 36 | --- @param entity LuaEntity 37 | --- @return boolean rotated `true` when rotation was successful. 38 | function flib_train.rotate_carriage(entity) 39 | local disconnected_back = entity.disconnect_rolling_stock(defines.rail_direction.back) 40 | local disconnected_front = entity.disconnect_rolling_stock(defines.rail_direction.front) 41 | entity.rotate({}) 42 | -- Only reconnect the side that was disconnected 43 | local reconnected_front = disconnected_front 44 | local reconnected_back = disconnected_back 45 | if disconnected_back then 46 | reconnected_back = entity.connect_rolling_stock(defines.rail_direction.front) 47 | end 48 | if disconnected_front then 49 | reconnected_front = entity.connect_rolling_stock(defines.rail_direction.back) 50 | end 51 | 52 | if disconnected_front and not reconnected_front then 53 | return false 54 | end 55 | if disconnected_back and not reconnected_back then 56 | return false 57 | end 58 | return true 59 | end 60 | 61 | --- Create a string representing train composition, and return a count of locomotives and wagons in the train. 62 | --- - `L>`: Locomotives 63 | --- - `C`: Cargo wagon 64 | --- - `F`: Fluid wagon 65 | --- - `A`: Artillery wagon 66 | --- @param train LuaTrain 67 | --- @return string? composition The composition string, or `nil` if the train was invalid. 68 | --- @return TrainCompositionCounts? 69 | function flib_train.get_composition_string(train) 70 | if train and train.valid then 71 | local carriages = train.carriages 72 | local string_table = {} 73 | local count_wagons, count_loco_front, count_loco_back, i = 0, 0, 0, 0 74 | local locos_front = train.locomotives.front_movers 75 | for _, carriage in pairs(carriages) do 76 | i = i + 1 77 | if carriage.type == "locomotive" then 78 | local faces_forward = false 79 | for _, loco in pairs(locos_front) do 80 | if carriage.unit_number == loco.unit_number then 81 | faces_forward = true 82 | break 83 | end 84 | end 85 | if faces_forward then 86 | string_table[i] = "