From c6da1fd9ac1687ab9c432e98e79a750c7a6d454e Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 20:59:58 +0000 Subject: [PATCH 01/46] Replace the luals lint config with emmylua fmtk 2.1.4 onwards generates typedefs targeting emmylua that luals misreads, and every fmtk changelog since 2.1.5 states luals support is being dropped. luals 3.18.2 is already the latest release, so there is nothing to bump. The `--clusterio-modules` plugin has no emmylua equivalent, as emmylua has no plugin interface, so it is reimplemented as `workspace.moduleMap`. Those rules rewrite each file's own module path rather than the require string, hence the reversed direction. type.patch.lua is obsolete: `raise_event` now takes `LuaEventType` in the generated typedefs, and `LuaObject` has become `LuaObject.base`. exp_scenario/.luacheckrc was already dead, luacheck is never invoked. Co-Authored-By: Claude Opus 5 (1M context) --- .emmyrc.json | 42 + .github/workflows/fmtk.yml | 169 ++-- .gitignore | 1 + .luarc.json | 103 --- exp_scenario/.luacheckrc | 1697 ------------------------------------ type.patch.lua | 39 - 6 files changed, 136 insertions(+), 1915 deletions(-) create mode 100644 .emmyrc.json delete mode 100644 .luarc.json delete mode 100644 exp_scenario/.luacheckrc delete mode 100644 type.patch.lua diff --git a/.emmyrc.json b/.emmyrc.json new file mode 100644 index 00000000..c103023e --- /dev/null +++ b/.emmyrc.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json", + "runtime": { + "version": "Lua5.2", + "requirePattern": [ "?", "?.lua" ] + }, + "workspace": { + "$comment-moduleMap": "Replaces the fmtk --clusterio-modules plugin, rewrites each file's own module path rather than the require string", + "ignoreDir": [ ".github", "web", "dist", "factorio" ], + "ignoreGlobs": [ "**/node_modules/**", "**/dist/**" ], + "moduleMap": [ + { "pattern": "^([^.]+)\\.module\\.module_exports$", "replace": "modules.$1" }, + { "pattern": "^([^.]+)\\.module\\.(.+)$", "replace": "modules.$1.$2" } + ] + }, + "doc": { + "privateName": [ "__*" ] + }, + "$comment-disable": "Carried over from the luals config, which had these as editor only rather than checked in ci", + "$comment-unnecessary-if": "Disabled by the config fmtk generates for itself, the api union types make it unreliable", + "diagnostics": { + "globals": [ "__DebugAdapter", "__Profiler" ], + "disable": [ + "assign-type-mismatch", + "await-in-sync", + "invert-if", + "missing-parameter", + "missing-return-value", + "param-type-mismatch", + "preferred-local-alias", + "redefined-local", + "redundant-parameter", + "redundant-return-value", + "return-type-mismatch", + "unbalanced-assignments", + "undefined-doc-param", + "unnecessary-assert", + "unnecessary-if", + "unused" + ] + } +} diff --git a/.github/workflows/fmtk.yml b/.github/workflows/fmtk.yml index 4050b457..75d877d3 100644 --- a/.github/workflows/fmtk.yml +++ b/.github/workflows/fmtk.yml @@ -1,76 +1,93 @@ -name: FMTK Lint - -on: - push: - pull_request: - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 10 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install FMTK - run: | - # Pinned: 2.1.4+ generates typedefs targeting EmmyLua that LuaLS misreads - pnpm install factoriomod-debug@2.1.3 - pnpm exec fmtk luals-addon - jq '.["workspace.library"] += ["${{ github.workspace }}/factorio/library"] | .["runtime.plugin"] = "${{ github.workspace }}/factorio/plugin.lua"' .luarc.json > temp.luarc.json - jq -s '.[0] * .[1].settings' temp.luarc.json ${{ github.workspace }}/factorio/config.json > check.luarc.json - - name: Install LuaLS - run: | - wget https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-linux-x64.tar.gz -q -O lusls.tar.gz - mkdir luals && tar -xf lusls.tar.gz -C luals && rm lusls.tar.gz - - name: Run Lint Report - shell: bash - run: | - ./luals/bin/lua-language-server --check=. --logpath=. --configpath=check.luarc.json --checklevel=Information --check_out_path=check.json - - # Credit to https://github.com/Krealle/luals-check-action/blob/main/action.yml - # Although some minor fixes were needed - - # Format and print the messages (would be nice to format as a table, but that's too complex for jq) - cat check.json | \ - jq -r \ - 'to_entries | map(.key as $file | .value[] | - { - file: $file | sub("file://${{ github.workspace }}/./"; ""), - code: .code, - line: .range.start.line, - message: (.message | split("\n") - | map(if startswith("- ") then .[2:] else . end) - | join("\n")), - severity: (if .severity <= 1 then "ERR" else "WARN" end) - }) | - map("**[\(.severity)] \(.code):** \(.file)#L\(.line)
\(.message)") | .[]' \ - > $GITHUB_STEP_SUMMARY - - echo "" - - # Github Annotations (limited to 10) - cat check.json | \ - jq -r \ - 'to_entries | map(.key as $file | .value[] | - { - file: $file | sub("file://${{ github.workspace }}/./"; ""), - title: .code, - line: .range.start.line, - endLine: .range.end.line, - col: .range.start.character, - endColumn: .range.end.character, - message: .message | gsub("\n"; "%0A"; "m"), - level: (if .severity <= 1 then "error" else "warning" end) - }) | - map("::\(.level) file=\(.file),line=\(.line+1),endLine=\(.line+1),col=\(.col+1),endColumn=\(.endColumn+1),title=\(.title)::\(.message) (\(.title))") | .[]' - - if [[ $(wc -l < check.json) > 1 ]] ; then - exit 1 - else - echo "✅ All checks succeeded!" | tee $GITHUB_STEP_SUMMARY - fi - \ No newline at end of file +name: Lua Lint + +on: + push: + pull_request: + +env: + EMMYLUA_CHECK_VERSION: "0.24.0" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout clusterio modules + uses: actions/checkout@v4 + with: + repository: clusterio/clusterio + path: .clusterio + sparse-checkout: packages/host/modules + sparse-checkout-cone-mode: false + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Generate Factorio typedefs + run: | + pnpm install factoriomod-debug + pnpm exec fmtk luals-addon "$RUNNER_TEMP/factorio" + - name: Install emmylua_check + run: | + curl -fsSL "https://github.com/EmmyLuaLs/emmylua-analyzer-rust/releases/download/${EMMYLUA_CHECK_VERSION}/emmylua_check-linux-x64.tar.gz" \ + | tar -xz -C "$RUNNER_TEMP" + - name: Run Lint Report + shell: bash + run: | + # Library paths are machine specific, so they are merged in rather than committed + jq -n --arg lib "$RUNNER_TEMP/factorio/library" --arg clusterio "$PWD/.clusterio/packages/host" \ + '{ workspace: { library: [ $lib, $clusterio ], ignoreGlobs: [ ".clusterio/**" ] } }' \ + > "$RUNNER_TEMP/ci.emmyrc.json" + + # Exits non zero on error severity, so the gate below is used instead + "$RUNNER_TEMP/emmylua_check" . --config ".emmyrc.json,$RUNNER_TEMP/ci.emmyrc.json" \ + --output-format json --output check.json || true + + # Credit to https://github.com/Krealle/luals-check-action/blob/main/action.yml + # Although some minor fixes were needed + + # Format and print the messages (would be nice to format as a table, but that's too complex for jq) + jq -r --arg root "$PWD/" \ + '.[] | (.file | ltrimstr($root)) as $file | .diagnostics[] | + { + file: $file, + code: .code, + line: .range.start.line, + message: (.message | split("\n") + | map(if startswith("- ") then .[2:] else . end) + | join("\n")), + severity: (if .severity <= 1 then "ERR" else "WARN" end) + } | + "**[\(.severity)] \(.code):** \(.file)#L\(.line + 1)
\(.message)"' \ + check.json > "$GITHUB_STEP_SUMMARY" + + echo "" + + # Github Annotations (limited to 10) + jq -r --arg root "$PWD/" \ + '.[] | (.file | ltrimstr($root)) as $file | .diagnostics[] | + { + file: $file, + title: .code, + line: .range.start.line, + endLine: .range.end.line, + col: .range.start.character, + endColumn: .range.end.character, + message: (.message | gsub("\n"; "%0A"; "m")), + level: (if .severity <= 1 then "error" else "warning" end) + } | + "::\(.level) file=\(.file),line=\(.line + 1),endLine=\(.endLine + 1),col=\(.col + 1),endColumn=\(.endColumn + 1),title=\(.title)::\(.message) (\(.title))"' \ + check.json + + # An entry is emitted for every file checked, including clean ones, so + # the gate counts diagnostics and asserts the workspace was actually read + if [[ $(jq 'length' check.json) -lt 100 ]] ; then + echo "::error::Only $(jq 'length' check.json) files were checked, expected the whole workspace" + exit 1 + elif [[ $(jq '[.[].diagnostics[]] | length' check.json) -gt 0 ]] ; then + exit 1 + else + echo "✅ All checks succeeded!" | tee "$GITHUB_STEP_SUMMARY" + fi diff --git a/.gitignore b/.gitignore index a331c19a..bf2406f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist/ node_modules/ .vscode +.luarc.json diff --git a/.luarc.json b/.luarc.json deleted file mode 100644 index ba4b39f0..00000000 --- a/.luarc.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", - "workspace.ignoreDir": [ - ".vscode", ".github", "luals", "factorio", "web" - ], - "completion.requireSeparator": "/", - "runtime.pluginArgs": [ - "--clusterio-modules" - ], - "doc": { - "regengine": "glob", - "privateName": [ "__*" ], - "packageName": [ "_*" ] - }, - "type": { - "checkTableShape": true, - "inferParamType": true - }, - "$comment-name-style-check": "Disabled below until config issue fixed: https://github.com/LuaLS/lua-language-server/issues/2643", - "$comment-type-mismatch": "Disabled unless opened due to performance concerns, time reduced to 10s from 400s", - "diagnostics": { - "unusedLocalExclude": [ "_*", "i", "j", "k", "v" ], - "groupFileStatus": { - "ambiguity": "Any", - "await": "None", - "codestyle": "Opened", - "conventions": "Any", - "duplicate": "Any", - "global": "Any", - "luadoc": "Opened", - "redefined": "Opened", - "strict": "Any", - "strong": "Any", - "type-check": "Any", - "unbalanced": "Opened", - "unused": "Opened" - }, - "neededFileStatus": { - "no-unknown": "None!", - "spell-check": "None!", - "name-style-check": "None!", - "assign-type-mismatch": "Opened!", - "return-type-mismatch": "Opened!", - "param-type-mismatch": "Opened!" - } - }, - "nameStyle.config": { - "local_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }], - "module_local_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }, { - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "upper_snake_case" - }, { - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "pascal_case" - }], - "function_param_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }], - "function_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }], - "local_function_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }], - "table_field_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }], - "global_variable_name_style": [{ - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "snake_case" - }, { - "type" : "pattern", - "param": "_?_?(\\S*)", - "$1": "upper_snake_case" - }], - "module_name_style": [ "pascal_case", "snake_case" ], - "require_module_name_style": [ "pascal_case", "snake_case" ], - "class_name_style": "pascal_case", - "const_variable_name_style": "upper_snake_case" - }, - "format.defaultConfig": { - "continuation_indent.in_expr": "0" - } -} \ No newline at end of file diff --git a/exp_scenario/.luacheckrc b/exp_scenario/.luacheckrc deleted file mode 100644 index 9b043265..00000000 --- a/exp_scenario/.luacheckrc +++ /dev/null @@ -1,1697 +0,0 @@ ---[[---------------------------------------------------------------------------- - LICENSE - .luacheckrc for Factorio Version 0.18.8, luacheck version 0.23.0 - This file is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this file, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this file dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - THE FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - - For more information, please refer to -------------------------------------------------------------------------------]] -local LINE_LENGTH = false - -local IGNORE = {'21./%w+_$', '21./^_%w+$', '213/[ijk]', '213/index', '213/key'} - --- These globals are not available to the factorio API -local NOT_GLOBALS = {'coroutine', 'io', 'socket', 'dofile', 'loadfile'} - -local STD_CONTROL = 'lua52+factorio+factorio_control+stdlib+factorio_defines' -local STD_DATA = 'lua52+factorio+factorio_data+stdlib+stdlib_data+factorio_defines' - --- For Base and Core Mods -local STD_BASE_DATA = 'lua52+factorio+factorio_data+factorio_defines+factorio_base_data' -local STD_BASE_CONTROL = 'lua52+factorio+factorio_control+factorio_defines+factorio_base_control' - -do -- Assume Factorio Control Stage as Default - std = STD_CONTROL - --cache = true - not_globals = NOT_GLOBALS - ignore = IGNORE - quiet = 1 -- pass -q option - max_cyclomatic_complexity = false - codes = true - max_line_length = LINE_LENGTH - max_code_line_length = LINE_LENGTH - max_string_line_length = LINE_LENGTH - max_comment_line_length = LINE_LENGTH - - --List of files and directories to exclude - exclude_files = { - --Ignore special folders - '**/.trash/', - '**/.history/', - '**/stdlib/vendor/', - - --Ignore development mods - '**/combat-tester/', - '**/test-maker/', - '**/trailer/', - - --Ignore love Includes - '**/love/includes/' - } -end - -do -- Set default prototype files - files['**/data.lua'].std = STD_DATA - files['**/data-updates.lua'].std = STD_DATA - files['**/data-final-fixes.lua'].std = STD_DATA - files['**/settings.lua'].std = STD_DATA - files['**/settings-updates.lua'].std = STD_DATA - files['**/settings-final-fixes.lua'].std = STD_DATA - files['**/prototypes/'].std = STD_DATA - files['**/settings/'].std = STD_DATA -end - -do -- Base and Core mod files - local base_scenarios = { - std = STD_BASE_CONTROL .. '+factorio_base_scenarios+factorio_base_story', - --ignore = {'212/event', '111', '112', '113', '211', '212', '213', '311', '411', '412', '421', '422', '423', '431', '432', '512'} - ignore = {'...'} - } - files['**/base/scenarios/'] = base_scenarios - files['**/base/tutorials/'] = base_scenarios - files['**/base/campaigns/'] = base_scenarios - files['**/wip-scenario/'] = base_scenarios - - files['**/base/migrations/'] = {std = STD_BASE_CONTROL} - - files['**/core/lualib/'] = {std = STD_BASE_CONTROL} - files['**/core/lualib/util.lua'] = {globals = {'util', 'table'}, ignore = {'432/object'}} - files['**/core/lualib/silo-script.lua'] = {globals = {'silo_script'}, ignore = {'4../player'}} - files['**/core/lualib/production-score.lua'] = {globals = {'production_score', 'get_price_recursive'}, ignore = {'4../player'}} - files['**/core/lualib/story*'] = {std = '+factorio_base_story', ignore = {'42./k', '42./filter'}} - files['**/core/lualib/mod-gui.lua'] = {globals = {'mod_gui'}} - files['**/core/lualib/camera.lua'] = {globals = {'camera'}} - files['**/core/lualib/builder.lua'] = {globals = {'Builder', 'builder', 'action', 'down', 'right'}} - - files['**/core/lualib/bonus-gui-ordering/'] = {std = STD_BASE_DATA} - files['**/core/lualib/dataloader.lua'] = {globals = {'data'}} - files['**/core/lualib/circuit-connector-*'] = {std = STD_BASE_DATA..'+factorio_circuit_connector_generated'} - files['**/core/lualib/bonus-gui-ordering.lua'] = {globals = {'bonus_gui_ordering'}} - - files['**/base/prototypes/'] = {std = STD_BASE_DATA} - files['**/core/prototypes/'] = {std = STD_BASE_DATA} - files['**/core/prototypes/noise-programs.lua'] = {ignore = {'212/x', '212/y', '212/tile', '212/map'}} -end - -do -- Stdlib Files - local stdlib_control = { - std = 'lua52+factorio+factorio_control+stdlib+factorio_defines', - max_line_length = LINE_LENGTH - } - - local stdlib_data = { - std = 'lua52+factorio+factorio_data+stdlib+factorio_defines', - max_line_length = LINE_LENGTH - } - - -- Assume control stage for stdlib - files['**/stdlib/'] = stdlib_control - - -- Assume generic lua for stdlib utils - files['**/stdlib/utils/**'].std = 'lua52+stdlib' - - -- STDLIB data stage files - files['**/stdlib/data/'] = stdlib_data - - -- STDLIB Busted Spec - files['**/spec/**'] = { - globals = {'serpent', 'log', 'package.remove_stdlib'}, - std = 'lua52c+busted+factorio_defines+factorio_control+stdlib' - } - - -- Love - files['**/love/'].std = 'luajit+love+love_extra+stdlib+stdlib_data' -end - -do -- Factorio STDs-- - stds.factorio = { - --Set the read only variables - read_globals = { - -- @log@: Gives writing access to Factorio's logger instance. - "log", - -- @serpent@: Lua serializer and pretty printer. (https://github.com/pkulchenko/serpent). - "serpent", - -- @table_size@: Returns the number of elements inside an LUA table. - "table_size", - -- @lldebugger@: Provided by lua local debugger vscode extension. - lldebugger = { - fields = {'requestBreak'}, - other_fields = true, - read_only = false, - }, - -- @__DebugAdapter@: Provided by Factorio Mod Debug vscode extension. - __DebugAdapter = { - fields = {'print', 'stepIgnoreAll', 'stepIgnore', 'breakpoint'}, - other_fields = true, - read_only = false - }, - util = { - fields = { - "by_pixel", "distance", "findfirstentity", "positiontostr", "formattime", "moveposition", "oppositedirection", - "ismoduleavailable", "multiplystripes", "format_number", "increment", "color", "conditional_return", - "add_shift", "merge", "premul_color", "encode", "decode", "insert_safe", - table = { - fields = { - "compare", "deepcopy" - }, - }, - }, - }, - table = { - fields = { - "compare", "deepcopy" - }, - }, - }, - } - - stds.factorio_control = { - read_globals = { - - -- @commands@: - commands = { - fields = { - "add_command", "commands", "game_commands", "remove_command" - }, - }, - - -- @settings@: - settings = { - fields = { - "get_player_settings", - startup = {other_fields = true}, - global = {other_fields = true}, - player = {other_fields = true}, - get = {read_only = false}, -- stdlib added - get_startup = {read_only = false} -- stdlib added - }, - }, - - -- @script@: Provides an interface for registering event handlers. - -- (http://lua-api.factorio.com/latest/LuaBootstrap.html) - script = { - fields = { - "on_event", "on_nth_tick", "on_configuration_changed", "on_init", "on_load", "generate_event_name", - "raise_event", "get_event_handler", "mod_name", "get_event_order", - "is_game_in_debug_mode", "object_name", "set_event_filter", "get_event_filter", "register_metatable", - active_mods = {read_only = true, other_fields = true}, - }, - other_fields = false, - }, - - -- @remote@: Allows inter-mod communication by providing a repository of interfaces that is shared by all mods. - -- (http://lua-api.factorio.com/latest/LuaRemote.html) - remote = { - fields = { - interfaces = {read_only = false, other_fields = true}, - "add_interface", "remove_interface", "call" - }, - read_only = true, - other_fields = false, - }, - - rcon = { - fields = {'print'} - }, - - rendering = { - other_fields = false, - read_only = true, - fields = { - 'draw_animation', - 'draw_line', - 'draw_text', - 'draw_circle', - 'draw_rectangle', - 'draw_arc', - 'draw_polygon', - 'draw_sprite', - 'draw_light', - 'destroy', - 'is_font_valid', - 'is_valid', - 'get_all_ids', - 'clear', - 'get_type', - 'get_surface', - 'get_time_to_live', - 'set_time_to_live', - 'get_forces', - 'set_forces', - 'get_players', - 'set_players', - 'get_color', - 'set_color', - 'get_width', - 'set_width', - 'get_from', - 'set_from', - 'get_to', - 'set_to', - 'get_gap_amount', - 'set_gap_amount', - 'get_gap_length', - 'set_gap_length', - 'get_target', - 'set_target', - 'get_orientation', - 'set_orientation', - 'get_scale', - 'set_scale', - 'get_text', - 'set_text', - 'get_font', - 'set_font', - 'get_alignment', - 'set_alignment', - 'get_scale_with_zoom', - 'set_scale_with_zoom', - 'get_filled', - 'set_filled', - 'get_radius', - 'set_radius', - 'get_left_top', - 'set_left_top', - 'get_right_bottom', - 'set_right_bottom', - 'get_max_radius', - 'set_max_radius', - 'get_min_radius', - 'set_min_radius', - 'get_start_angle', - 'set_start_angle', - 'get_angle', - 'set_angle', - 'get_vertices', - 'set_vertices', - 'get_sprite', - 'set_sprite', - 'get_x_scale', - 'set_x_scale', - 'get_y_scale', - 'set_y_scale', - 'get_render_layer', - 'set_render_layer', - 'get_orientation_target', - 'set_orientation_target', - 'get_oriented_offset', - 'set_oriented_offset', - 'get_intensity', - 'set_intensity', - 'get_minimum_darkness', - 'set_minimum_darkness' - } - }, - - -- @game@: Main object through which most of the API is accessed. - -- It is, however, not available inside handlers registered with @script.on_load@. - -- (http://lua-api.factorio.com/latest/LuaGameScript.html) - game ={ - other_fields = false, - read_only = false, - fields = { - 'auto_save', - 'ban_player', - 'check_consistency', - 'check_prototype_translations', - 'count_pipe_groups', - 'create_force', - 'create_profiler', - 'create_random_generator', - 'create_surface', - 'decode_string', - 'delete_surface', - 'direction_to_string', - 'disable_replay', - 'disable_tips_and_tricks', - 'disable_tutorial_triggers', - 'encode_string', - 'evaluate_expression', - 'force_crc', - 'get_active_entities_count', - 'get_entity_by_tag', - 'get_filtered_achievement_prototypes', - 'get_filtered_decorative_prototypes', - 'get_filtered_entity_prototypes', - 'get_filtered_equipment_prototypes', - 'get_filtered_fluid_prototypes', - 'get_filtered_item_prototypes', - 'get_filtered_mod_setting_prototypes', - 'get_filtered_recipe_prototypes', - 'get_filtered_technology_prototypes', - 'get_filtered_tile_prototypes', - 'get_map_exchange_string', - 'get_player', - 'get_surface', - 'get_train_stops', - 'help', - 'is_demo', - 'is_multiplayer', - 'is_valid_sound_path', - 'is_valid_sprite_path', - 'json_to_table', - 'kick_player', - 'log_recipe_locale', - 'merge_forces', - 'mute_player', - 'parse_map_exchange_string', - 'play_sound', - 'print', - 'print_stack_size', - 'purge_player', - 'regenerate_entity', - 'reload_mods', - 'reload_script', - 'remove_offline_players', - 'remove_path', - 'reset_time_played', - 'save_atlas', - 'server_save', - 'set_game_state', - 'set_wait_for_screenshots_to_finish', - 'show_message_dialog', - 'table_to_json', - 'take_screenshot', - 'take_technology_screenshot', - 'unban_player', - 'unmute_player', - 'write_file', - - achievement_prototypes = {read_only = true, other_fields = true}, - active_mods = {read_only = true, other_fields = true}, - ammo_category_prototypes = {read_only = true, other_fields = true}, - autoplace_control_prototypes = {read_only = true, other_fields = true}, - backer_names = {read_only = true, other_fields = true}, - connected_players = {read_only = true, other_fields = true}, - custom_input_prototypes = {read_only = true, other_fields = true}, - damage_prototypes = {read_only = true, other_fields = true}, - decorative_prototypes = {read_only = true, other_fields = true}, - default_map_gen_settings = {read_only = true, other_fields = true}, - difficulty = {read_only = true, other_fields = true}, - difficulty_settings = {read_only = true, other_fields = true}, - entity_prototypes = {read_only = true, other_fields = true}, - equipment_category_prototypes = {read_only = true, other_fields = true}, - equipment_grid_prototypes = {read_only = true, other_fields = true}, - equipment_prototypes = {read_only = true, other_fields = true}, - finished = {read_only = true, other_fields = true}, - fluid_prototypes = {read_only = true, other_fields = true}, - forces = {read_only = true, other_fields = true}, - fuel_category_prototypes = {read_only = true, other_fields = true}, - item_group_prototypes = {read_only = true, other_fields = true}, - item_prototypes = {read_only = true, other_fields = true}, - item_subgroup_prototypes = {read_only = true, other_fields = true}, - map_settings = {read_only = true, other_fields = true}, - mod_setting_prototypes = {read_only = true, other_fields = true}, - module_category_prototypes = {read_only = true, other_fields = true}, - named_noise_expressions = {read_only = true, other_fields = true}, - noise_layer_prototypes = {read_only = true, other_fields = true}, - object_name = {read_only = true, other_fields = true}, - particle_prototypes = {read_only = true, other_fields = true}, - permissions = {read_only = true, other_fields = true}, - player = {read_only = true, other_fields = true}, - players = {read_only = true, other_fields = true}, - pollution_statistics = {read_only = true, other_fields = true}, - recipe_category_prototypes = {read_only = true, other_fields = true}, - recipe_prototypes = {read_only = true, other_fields = true}, - resource_category_prototypes = {read_only = true, other_fields = true}, - shortcut_prototypes = {read_only = true, other_fields = true}, - styles = {read_only = true, other_fields = true}, - surfaces = {read_only = true, other_fields = true}, - technology_prototypes = {read_only = true, other_fields = true}, - tick = {read_only = true, other_fields = true}, - ticks_played = {read_only = true, other_fields = true}, - tile_prototypes = {read_only = true, other_fields = true}, - trivial_smoke_prototypes = {read_only = true, other_fields = true}, - virtual_signal_prototypes = {read_only = true, other_fields = true}, - - autosave_enabled = {read_only = false, other_fields = false}, - draw_resource_selection = {read_only = false, other_fields = false}, - enemy_has_vision_on_land_mines = {read_only = false, other_fields = false}, - speed = {read_only = false, other_fields = false}, - tick_paused = {read_only = false, other_fields = false}, - ticks_to_run = {read_only = false, other_fields = false} - }, - }, - }, - - globals = { - -- @global@: The global dictionary, useful for storing data persistent across a save-load cycle. - -- Writing access is given to the mod-id field (for mod-wise saved data). - -- (http://lua-api.factorio.com/latest/Global.html) - "global", - - -- @MOD@: Keep it organized, use this variable for anything that "NEEDS" to be global for some reason. - "MOD" - }, - } - - stds.factorio_data = { - - read_globals = { - data = { - fields = { - raw = { - other_fields = true, - read_only = false - }, - "extend", "is_demo" - }, - }, - - settings = { - fields = { - "startup", "global", "player", "get", "get_startup" - }, - }, - - mods = { - other_fields = true - }, - - -- Popular mods that think they need globals! - angelsmods = { - other_fields = true - }, - - -- Popular mods that think they need globals! - bobmods = { - other_fields = true - }, - - - } - } -end - -do -- Factorio Base/Core STDs-- - stds.factorio_base_control = { - read_globals = {"silo_script", "mod_gui", "camera"} - } - - stds.factorio_base_scenarios = { - globals = { - "check_automate_science_packs_advice", "check_research_hints", "check_supplies", "manage_attacks", "all_dead", - "on_win", "difficulty_number", "init_attack_data", "handle_attacks", "count_items_in_container", "progress", "scanned", - "check_light", "check_machine_gun", "level", "story_table", - - "tightspot_prices", "tightspot_make_offer", "tightspot_init", "tightspot_get_required_balance", - "tightspot_init_level", "tightspot_init_spending_frame", "tightspot_init_progress_frame", "tightspot_update_progress", "tightspot_update_spending", - "tightspot_get_missing_to_win", "tightspot_sell_back", "tightspot_start_level", "tightspot_show_level_description", "tightspot_update_speed_label", - "map_ignore", "tightspot_check_level", "land_price", - - "transport_belt_madness_init", "transport_belt_madness_init_level", "transport_belt_madness_create_chests", "transport_belt_madness_fill_chests", - "transport_belt_madness_start_level", "map_ignore", "map_clear", "map_load", "map_save", "transport_belt_madness_show_level_description", - "transport_belt_madness_check_level", "transport_belt_madness_next_level", "transport_belt_madness_clear_level", "transport_belt_madness_contains_next_level", - - "restricted", "check_built_items", "result", "disable_combat_technologies", "apply_character_modifiers", "apply_combat_modifiers", "apply_balance", - "load_config", "starting_area_constant", "create_next_surface", "end_round", "prepare_next_round", "silo_died","choose_joining_gui", - "destroy_joining_guis", "create_random_join_gui", "create_auto_assign_gui", "create_pick_join_gui", "create_config_gui", "make_config_table", "default", - "make_team_gui", "make_team_gui_config", "add_team_button_press", "trash_team_button_press", "remove_team_from_team_table", "add_team_to_team_table", - "set_teams_from_gui", "on_team_button_press", "make_color_dropdown", "create_balance_option", "create_disable_frame", "disable_frame", "parse_disabled_items", - "set_balance_settings", "config_confirm", "parse_config_from_gui", "get_color", "roll_starting_area", "delete_roll_surfaces", "auto_assign", - "destroy_config_for_all", "prepare_map", "set_evolution_factor", "update_players_on_team_count", "random_join", "init_player_gui", - "destroy_player_gui", "objective_button_press", "admin_button_press", "admin_frame_button_press", "diplomacy_button_press", "update_diplomacy_frame", - "diplomacy_frame_button_press", "team_changed_diplomacy", "diplomacy_check_press", "get_stance", "give_inventory", "setup_teams", "disable_items_for_all", - "set_random_team", "set_diplomacy", "create_spawn_positions", "set_spawn_position", "set_team_together_spawns", "chart_starting_area_for_force_spawns", - "check_starting_area_chunks_are_generated", "check_player_color", "check_round_start", "clear_starting_area_enemies", "check_no_rush_end", "check_no_rush_players", - "finish_setup", "chart_area_for_force", "setup_start_area_copy", "update_copy_progress", "update_progress_bar", "copy_paste_starting_area_tiles", - "copy_paste_starting_area_entities", "create_silo_for_force", "setup_research", "on_chunk_generated", "get_distance_to_nearest_spawn", - "create_wall_for_force", "fpn", "give_items", "create_item_frame", "create_technologies_frame", "create_cheat_frame", "create_day_frame", - "time_modifier", "points_per_second_start", "points_per_second_level_subtract", "levels", "update_info", "get_time_left", "update_time_left", - "on_joined", "make_frame", "update_frame", "update_table", "calculate_task_item_multiplayer", "setup_config", "select_from_probability_table", - "select_inventory", "select_equipment", "select_challange_type", "save_round_statistics", "start_challenge", "create_teams", "set_areas", - "decide_player_team", "set_teams", "refresh_leaderboard", "set_player", "generate_technology_list", "generate_research_task","setup_unlocks", - "check_technology_progress", "generate_production_task", "generate_shopping_list_task", "set_gui_flow_table", "create_visibility_button", - "check_item_lists", "update_task_gui", "check_end_of_round", "end_round_gui_update", "try_to_check_victory", "update_gui", "check_start_round", - "check_start_set_areas", "check_start_setting_entities", "check_set_areas", "check_clear_areas", "check_chests", "check_chests_shopping_list", - "check_chests_production", "check_input_chests", "fill_input_chests", "check_victory", "shopping_task_finished", "calculate_force_points", - "update_research_task_table", "update_production_task_table", "update_shopping_list_task_table", "create_joined_game_gui", "pre_ending_round", - "player_ending_prompt", "update_end_timer", "update_begin_timer", "team_finished", "save_points_list", "give_force_players_points", - "update_winners_list", "set_spectator", "set_character", "give_starting_inventory", "give_equipment", "shuffle_table", "format_time", - "spairs", "fill_leaderboard", "create_grid", "simple_entities", "save_map_data", "clear_map", "create_tiles", "recreate_entities", - "map_sets", "give_points", "init_forces", "init_globals", "init_unit_settings", "check_next_wave", "next_wave", "calculate_wave_power", - "wave_end", "make_next_spawn_tick", "check_spawn_units", "get_wave_units", "spawn_units", "randomize_ore", "set_command", "command_straglers", - "unit_config", "make_next_wave_tick", "time_to_next_wave", "time_to_wave_end", "rocket_died", "unit_died", "get_bounty_price", "setup_waypoints", - "insert_items", "give_starting_equipment", "give_spawn_equipment", "next_round_button_visible", "gui_init", "create_wave_frame", "create_money_frame", - "create_upgrade_gui", "update_upgrade_listing", "upgrade_research", "get_upgrades", "get_money", "update_connected_players", "update_round_number", - "set_research", "set_recipes", "check_deconstruction", "check_blueprint_placement", "loop_entities", "experiment_items", - "setup", "story_gui_click", "clear_surface", "add_run_trains_button", "puzzle_condition", "basic_signals", - "loop_trains", "Y_offset", "ghosts_1", "ghosts_2", "required_path", "through_wall_path", "count", "check_built_real_rail", - "current_ghosts_count", "other", "rails", "set_rails", "straight_section", "late_entities", "entities", "stop", - "get_spawn_coordinate", - - --tutorials - "intermission", "create_entities_on_tick", "on_player_created", "required_count", "non_player_entities", "clear_rails", - "chest", "damage", "furnace", "init_prototypes", "build_infi_table", "junk", "update_player_tags", "time_left", "team_production", - "create_task_frame", "create_visibilty_buttons", "update_leaderboard", "in_in_area" - } - } - - stds.factorio_base_data = { - globals = { - --Style - "make_cursor_box", "make_full_cursor_box", - "default_container_padding", "default_orange_color", "default_light_orange_color", "warning_red_color", - "achievement_green_color", "achievement_tan_color", "orangebuttongraphcialset", "bluebuttongraphcialset", - "bonus_gui_ordering", "trivial_smoke", "technology_slot_base_width", "technology_slot_base_height", "default_frame_font_vertical_compensation", - - --Belts - "transport_belt_connector_frame_sprites", "transport_belt_circuit_wire_connection_point", "transport_belt_circuit_wire_max_distance", - "transport_belt_circuit_connector_sprites", "ending_patch_prototype", "basic_belt_horizontal", "basic_belt_vertical", - "basic_belt_ending_top", "basic_belt_ending_bottom", "basic_belt_ending_side", "basic_belt_starting_top", "basic_belt_starting_bottom", - "basic_belt_starting_side", "fast_belt_horizontal", "fast_belt_vertical", "fast_belt_ending_top", "fast_belt_ending_bottom", - "fast_belt_ending_side", "fast_belt_starting_top", "fast_belt_starting_bottom", "fast_belt_starting_side", "express_belt_horizontal", - "express_belt_vertical", "express_belt_ending_top", "express_belt_ending_bottom", "express_belt_ending_side", "express_belt_starting_top", - "express_belt_starting_bottom", "express_belt_starting_side", - - --Circuit Connectors - "circuit_connector_definitions", "default_circuit_wire_max_distance", "inserter_circuit_wire_max_distance", - "universal_connector_template", "belt_connector_template", "belt_frame_connector_template", "inserter_connector_template", - - --Inserter Circuit Connectors - "inserter_circuit_wire_max_distance", "inserter_default_stack_control_input_signal", - - --Sounds/beams - "make_heavy_gunshot_sounds", "make_light_gunshot_sounds", "make_laser_sounds", - - --Gun/Laser - "gun_turret_extension", "gun_turret_extension_shadow", "gun_turret_extension_mask", "gun_turret_attack", - "laser_turret_extension", "laser_turret_extension_shadow", "laser_turret_extension_mask", - - --Pipes - "pipecoverspictures", "pipepictures", "assembler2pipepictures", "assembler3pipepictures", "make_heat_pipe_pictures", - - --Combinators - "generate_arithmetic_combinator", "generate_decider_combinator", "generate_constant_combinator", - - --Rail - "destroyed_rail_pictures", "rail_pictures", "rail_pictures_internal", "standard_train_wheels", "drive_over_tie", - "rolling_stock_back_light", "rolling_stock_stand_by_light", - - --Enemies - "make_enemy_autoplace", "make_enemy_spawner_autoplace", "make_enemy_worm_autoplace", - "make_spitter_attack_animation", "make_spitter_run_animation", "make_spitter_dying_animation", - "make_spitter_attack_parameters", "make_spitter_roars", "make_spitter_dying_sounds", - "make_spawner_idle_animation", "make_spawner_die_animation", - "make_biter_run_animation", "make_biter_attack_animation", "make_biter_die_animation", - "make_biter_roars", "make_biter_dying_sounds", "make_biter_calls", - "make_worm_roars", "make_worm_dying_sounds", "make_worm_folded_animation", "make_worm_preparing_animation", - "make_worm_prepared_animation", "make_worm_attack_animation", "make_worm_die_animation", - - --Other - "tile_variations_template", "make_water_autoplace_settings", - "make_unit_melee_ammo_type", "make_trivial_smoke", "make_4way_animation_from_spritesheet", "flying_robot_sounds", - "productivitymodulelimitation", "crash_trigger", "capsule_smoke", "make_beam", "playeranimations", - "make_blood_tint", "make_shadow_tint", - - --tiles - "water_transition_template", "make_water_transition_template", "water_autoplace_settings", "water_tile_type_names", - "patch_for_inner_corner_of_transition_between_transition", - } - } - - stds.factorio_base_story = { - globals = { - "story_init_helpers", "story_update_table", "story_init", "story_update", "story_on_tick", "story_add_update", - "story_remove_update", "story_jump_to", "story_elapsed", "story_elapsed_check", "story_show_message_dialog", - "set_goal", "player_set_goal", "on_player_joined", "flash_goal", "set_info", "player_set_info", "export_entities", - "list", "recreate_entities", "entity_to_connect", "limit_camera", "find_gui_recursive", "enable_entity_export", - "add_button", "on_gui_click", "set_continue_button_style", "add_message_log", "story_add_message_log", - "player_add_message_log", "message_log_frame", "message_log_scrollpane", "message_log_close_button", - "message_log_table", "toggle_message_log_button", "toggle_objective_button", "message_log_init", - "add_gui_recursive", "add_toggle_message_log_button", "add_toggle_objective_button", "mod_gui", - "flash_message_log_button", "flash_message_log_on_tick", "story_gui_click", "story_points_by_name", "story_branches", - "player", "surface", "deconstruct_on_tick", "recreate_entities_on_tick", "flying_congrats", "story_table" - } - } - - stds.factorio_circuit_connector_generated = { - globals = { - 'default_circuit_wire_max_distance', 'circuit_connector_definitions', 'universal_connector_template', - 'belt_connector_template', 'belt_frame_connector_template', 'inserter_connector_template', 'inserter_connector_template', - 'inserter_circuit_wire_max_distance', 'inserter_default_stack_control_input_signal', 'transport_belt_connector_frame_sprites', - 'transport_belt_circuit_wire_max_distance', - } - } -end - -do -- STDLIB STDs-- - stds.stdlib = { - read_globals = {}, - globals = { - 'STDLIB', - 'prequire', - 'rawtostring', - 'traceback', - 'data_traceback', - 'inspect', - 'serpent', - 'inline_if', - 'install', - 'log', - 'concat', - 'GAME', - 'AREA', - 'POSITION', - 'TILE', - 'SURFACE', - 'CHUNK', - 'COLOR', - 'ENTITY', - 'INVENTORY', - 'RESOURCE', - 'CONFIG', - 'LOGGER', - 'QUEUE', - 'EVENT', - 'GUI', - 'PLAYER', - 'FORCE', - 'MATH', - 'STRING', - 'TABLE' - } - } - - stds.stdlib_control = {} - - stds.stdlib_data = { - globals = { - 'DATA', - 'RECIPE', - 'ITEM', - 'FLUID', - 'ENTITY', - 'TECHNOLOGY', - 'CATEGORY' - } - } -end - -do -- Love STDs - stds.love_extra = { - read_globals = { - love = { - fields = { - arg = { - fields = { - 'parseGameArguments', - 'parseOption', - 'getLow', - 'optionIndices', - 'options' - } - } - } - }, - 'coroutine', - 'io', - 'socket', - 'dofile', - 'loadfile' - }, - globals = { - love = { - fields = { - 'handlers' - } - } - } - } -end - -do -- Factorio Defines STDs-- - stds.factorio_defines = { - read_globals = { - defines = { - fields = { - alert_type = { - fields = { - 'entity_destroyed', - 'entity_under_attack', - 'not_enough_construction_robots', - 'no_material_for_construction', - 'not_enough_repair_packs', - 'turret_fire', - 'custom', - 'no_storage', - 'train_out_of_fuel', - 'fluid_mixing' - } - }, - behavior_result = { - fields = { - 'in_progress', - 'fail', - 'success', - 'deleted' - } - }, - build_check_type = { - fields = { - 'script', - 'manual', - 'ghost_place', - 'ghost_revive' - } - }, - chain_signal_state = { - fields = { - 'all_open', - 'partially_open', - 'none_open' - } - }, - chunk_generated_status = { - fields = { - 'nothing', - 'custom_tiles', - 'basic_tiles', - 'corrected_tiles', - 'tiles', - 'entities' - } - }, - circuit_condition_index = { - fields = { - 'inserter_circuit', - 'inserter_logistic', - 'lamp', - 'arithmetic_combinator', - 'decider_combinator', - 'constant_combinator', - 'offshore_pump', - 'pump' - } - }, - circuit_connector_id = { - fields = { - 'accumulator', - 'constant_combinator', - 'container', - 'programmable_speaker', - 'rail_signal', - 'rail_chain_signal', - 'roboport', - 'storage_tank', - 'wall', - 'electric_pole', - 'inserter', - 'lamp', - 'combinator_input', - 'combinator_output', - 'offshore_pump', - 'pump' - } - }, - command = { - fields = { - 'attack', - 'go_to_location', - 'compound', - 'group', - 'attack_area', - 'wander', - 'flee', - 'stop', - 'build_base' - } - }, - compound_command = { - fields = { - 'logical_and', - 'logical_or', - 'return_last' - } - }, - control_behavior = { - fields = { - inserter = { - fields = { - circuit_mode_of_operation = { - fields = { - 'enable_disable', - 'set_filters', - 'read_hand_contents', - 'set_stack_size' - } - }, - hand_read_mode = { - fields = { - 'hold', - 'pulse' - } - } - } - }, - lamp = { - fields = { - circuit_mode_of_operation = { - fields = { - 'use_colors' - } - } - } - }, - logistic_container = { - fields = { - circuit_mode_of_operation = { - fields = { - 'send_contents', - 'set_requests' - } - } - } - }, - mining_drill = { - fields = { - resource_read_mode = { - fields = { - 'this_miner', - 'entire_patch' - } - } - } - }, - roboport = { - fields = { - circuit_mode_of_operation = { - fields = { - 'read_logistics', - 'read_robot_stats' - } - } - } - }, - train_stop = { - fields = { - circuit_mode_of_operation = { - fields = { - 'enable_disable', - 'send_to_train', - 'read_from_train', - 'read_stopped_train' - } - } - } - }, - transport_belt = { - fields = { - content_read_mode = { - fields = { - 'pulse', - 'hold' - } - } - } - }, - type = { - fields = { - 'container', - 'generic_on_off', - 'inserter', - 'lamp', - 'logistic_container', - 'roboport', - 'storage_tank', - 'train_stop', - 'decider_combinator', - 'arithmetic_combinator', - 'constant_combinator', - 'transport_belt', - 'accumulator', - 'rail_signal', - 'rail_chain_signal', - 'wall', - 'mining_drill', - 'programmable_speaker' - } - } - } - }, - controllers = { - fields = { - 'ghost', - 'character', - 'god', - 'editor', - 'cutscene', - 'spectator' - } - }, - deconstruction_item = { - fields = { - entity_filter_mode = { - fields = { - 'whitelist', - 'blacklist' - } - }, - tile_filter_mode = { - fields = { - 'whitelist', - 'blacklist' - } - }, - tile_selection_mode = { - fields = { - 'normal', - 'always', - 'never', - 'only' - } - } - } - }, - difficulty = { - fields = { - 'easy', - 'normal', - 'hard' - } - }, - difficulty_settings = { - fields = { - recipe_difficulty = { - fields = { - 'normal', - 'expensive' - } - }, - technology_difficulty = { - fields = { - 'normal', - 'expensive' - } - } - } - }, - direction = { - fields = { - 'north', - 'northeast', - 'east', - 'southeast', - 'south', - 'southwest', - 'west', - 'northwest' - } - }, - distraction = { - fields = { - 'by_enemy', - 'by_anything', - 'by_damage' - } - }, - entity_status = { - fields = { - 'working', - 'no_power', - 'no_fuel', - 'no_recipe', - 'no_input_fluid', - 'no_research_in_progress', - 'no_minable_resources', - 'low_input_fluid', - 'low_power', - 'disabled_by_control_behavior', - 'disabled_by_script', - 'fluid_ingredient_shortage', - 'fluid_production_overload', - 'item_ingredient_shortage', - 'item_production_overload', - 'marked_for_deconstruction', - 'missing_required_fluid', - 'missing_science_packs', - 'waiting_for_source_items', - 'waiting_for_space_in_destination', - 'waiting_to_launch_rocket' - } - }, - events = { - fields = { - 'on_tick', - 'on_gui_click', - 'on_gui_confirmed', - 'on_gui_text_changed', - 'on_gui_checked_state_changed', - 'on_entity_died', - 'on_post_entity_died', - 'on_entity_damaged', - 'on_picked_up_item', - 'on_built_entity', - 'on_sector_scanned', - 'on_player_mined_item', - 'on_put_item', - 'on_rocket_launched', - 'on_pre_player_mined_item', - 'on_chunk_generated', - 'on_player_crafted_item', - 'on_robot_built_entity', - 'on_robot_pre_mined', - 'on_robot_mined', - 'on_research_started', - 'on_research_finished', - 'on_player_rotated_entity', - 'on_player_set_quickbar_slot', - 'on_marked_for_deconstruction', - 'on_cancelled_deconstruction', - 'on_trigger_created_entity', - 'on_trigger_fired_artillery', - 'on_train_changed_state', - 'on_player_created', - 'on_resource_depleted', - 'on_player_driving_changed_state', - 'on_force_created', - 'on_forces_merging', - 'on_player_cursor_stack_changed', - 'on_pre_entity_settings_pasted', - 'on_entity_settings_pasted', - 'on_player_main_inventory_changed', - 'on_player_armor_inventory_changed', - 'on_player_ammo_inventory_changed', - 'on_player_gun_inventory_changed', - 'on_player_placed_equipment', - 'on_player_removed_equipment', - 'on_pre_player_died', - 'on_player_died', - 'on_player_respawned', - 'on_player_joined_game', - 'on_player_left_game', - 'on_player_built_tile', - 'on_player_mined_tile', - 'on_robot_built_tile', - 'on_robot_mined_tile', - 'on_player_selected_area', - 'on_player_alt_selected_area', - 'on_player_changed_surface', - 'on_selected_entity_changed', - 'on_market_item_purchased', - 'on_player_dropped_item', - 'on_biter_base_built', - 'on_player_changed_force', - 'on_entity_renamed', - 'on_gui_selection_state_changed', - 'on_runtime_mod_setting_changed', - 'on_difficulty_settings_changed', - 'on_surface_created', - 'on_surface_deleted', - 'on_pre_surface_deleted', - 'on_player_mined_entity', - 'on_robot_mined_entity', - 'on_train_created', - 'on_gui_elem_changed', - 'on_player_setup_blueprint', - 'on_player_deconstructed_area', - 'on_player_configured_blueprint', - 'on_console_chat', - 'on_console_command', - 'on_player_removed', - 'on_pre_player_removed', - 'on_player_used_capsule', - 'script_raised_built', - 'script_raised_destroy', - 'script_raised_revive', - 'on_player_promoted', - 'on_player_demoted', - 'on_combat_robot_expired', - 'on_player_changed_position', - 'on_mod_item_opened', - 'on_gui_opened', - 'on_gui_closed', - 'on_gui_value_changed', - 'on_player_muted', - 'on_player_unmuted', - 'on_player_cheat_mode_enabled', - 'on_player_cheat_mode_disabled', - 'on_character_corpse_expired', - 'on_pre_ghost_deconstructed', - 'on_player_pipette', - 'on_player_display_resolution_changed', - 'on_player_display_scale_changed', - 'on_pre_player_crafted_item', - 'on_player_cancelled_crafting', - 'on_chunk_charted', - 'on_technology_effects_reset', - 'on_land_mine_armed', - 'on_forces_merged', - 'on_player_trash_inventory_changed', - 'on_pre_player_left_game', - 'on_pre_surface_cleared', - 'on_surface_cleared', - 'on_chunk_deleted', - 'on_pre_chunk_deleted', - 'on_train_schedule_changed', - 'on_player_banned', - 'on_player_kicked', - 'on_player_unbanned', - 'on_rocket_launch_ordered', - 'on_script_path_request_finished', - 'on_ai_command_completed', - 'on_marked_for_upgrade', - 'on_cancelled_upgrade', - 'on_player_toggled_map_editor', - 'on_entity_cloned', - 'on_area_cloned', - 'on_brush_cloned', - 'on_game_created_from_scenario', - 'on_surface_imported', - 'on_surface_renamed', - 'on_player_toggled_alt_mode', - 'on_player_repaired_entity', - 'on_player_fast_transferred', - 'on_pre_robot_exploded_cliff', - 'on_robot_exploded_cliff', - 'on_entity_spawned', - 'on_cutscene_waypoint_reached', - 'on_unit_group_created', - 'on_unit_added_to_group', - 'on_unit_removed_from_group', - 'on_unit_group_finished_gathering', - 'on_build_base_arrived', - 'on_chart_tag_added', - 'on_chart_tag_modified', - 'on_chart_tag_removed', - 'on_lua_shortcut', - 'on_gui_location_changed', - 'on_gui_selected_tab_changed', - 'on_gui_switch_state_changed', - 'on_force_cease_fire_changed', - 'on_force_friends_changed', - 'on_string_translated', - 'on_script_trigger_effect' - } - }, - flow_precision_index = { - fields = { - 'one_second', - 'one_minute', - 'ten_minutes', - 'one_hour', - 'ten_hours', - 'fifty_hours', - 'two_hundred_fifty_hours', - 'one_thousand_hours' - } - }, - group_state = { - fields = { - 'gathering', - 'moving', - 'attacking_distraction', - 'attacking_target', - 'finished', - 'pathfinding', - 'wander_in_group' - } - }, - gui_type = { - fields = { - 'entity', - 'research', - 'controller', - 'production', - 'item', - 'bonus', - 'trains', - 'achievement', - 'blueprint_library', - 'equipment', - 'logistic', - 'other_player', - 'kills', - 'permissions', - 'tutorials', - 'custom', - 'server_management', - 'player_management', - 'tile' - } - }, - input_action = { - fields = { - 'activate_copy', - 'activate_cut', - 'activate_paste', - 'add_permission_group', - 'add_train_station', - 'admin_action', - 'alt_select_area', - 'alt_select_blueprint_entities', - 'alternative_copy', - 'begin_mining', - 'begin_mining_terrain', - 'build_item', - 'build_rail', - 'build_terrain', - 'cancel_craft', - 'cancel_deconstruct', - 'cancel_new_blueprint', - 'cancel_research', - 'cancel_upgrade', - 'change_active_item_group_for_crafting', - 'change_active_item_group_for_filters', - 'change_active_quick_bar', - 'change_arithmetic_combinator_parameters', - 'change_blueprint_book_record_label', - 'change_decider_combinator_parameters', - 'change_item_label', - 'change_multiplayer_config', - 'change_picking_state', - 'change_programmable_speaker_alert_parameters', - 'change_programmable_speaker_circuit_parameters', - 'change_programmable_speaker_parameters', - 'change_riding_state', - 'change_shooting_state', - 'change_single_blueprint_record_label', - 'change_train_stop_station', - 'change_train_wait_condition', - 'change_train_wait_condition_data', - 'clean_cursor_stack', - 'clear_selected_blueprint', - 'clear_selected_deconstruction_item', - 'clear_selected_upgrade_item', - 'connect_rolling_stock', - 'copy', - 'copy_entity_settings', - 'craft', - 'create_blueprint_like', - 'cursor_split', - 'cursor_transfer', - 'custom_input', - 'cycle_blueprint_book_backwards', - 'cycle_blueprint_book_forwards', - 'deconstruct', - 'delete_blueprint_library', - 'delete_blueprint_record', - 'delete_custom_tag', - 'delete_permission_group', - 'destroy_opened_item', - 'disconnect_rolling_stock', - 'drag_train_schedule', - 'drag_train_wait_condition', - 'drop_blueprint_record', - 'drop_item', - 'drop_to_blueprint_book', - 'edit_custom_tag', - 'edit_permission_group', - 'export_blueprint', - 'fast_entity_split', - 'fast_entity_transfer', - 'go_to_train_station', - 'grab_blueprint_record', - 'gui_checked_state_changed', - 'gui_click', - 'gui_confirmed', - 'gui_elem_changed', - 'gui_location_changed', - 'gui_selected_tab_changed', - 'gui_selection_state_changed', - 'gui_switch_state_changed', - 'gui_text_changed', - 'gui_value_changed', - 'import_blueprint', - 'import_blueprint_string', - 'import_permissions_string', - 'inventory_split', - 'inventory_transfer', - 'launch_rocket', - 'lua_shortcut', - 'map_editor_action', - 'market_offer', - 'mod_settings_changed', - 'open_achievements_gui', - 'open_blueprint_library_gui', - 'open_blueprint_record', - 'open_bonus_gui', - 'open_character_gui', - 'open_equipment', - 'open_gui', - 'open_item', - 'open_kills_gui', - 'open_logistic_gui', - 'open_mod_item', - 'open_production_gui', - 'open_technology_gui', - 'open_train_gui', - 'open_train_station_gui', - 'open_trains_gui', - 'open_tutorials_gui', - 'paste_entity_settings', - 'place_equipment', - 'quick_bar_pick_slot', - 'quick_bar_set_selected_page', - 'quick_bar_set_slot', - 'remove_cables', - 'remove_train_station', - 'reset_assembling_machine', - 'rotate_entity', - 'select_area', - 'select_blueprint_entities', - 'select_entity_slot', - 'select_item', - 'select_mapper_slot', - 'select_next_valid_gun', - 'select_tile_slot', - 'set_auto_launch_rocket', - 'set_autosort_inventory', - 'set_behavior_mode', - 'set_car_weapons_control', - 'set_circuit_condition', - 'set_circuit_mode_of_operation', - 'set_deconstruction_item_tile_selection_mode', - 'set_deconstruction_item_trees_and_rocks_only', - 'set_entity_color', - 'set_entity_energy_property', - 'set_filter', - 'set_heat_interface_mode', - 'set_heat_interface_temperature', - 'set_infinity_container_filter_item', - 'set_infinity_container_remove_unfiltered_items', - 'set_infinity_pipe_filter', - 'set_inserter_max_stack_size', - 'set_inventory_bar', - 'set_logistic_filter_item', - 'set_logistic_filter_signal', - 'set_logistic_trash_filter_item', - 'set_request_from_buffers', - 'set_research_finished_stops_game', - 'set_signal', - 'set_single_blueprint_record_icon', - 'set_splitter_priority', - 'set_train_stopped', - 'setup_assembling_machine', - 'setup_blueprint', - 'setup_single_blueprint_record', - 'smart_pipette', - 'stack_split', - 'stack_transfer', - 'start_repair', - 'start_research', - 'start_walking', - 'stop_building_by_moving', - 'switch_connect_to_logistic_network', - 'switch_constant_combinator_state', - 'switch_inserter_filter_mode_state', - 'switch_power_switch_state', - 'switch_to_rename_stop_gui', - 'take_equipment', - 'toggle_deconstruction_item_entity_filter_mode', - 'toggle_deconstruction_item_tile_filter_mode', - 'toggle_driving', - 'toggle_enable_vehicle_logistics_while_moving', - 'toggle_equipment_movement_bonus', - 'toggle_map_editor', - 'toggle_personal_roboport', - 'toggle_show_entity_info', - 'translate_string', - 'undo', - 'upgrade', - 'upgrade_opened_blueprint', - 'use_artillery_remote', - 'use_item', - 'wire_dragging', - 'write_to_console' - } - }, - inventory = { - fields = { - 'fuel', - 'burnt_result', - 'chest', - 'furnace_source', - 'furnace_result', - 'furnace_modules', - 'character_main', - 'character_guns', - 'character_ammo', - 'character_armor', - 'character_vehicle', - 'character_trash', - 'god_main', - 'editor_main', - 'editor_guns', - 'editor_ammo', - 'editor_armor', - 'roboport_robot', - 'roboport_material', - 'robot_cargo', - 'robot_repair', - 'assembling_machine_input', - 'assembling_machine_output', - 'assembling_machine_modules', - 'lab_input', - 'lab_modules', - 'mining_drill_modules', - 'item_main', - 'rocket_silo_rocket', - 'rocket_silo_result', - 'rocket', - 'car_trunk', - 'car_ammo', - 'cargo_wagon', - 'turret_ammo', - 'beacon_modules', - 'character_corpse', - 'artillery_turret_ammo', - 'artillery_wagon_ammo' - } - }, - logistic_member_index = { - fields = { - 'logistic_container', - 'vehicle_storage', - 'character_requester', - 'character_storage', - 'character_provider', - 'generic_on_off_behavior' - } - }, - logistic_mode = { - fields = { - 'active_provider', - 'storage', - 'requester', - 'passive_provider', - 'buffer' - } - }, - mouse_button_type = { - fields = { - 'left', - 'right', - 'middle' - } - }, - rail_connection_direction = { - fields = { - 'left', - 'straight', - 'right' - } - }, - rail_direction = { - fields = { - 'front', - 'back' - } - }, - render_mode = { - fields = { - 'game', - 'chart', - 'chart_zoomed_in' - } - }, - rich_text_setting = { - fields = { - 'enabled', - 'disabled', - 'highlight' - } - }, - riding = { - fields = { - acceleration = { - fields = { - 'nothing', - 'accelerating', - 'braking', - 'reversing' - } - }, - direction = { - fields = { - 'left', - 'straight', - 'right' - } - } - } - }, - shooting = { - fields = { - 'not_shooting', - 'shooting_enemies', - 'shooting_selected' - } - }, - signal_state = { - fields = { - 'open', - 'closed', - 'reserved', - 'reserved_by_circuit_network' - } - }, - train_state = { - fields = { - 'on_the_path', - 'path_lost', - 'no_schedule', - 'no_path', - 'arrive_signal', - 'wait_signal', - 'arrive_station', - 'wait_station', - 'manual_control_stop', - 'manual_control' - } - }, - transport_line = { - fields = { - 'left_line', - 'right_line', - 'left_underground_line', - 'right_underground_line', - 'secondary_left_line', - 'secondary_right_line', - 'left_split_line', - 'right_split_line', - 'secondary_left_split_line', - 'secondary_right_split_line' - } - }, - wire_connection_id = { - fields = { - 'electric_pole', - 'power_switch_left', - 'power_switch_right' - } - }, - wire_type = { - fields = { - 'red', - 'green', - 'copper' - } - }, - -- Defines additional modules - color = { - other_fields = true - }, - anticolor = { - other_fields = true - }, - lightcolor = { - other_fields = true - }, - time = { - fields = { - 'second', - 'minute', - 'hour', - 'day', - 'week', - 'month', - 'year' - } - } - } - } - } - } -end - ---[[ Options - "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined", - "unused_secondaries", "allow_defined", "allow_defined_top", "module", - "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals", - "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", - "max_cyclomatic_complexity" ---]] - ---[[ Warnings list - -- 011 A syntax error. - -- 021 An invalid inline option. - -- 022 An unpaired inline push directive. - -- 023 An unpaired inline pop directive. - -- 111 Setting an undefined global variable. - -- 112 Mutating an undefined global variable. - -- 113 Accessing an undefined global variable. - -- 121 Setting a read-only global variable. - -- 122 Setting a read-only field of a global variable. - -- 131 Unused implicitly defined global variable. - -- 142 Setting an undefined field of a global variable. - -- 143 Accessing an undefined field of a global variable. - -- 211 Unused local variable. - -- 212 Unused argument. - -- 213 Unused loop variable. - -- 221 Local variable is accessed but never set. - -- 231 Local variable is set but never accessed. - -- 232 An argument is set but never accessed. - -- 233 Loop variable is set but never accessed. - -- 241 Local variable is mutated but never accessed. - -- 311 Value assigned to a local variable is unused. - -- 312 Value of an argument is unused. - -- 313 Value of a loop variable is unused. - -- 314 Value of a field in a table literal is unused. - -- 321 Accessing uninitialized local variable. - -- 331 Value assigned to a local variable is mutated but never accessed. - -- 341 Mutating uninitialized local variable. - -- 411 Redefining a local variable. - -- 412 Redefining an argument. - -- 413 Redefining a loop variable. - -- 421 Shadowing a local variable. - -- 422 Shadowing an argument. - -- 423 Shadowing a loop variable. - -- 431 Shadowing an upvalue. - -- 432 Shadowing an upvalue argument. - -- 433 Shadowing an upvalue loop variable. - -- 511 Unreachable code. - -- 512 Loop can be executed at most once. - -- 521 Unused label. - -- 531 Left-hand side of an assignment is too short. - -- 532 Left-hand side of an assignment is too long. - -- 541 An empty do end block. - -- 542 An empty if branch. - -- 551 An empty statement. - -- 611 A line consists of nothing but whitespace. - -- 612 A line contains trailing whitespace. - -- 613 Trailing whitespace in a string. - -- 614 Trailing whitespace in a comment. - -- 621 Inconsistent indentation (SPACE followed by TAB). - -- 631 Line is too long. ---]] diff --git a/type.patch.lua b/type.patch.lua deleted file mode 100644 index 38c854d7..00000000 --- a/type.patch.lua +++ /dev/null @@ -1,39 +0,0 @@ ----@meta - ----@class script -script = { - ---Raise an event. Only events generated with [LuaBootstrap::generate\_event\_name](https://lua-api.factorio.com/latest/classes/LuaBootstrap.html#generate_event_name) and the following can be raised: - --- - ---Events that can be raised manually: - --- - ---* [on\_console\_chat](https://lua-api.factorio.com/latest/events.html#on_console_chat) - ---* [on\_player\_crafted\_item](https://lua-api.factorio.com/latest/events.html#on_player_crafted_item) - ---* [on\_player\_fast\_transferred](https://lua-api.factorio.com/latest/events.html#on_player_fast_transferred) - ---* [on\_biter\_base\_built](https://lua-api.factorio.com/latest/events.html#on_biter_base_built) - ---* [on\_market\_item\_purchased](https://lua-api.factorio.com/latest/events.html#on_market_item_purchased) - ---* [script\_raised\_built](https://lua-api.factorio.com/latest/events.html#script_raised_built) - ---* [script\_raised\_destroy](https://lua-api.factorio.com/latest/events.html#script_raised_destroy) - ---* [script\_raised\_revive](https://lua-api.factorio.com/latest/events.html#script_raised_revive) - ---* [script\_raised\_teleported](https://lua-api.factorio.com/latest/events.html#script_raised_teleported) - ---* [script\_raised\_set\_tiles](https://lua-api.factorio.com/latest/events.html#script_raised_set_tiles) - --- - ---### Example - --- - ---``` - ----- Raise the on_console_chat event with the desired message 'from' the first player - ---local data = {player_index = 1, message = "Hello friends!"} - ---script.raise_event(defines.events.on_console_chat, data) - ---``` - --- - ---[View Documentation](https://lua-api.factorio.com/latest/classes/LuaBootstrap.html#raise_event) - --- - --- Type patched in 2.0.28: [Bug Report](https://forums.factorio.com/viewtopic.php?f=233&t=125062) - --- Changed "event" from "string | integer" to "LuaEventType" - --- Resolved in 2.0.29, this patch will be removed was version is stable - ---@param event LuaEventType ID or name of the event to raise. - ---@param data table Table with extra data that will be passed to the event handler. Any invalid LuaObjects will silently stop the event from being raised. - raise_event = function(event, data) end; -} - ----@class LuaObject:userdata ---https://github.com/justarandomgeek/vscode-factoriomod-debug/issues/165 From 90f32699c72c385abbbbf6fc41045faeb07a75a0 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:04:24 +0000 Subject: [PATCH 02/46] Fix duplicate class and annotation placement lint errors Classes contributed to by more than one file now carry the `partial` attribute. trains.lua declared its command as `ExpCommand_Artillery`, which was a copy paste. `Color.0` and `MapPosition.0` became `.struct` in fmtk 2.1.6. emmylua rejects `@type` on a function statement or a `do` block, so those are moved onto the local or replaced by `@param` and `@return`. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/commands/authorities.lua | 2 +- exp_commands/module/commands/rcon.lua | 2 +- exp_commands/module/commands/types.lua | 2 +- exp_commands/module/module_exports.lua | 4 ++-- exp_gui/module/control.lua | 2 +- exp_gui/module/elements.lua | 2 +- exp_gui/module/styles.lua | 2 +- exp_gui/module/toolbar.lua | 2 +- exp_scenario/module/commands/_types.lua | 2 +- exp_scenario/module/commands/reports.lua | 5 ++++- exp_scenario/module/commands/search.lua | 8 ++++++-- exp_scenario/module/commands/trains.lua | 2 +- exp_scenario/module/control/discord_alerts.lua | 4 ++-- exp_scenario/module/control/spawn_area.lua | 2 +- exp_scenario/module/gui/elements.lua | 3 +-- exp_scenario/module/gui/player_bonus.lua | 3 +-- exp_scenario/module/gui/science_production.lua | 9 +++------ exp_util/module/async.lua | 3 --- 18 files changed, 29 insertions(+), 30 deletions(-) diff --git a/exp_commands/module/commands/authorities.lua b/exp_commands/module/commands/authorities.lua index db82a6ce..0001b16d 100644 --- a/exp_commands/module/commands/authorities.lua +++ b/exp_commands/module/commands/authorities.lua @@ -10,7 +10,7 @@ The default permission authorities controlled by the flags: admin_only, system_o local Storage = require("modules/exp_util/storage") -local Commands = require("modules/exp_commands") --- @class Commands +local Commands = require("modules/exp_commands") --- @class (partial) Commands local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised local authorities = {} diff --git a/exp_commands/module/commands/rcon.lua b/exp_commands/module/commands/rcon.lua index 1e1d2ec5..cedab49e 100644 --- a/exp_commands/module/commands/rcon.lua +++ b/exp_commands/module/commands/rcon.lua @@ -12,7 +12,7 @@ local ExpUtil = require("modules/exp_util") local Async = require("modules/exp_util/async") local Storage = require("modules/exp_util/storage") -local Commands = require("modules/exp_commands") --- @class Commands +local Commands = require("modules/exp_commands") --- @class (partial) Commands local rcon_env = {} --- @type table local rcon_static = {} --- @type table diff --git a/exp_commands/module/commands/types.lua b/exp_commands/module/commands/types.lua index 11511b4f..0cef9b84 100644 --- a/exp_commands/module/commands/types.lua +++ b/exp_commands/module/commands/types.lua @@ -25,7 +25,7 @@ local Commands = require("modules/exp_commands") local add, parse = Commands.add_data_type, Commands.parse_input local valid, invalid = Commands.status.success, Commands.status.invalid_input -local types = {} --- @class Commands.types +local types = {} --- @class (partial) Commands.types --- A boolean value where true is one of: yes, y, true, 1 types.boolean = diff --git a/exp_commands/module/module_exports.lua b/exp_commands/module/module_exports.lua index 93a5c626..564312b0 100644 --- a/exp_commands/module/module_exports.lua +++ b/exp_commands/module/module_exports.lua @@ -61,7 +61,7 @@ local Search = require("modules/exp_commands/search") --- @type LuaPlayer? local _print_player ---- @class Commands +--- @class (partial) Commands local Commands = { color = ExpUtil.color, format_rich_text_color = ExpUtil.format_rich_text_color, @@ -84,7 +84,7 @@ local Commands = { --- Contains the different status values a command can return Commands.status = {} ---- @class Commands.types: table +--- @class (partial) Commands.types: table --- Stores all input parsers and validators for different data types Commands.types = {} diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index e6c7989a..6fdd5727 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -16,7 +16,7 @@ Storage.register(player_elements, function(tbl) player_elements = tbl end) ---- @class Gui +--- @class (partial) Gui local Gui = { define = ExpElement.new, from_argument = ExpElement.from_argument, diff --git a/exp_gui/module/elements.lua b/exp_gui/module/elements.lua index f8df5853..368e9f4e 100644 --- a/exp_gui/module/elements.lua +++ b/exp_gui/module/elements.lua @@ -1,4 +1,4 @@ ---- @class Gui +--- @class (partial) Gui local Gui = require("modules/exp_gui") --- @class Gui.elements diff --git a/exp_gui/module/styles.lua b/exp_gui/module/styles.lua index ca04a907..b459d1c9 100644 --- a/exp_gui/module/styles.lua +++ b/exp_gui/module/styles.lua @@ -1,4 +1,4 @@ ---- @class Gui +--- @class (partial) Gui local Gui = require("modules/exp_gui") --- @class Gui.styles diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua index 401d9bd6..22a478af 100644 --- a/exp_gui/module/toolbar.lua +++ b/exp_gui/module/toolbar.lua @@ -1,5 +1,5 @@ ---- @class Gui +--- @class (partial) Gui local Gui = require("modules/exp_gui") local ExpElement = require("modules/exp_gui/prototype") local mod_gui = require("mod-gui") diff --git a/exp_scenario/module/commands/_types.lua b/exp_scenario/module/commands/_types.lua index 0acfeb82..f23ff943 100644 --- a/exp_scenario/module/commands/_types.lua +++ b/exp_scenario/module/commands/_types.lua @@ -18,7 +18,7 @@ local valid, invalid = Commands.status.success, Commands.status.invalid_input local Roles = require("modules.exp_legacy.expcore.roles") local highest_role = Roles.get_player_highest_role -local types = {} --- @class Commands.types +local types = {} --- @class (partial) Commands.types --- A role defined by exp roles types.role = add("role", Commands.types.key_of(Roles.config.roles)) diff --git a/exp_scenario/module/commands/reports.lua b/exp_scenario/module/commands/reports.lua index e0f845aa..6aebce38 100644 --- a/exp_scenario/module/commands/reports.lua +++ b/exp_scenario/module/commands/reports.lua @@ -11,7 +11,10 @@ local player_has_flag = Roles.player_has_flag local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports ---- @type Commands.InputParser +--- @param input string +--- @param player LuaPlayer +--- @return Commands.Status +--- @return LuaPlayer | LocalisedString local function reportable_player(input, player) local success, status, result = parse_input(input, player, Commands.types.player) if not success then return status, result end diff --git a/exp_scenario/module/commands/search.lua b/exp_scenario/module/commands/search.lua index 13bc03b8..1251420a 100644 --- a/exp_scenario/module/commands/search.lua +++ b/exp_scenario/module/commands/search.lua @@ -10,7 +10,10 @@ local format_player_name = Commands.format_player_name_locale local format_number = require("util").format_number --- A player who is of a lower role than the executing player ---- @type Commands.InputParser +--- @param input string +--- @param player LuaPlayer +--- @return Commands.Status +--- @return LuaItemPrototype | LocalisedString local function parse_item(input, player) -- First Case - internal name is given -- Second Case - rich text is given @@ -146,7 +149,8 @@ Commands.new("search-online", { "exp-commands_search.description-online" }) end) --- Return the amount of an item a player has ---- @type SortFunction +--- @param data SearchResult +--- @return number local function sort_by_count(data) return data.count end diff --git a/exp_scenario/module/commands/trains.lua b/exp_scenario/module/commands/trains.lua index ee8885a5..eb3a3144 100644 --- a/exp_scenario/module/commands/trains.lua +++ b/exp_scenario/module/commands/trains.lua @@ -10,7 +10,7 @@ local format_number = require("util").format_number local commands = {} --- Set all trains to automatic ---- @class ExpCommand_Artillery.commands.artillery: ExpCommand +--- @class ExpCommand_Trains.commands.set_trains_to_automatic: ExpCommand --- @overload fun(player: LuaPlayer, surface: LuaSurface?, force: LuaForce?) commands.set_trains_to_automatic = Commands.new("set-trains-to-automatic", { "exp-commands_trains.description" }) :optional("surface", { "exp-commands_trains.arg-surface" }, Commands.types.surface) diff --git a/exp_scenario/module/control/discord_alerts.lua b/exp_scenario/module/control/discord_alerts.lua index 38978e1c..5fc894f1 100644 --- a/exp_scenario/module/control/discord_alerts.lua +++ b/exp_scenario/module/control/discord_alerts.lua @@ -39,7 +39,7 @@ local function get_player_name(event) end --- Convert a colour value into hex ---- @param color Color.0 +--- @param color Color.struct --- @return string local function to_hex(color) local hex_digits = "0123456789ABCDEF" @@ -53,7 +53,7 @@ local function to_hex(color) end --- Emit the requires json to file for the given event arguments ---- @param opts { title: string?, color: (Color.0 | string)?, description: string?, tick: number?, fields: { name: string, value: string, inline: boolean? }[] } +--- @param opts { title: string?, color: (Color.struct | string)?, description: string?, tick: number?, fields: { name: string, value: string, inline: boolean? }[] } local function emit_event(opts) local admins_online = 0 local players_online = 0 diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index e4c7213e..4810585c 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -7,7 +7,7 @@ local config = require("modules.exp_legacy.config.spawn_area") --- Apply an offset to a LuaPosition --- @param position MapPosition --- @param offset MapPosition ---- @return MapPosition.0 +--- @return MapPosition.struct local function apply_offset(position, offset) return { x = (position.x or position[1]) + (offset.x or offset[1]), diff --git a/exp_scenario/module/gui/elements.lua b/exp_scenario/module/gui/elements.lua index 66cfc99d..151bdfef 100644 --- a/exp_scenario/module/gui/elements.lua +++ b/exp_scenario/module/gui/elements.lua @@ -25,8 +25,7 @@ Elements.online_player_dropdown = Gui.define("player_dropdown") } --[[ @as any ]] --- To help with caching and avoid context changes the player list from the previous update is remembered ---- @type (string?)[] -do local _player_names = {} +do local _player_names = {} --- @type (string?)[] --- Updates the player name list after a join or leave --- @return (string?)[] function Elements.online_player_dropdown._update_player_names() diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index 7c5fd84b..e5c9ff8a 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -47,8 +47,7 @@ Elements.bonus_used = Gui.define("player_bonus/bonus_used") :element_data(0) --[[ @as any ]] --- Value is cached to save perf ---- @type table -do local _points_limit = {} +do local _points_limit = {} --- @type table --- Clear the cache for points limit --- @param player LuaPlayer function Elements.bonus_used._clear_points_limit_cache(player) diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index 0224a253..f3ceb24a 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -326,8 +326,7 @@ function Elements.science_table.refresh_row(science_table, row_data) Elements.production_label.refresh(row.used, row_data.used) end ---- @type table -do local _row_data = {} +do local _row_data = {} --- @type table --- Refresh the production tables for all online players function Elements.science_table.refresh_online() -- Refresh the row data for online forces @@ -416,8 +415,7 @@ function Elements.eta_label.refresh(eta_label) eta_label.tooltip = display_data.tooltip end ---- @type Elements.eta_label.display_data -do local _display_data = {} +do local _display_data = {} --- @type Elements.eta_label.display_data --- Refresh the eta label for all online players function Elements.eta_label.refresh_online() -- Refresh the row data for online forces @@ -477,8 +475,7 @@ local _fp_one_hour = defines.flow_precision_index.one_hour --- @field ten_minutes ExpGui_ScienceProduction._item_data --- @field one_hour ExpGui_ScienceProduction._item_data ---- @type table -do local _production_data = {} +do local _production_data = {} --- @type table --- Get the production stats for a force --- @param flow_stats any diff --git a/exp_util/module/async.lua b/exp_util/module/async.lua index 9afaa377..4056969c 100644 --- a/exp_util/module/async.lua +++ b/exp_util/module/async.lua @@ -261,7 +261,6 @@ local empty_table = setmetatable({}, { --- Default status, will raise on_function_complete --- @param ... any The return value of the async call --- @return Async.Status, any[] ---- @type Async.Status function Async.status.complete(...) if ... == nil then return Async.status.complete, empty_table @@ -272,7 +271,6 @@ end --- Will queue the function to be called again on the next tick using the new arguments --- @param ... any The arguments to call the function with --- @return Async.Status, any[] ---- @type Async.Status function Async.status.continue(...) if ... == nil then return Async.status.continue, empty_table @@ -284,7 +282,6 @@ end --- @param ticks number The number of ticks to delay for --- @param ... any The arguments to call the function with --- @return Async.Status, number, any[] ---- @type Async.Status function Async.status.delay(ticks, ...) ExpUtil.assert_argument_type(ticks, "number", 1, "ticks") assert(ticks > 0, "Ticks must be a positive number") From b9cfbc90366141604a55cd36188e32198be80dd6 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:12:48 +0000 Subject: [PATCH 03/46] Narrow aabb types and give generic aliases their type argument The aabb functions all index the named members, so passing the shorthand [MapPosition, MapPosition] form would error at runtime. The annotations now say so. emmylua requires a type argument on a generic alias, luals did not. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/module_exports.lua | 12 +++--- .../module/control/fast_deconstruction.lua | 2 +- exp_scenario/module/control/help_bubbles.lua | 4 +- exp_util/module/aabb.lua | 41 +++++++++++-------- exp_util/module/async.lua | 30 +++++++------- exp_util/module/selection.lua | 2 +- 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/exp_commands/module/module_exports.lua b/exp_commands/module/module_exports.lua index 564312b0..44542a19 100644 --- a/exp_commands/module/module_exports.lua +++ b/exp_commands/module/module_exports.lua @@ -84,7 +84,7 @@ local Commands = { --- Contains the different status values a command can return Commands.status = {} ---- @class (partial) Commands.types: table +--- @class (partial) Commands.types: table | Commands.InputParserFactory> --- Stores all input parsers and validators for different data types Commands.types = {} @@ -103,7 +103,7 @@ end --- @class Commands.Argument --- @field name string The name of the argument --- @field description LocalisedString? The description of the argument ---- @field input_parser Commands.InputParser The input parser for the argument +--- @field input_parser Commands.InputParser The input parser for the argument --- @field optional boolean True when the argument is optional --- @field default any? The default value of the argument @@ -281,7 +281,7 @@ end --- @alias Commands.InputParserFactory fun(...: any): Commands.InputParser --- Add a new input parser to the command library, this method validates that it does not already exist ---- @generic T : Commands.InputParser | Commands.InputParserFactory +--- @generic T : Commands.InputParser | Commands.InputParserFactory --- @param data_type string The name of the data type the input parser reads in and validates, becomes a key of Commands.types --- @param input_parser T The function used to parse and validate the data type --- @return T # The function which was provided as the second argument @@ -295,7 +295,7 @@ function Commands.add_data_type(data_type, input_parser) end --- Remove an input parser for a data type, must be the same string that was passed to add_input_parser ---- @param data_type string | Commands.InputParser | Commands.InputParserFactory The data type or input parser you want to remove the input parser for +--- @param data_type string | Commands.InputParser | Commands.InputParserFactory The data type or input parser you want to remove the input parser for function Commands.remove_data_type(data_type) Commands.types[data_type] = nil for k, v in pairs(Commands.types) do @@ -433,7 +433,7 @@ end --- Add a new required argument to the command of the given data type --- @param name string The name of the argument being added --- @param description LocalisedString? The description of the argument being added ---- @param input_parser Commands.InputParser The input parser to be used for the argument +--- @param input_parser Commands.InputParser The input parser to be used for the argument --- @return ExpCommand function Commands._prototype:argument(name, description, input_parser) assert_command_mutable(self) @@ -454,7 +454,7 @@ end --- Add a new optional argument to the command of the given data type --- @param name string The name of the argument being added --- @param description LocalisedString? The description of the argument being added ---- @param input_parser Commands.InputParser The input parser to be used for the argument +--- @param input_parser Commands.InputParser The input parser to be used for the argument --- @return ExpCommand function Commands._prototype:optional(name, description, input_parser) assert_command_mutable(self) diff --git a/exp_scenario/module/control/fast_deconstruction.lua b/exp_scenario/module/control/fast_deconstruction.lua index 4481f9b4..5470afb6 100644 --- a/exp_scenario/module/control/fast_deconstruction.lua +++ b/exp_scenario/module/control/fast_deconstruction.lua @@ -22,7 +22,7 @@ local min = math.min --- @field trees LuaEntity[] --- @field tree_count number --- @field permission "fast" | "allow" | "disallow" ---- @field task Async.AsyncReturn +--- @field task Async.AsyncReturn local cache --- @type TreeDeconCache? diff --git a/exp_scenario/module/control/help_bubbles.lua b/exp_scenario/module/control/help_bubbles.lua index 6740b93a..b37c143f 100644 --- a/exp_scenario/module/control/help_bubbles.lua +++ b/exp_scenario/module/control/help_bubbles.lua @@ -6,7 +6,7 @@ local Async = require("modules/exp_util/async") local Storage = require("modules/exp_util/storage") local config = require("modules.exp_legacy.config.compilatron") ---- @type table +--- @type table> local persistent_locations = {} Storage.register(persistent_locations, function(tbl) persistent_locations = tbl @@ -55,7 +55,7 @@ local speech_bubble_task = --- @param entity LuaEntity the entity which will have messages spawn from it --- @param messages LocalisedString[] the messages which should be shown --- @param starting_index number? the message index to start at, default 1 ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn local function register_entity(entity, messages, starting_index) return speech_bubble_task{ entity = entity, diff --git a/exp_util/module/aabb.lua b/exp_util/module/aabb.lua index 64c5719d..9ac567d6 100644 --- a/exp_util/module/aabb.lua +++ b/exp_util/module/aabb.lua @@ -7,26 +7,31 @@ local ceil = math.ceil local min = math.min local max = math.max +--- An axis aligned bounding box using named members, the shorthand form is not supported +--- @class ExpUtil_AABB.Box +--- @field left_top MapPosition.struct +--- @field right_bottom MapPosition.struct + --- @class ExpUtil_AABB local AABB = {} --- Check if an area is valid ---- @param aabb BoundingBox +--- @param aabb ExpUtil_AABB.Box --- @return boolean # True if the area is valid function AABB.valid(aabb) return aabb.left_top.x < aabb.right_bottom.x and aabb.left_top.y < aabb.right_bottom.y end --- Returns the size of the area contained within an AABB ---- @param aabb BoundingBox +--- @param aabb ExpUtil_AABB.Box --- @return number function AABB.size(aabb) return (aabb.right_bottom.x - aabb.left_top.x) * (aabb.right_bottom.y - aabb.left_top.y) end --- Clone an area, allows for safe mutation of an input value ---- @param aabb BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.clone(aabb) return { left_top = { x = aabb.left_top.x, y = aabb.left_top.y }, @@ -35,8 +40,8 @@ function AABB.clone(aabb) end --- Expand an area to be integer aligned, expanding away from 0 ---- @param aabb BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.expand(aabb) return { left_top = { x = floor(aabb.left_top.x), y = floor(aabb.left_top.y) }, @@ -45,8 +50,8 @@ function AABB.expand(aabb) end --- Contract an area to be integer aligned, contracting towards 0 ---- @param aabb BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.contract(aabb) return { left_top = { x = ceil(aabb.left_top.x), y = ceil(aabb.left_top.y) }, @@ -55,9 +60,9 @@ function AABB.contract(aabb) end --- Expand an area to include all other areas ---- @param aabb BoundingBox ---- @param ... BoundingBox ---- @return BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @param ... ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box function AABB.union(aabb, ...) local rtn = AABB.clone(aabb) for _, next_aabb in ipairs{ ... } do @@ -70,9 +75,9 @@ function AABB.union(aabb, ...) end --- Contract an area to include to the overlap of all areas ---- @param aabb BoundingBox ---- @param ... BoundingBox ---- @return BoundingBox? # Nil if there is no intersection +--- @param aabb ExpUtil_AABB.Box +--- @param ... ExpUtil_AABB.Box +--- @return ExpUtil_AABB.Box? # Nil if there is no intersection function AABB.intersect(aabb, ...) local rtn = AABB.clone(aabb) for _, next_aabb in ipairs{ ... } do @@ -88,8 +93,8 @@ function AABB.intersect(aabb, ...) end --- Check if a point is contained within an area ---- @param aabb BoundingBox ---- @param point MapPosition +--- @param aabb ExpUtil_AABB.Box +--- @param point MapPosition.struct --- @return boolean # True if the point is within or on the edge of the bounding box function AABB.contains_point(aabb, point) return point.x >= aabb.left_top.x and point.y >= aabb.left_top.y @@ -97,8 +102,8 @@ function AABB.contains_point(aabb, point) end --- Check if an area is fulling contained within another area ---- @param aabb BoundingBox ---- @param other BoundingBox +--- @param aabb ExpUtil_AABB.Box +--- @param other ExpUtil_AABB.Box --- @return boolean # True if the point is within or on the edge of the bounding box function AABB.contains_area(aabb, other) return AABB.contains_point(aabb, other.left_top) and AABB.contains_point(aabb, other.right_bottom) diff --git a/exp_util/module/async.lua b/exp_util/module/async.lua index 4056969c..3a837030 100644 --- a/exp_util/module/async.lua +++ b/exp_util/module/async.lua @@ -94,7 +94,7 @@ Async.status = {} --- @class Async.AsyncFunction --- @field id number The id of this async function ---- @operator call: Async.AsyncReturn +--- @operator call: Async.AsyncReturn Async._function_prototype = {} Async._function_metatable = { @@ -123,20 +123,20 @@ script.register_metatable("AsyncReturn", Async._return_metatable) --- Storage Variables -local resolve_next --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on the next tick -local resolve_queue --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on a later tick +local resolve_next --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on the next tick +local resolve_queue --- @type Async.AsyncReturn[] Stores a queue of async functions to be executed on a later tick --- Insert an item into the priority queue ---- @param pending Async.AsyncReturn ---- @return Async.AsyncReturn +--- @param pending Async.AsyncReturn +--- @return Async.AsyncReturn local function add_to_next_tick(pending) resolve_next[#resolve_next + 1] = pending return pending end --- Insert an item into the priority queue ---- @param pending Async.AsyncReturn ---- @return Async.AsyncReturn +--- @param pending Async.AsyncReturn +--- @return Async.AsyncReturn local function add_to_resolve_queue(pending) local tick = pending.tick for index = #resolve_queue, 1, -1 do @@ -191,7 +191,7 @@ end --- Run an async function on the next tick, this is the default and can be used to bypass permission groups --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn function Async._function_prototype:start_soon(...) assert(Async._registered[self.id], "Async function is not registered") Async._queue_pressure[self.id] = Async._queue_pressure[self.id] + 1 @@ -204,7 +204,7 @@ end --- Run an async function after the given number of ticks --- @param ticks number The number of ticks to call the function after --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn function Async._function_prototype:start_after(ticks, ...) ExpUtil.assert_argument_type(ticks, "number", 1, "ticks") assert(ticks > 0, "Ticks must be a positive number") @@ -219,7 +219,7 @@ end --- Run an async function on the next tick if the function is not already queued, allows singleton task/thread behaviour --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn | nil +--- @return Async.AsyncReturn | nil function Async._function_prototype:start_task(...) assert(Async._registered[self.id], "Async function is not registered") if Async._queue_pressure[self.id] > 0 then return end @@ -228,7 +228,7 @@ end --- Run an async function on this tick, then queue it based on its return value --- @param ... any The arguments to call the function with ---- @return Async.AsyncReturn +--- @return Async.AsyncReturn function Async._function_prototype:start_now(...) assert(Async._registered[self.id], "Async function is not registered") local status, rtn1, rtn2 = Async._registered[self.id](...) @@ -293,11 +293,11 @@ end --- Status Returns. ---- @type Async.AsyncReturn[], Async.AsyncReturn[] +--- @type Async.AsyncReturn[], Async.AsyncReturn[] local new_next, new_queue = {}, {} -- File scope to allow for reuse --- Executes an async function and processes the return value ---- @param pending Async.AsyncReturn +--- @param pending Async.AsyncReturn --- @param tick number local function exec(pending, tick) local async_func = Async._registered[pending.func_id] @@ -397,9 +397,9 @@ end --- @package function Async.on_init() if storage.exp_async_next == nil then - --- @type Async.AsyncReturn[] + --- @type Async.AsyncReturn[] storage.exp_async_next = {} - --- @type Async.AsyncReturn[] + --- @type Async.AsyncReturn[] storage.exp_async_queue = {} end Async.on_load() diff --git a/exp_util/module/selection.lua b/exp_util/module/selection.lua index a50a7940..c6695ca7 100644 --- a/exp_util/module/selection.lua +++ b/exp_util/module/selection.lua @@ -23,7 +23,7 @@ local Selection = { --- @field player_index number --- @field selection Selection.Active - --- @type table + --- @type table[] }> _registered = {}, --- @package From c233a9ab066f57cfbd3d63a1ed47972c2b2d5af5 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:16:40 +0000 Subject: [PATCH 04/46] Defer need-check-nil and clean up stale suppressions emmylua deserialises an unknown diagnostic code to a catch all that matches no checker, so every `invisible`, `nil-check` and `global-element` suppression was silently doing nothing. Renamed to the emmylua codes. `name-style-check` has no equivalent and is dropped, it was already disabled under luals. The `get_tile` suppressions referenced an api typedef bug from 2024 which no longer reproduces. need-check-nil is deferred rather than disabled on merit: a third of the 407 findings come from MapPosition and BoundingBox being aliased as `struct|[double, double]`, so every `entity.position.x` reads as possibly nil. The rest need per site knowledge of runtime invariants. Co-Authored-By: Claude Opus 5 (1M context) --- .emmyrc.json | 2 ++ exp_commands/module/commands/rcon.lua | 2 -- exp_commands/module/commands/sudo.lua | 2 +- exp_groups/module/globals.lua | 2 +- exp_gui/module/control.lua | 6 +++--- exp_gui/module/toolbar.lua | 2 +- exp_legacy/module/modules/gui/_role_updates.lua | 2 +- exp_roles/module/globals.lua | 2 +- exp_scenario/module/commands/roles.lua | 2 +- exp_scenario/module/control/degrading_tiles.lua | 2 -- exp_scenario/module/control/spawn_area.lua | 2 -- exp_scenario/module/gui/debug/expcore_gui_view.lua | 14 +++++++------- .../module/gui/debug/redmew_global_view.lua | 2 +- 13 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index c103023e..62b2e74a 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -18,6 +18,7 @@ }, "$comment-disable": "Carried over from the luals config, which had these as editor only rather than checked in ci", "$comment-unnecessary-if": "Disabled by the config fmtk generates for itself, the api union types make it unreliable", + "$comment-need-check-nil": "To be enabled once the backlog is cleared, a third of it is MapPosition and BoundingBox being aliased as struct|[double, double]", "diagnostics": { "globals": [ "__DebugAdapter", "__Profiler" ], "disable": [ @@ -26,6 +27,7 @@ "invert-if", "missing-parameter", "missing-return-value", + "need-check-nil", "param-type-mismatch", "preferred-local-alias", "redefined-local", diff --git a/exp_commands/module/commands/rcon.lua b/exp_commands/module/commands/rcon.lua index cedab49e..77da68f6 100644 --- a/exp_commands/module/commands/rcon.lua +++ b/exp_commands/module/commands/rcon.lua @@ -21,12 +21,10 @@ setmetatable(rcon_static, { __index = _G }) setmetatable(rcon_env, { __index = rcon_static }) --- Some common static values which can be added now ---- @diagnostic disable: name-style-check rcon_static.Async = Async rcon_static.ExpUtil = ExpUtil rcon_static.Commands = Commands rcon_static.print = Commands.print ---- @diagnostic enable: name-style-check --- Some common callback values which are useful when a player uses the command --- @alias ExpCommand.RconDynamic fun(player: LuaPlayer?): any diff --git a/exp_commands/module/commands/sudo.lua b/exp_commands/module/commands/sudo.lua index c56ef29d..33fb9e3c 100644 --- a/exp_commands/module/commands/sudo.lua +++ b/exp_commands/module/commands/sudo.lua @@ -19,7 +19,7 @@ Commands.new("_sudo", { "exp-commands_sudo.description" }) --- @cast command ExpCommand --- @cast parameter string - --- @diagnostic disable-next-line: invisible + --- @diagnostic disable-next-line: access-invisible return Commands._event_handler{ name = command.name, tick = game.tick, diff --git a/exp_groups/module/globals.lua b/exp_groups/module/globals.lua index ce036c21..27ecdb31 100644 --- a/exp_groups/module/globals.lua +++ b/exp_groups/module/globals.lua @@ -5,7 +5,7 @@ Therefore, we advise that this should be the only file in your module to expose Typically this would be your control file as shown in the example below ]] ---- @diagnostic disable: global-element +--- @diagnostic disable: global-in-non-module -- Access using `/sc exp_groups.foo()` exp_groups = require("modules/exp_groups/control") diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index 6fdd5727..56cf2dd3 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -188,9 +188,9 @@ function Gui._ensure_consistency(event) -- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code if Gui.toolbar then - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible Gui.toolbar._create_elements(player) - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible Gui.toolbar._ensure_consistency(player) end end @@ -211,7 +211,7 @@ local function on_gui_opened(event) if visible then event.element = element - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible define:raise_event(event) end end diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua index 22a478af..69cc68ff 100644 --- a/exp_gui/module/toolbar.lua +++ b/exp_gui/module/toolbar.lua @@ -29,7 +29,7 @@ Toolbar.on_gui_button_toggled = script.generate_event_name() --- @class _ExpElement._prototype --- @field on_button_toggled ExpElement.OnEventAdder ---- @diagnostic disable-next-line: invisible, inject-field +--- @diagnostic disable-next-line: access-invisible, inject-field function ExpElement._prototype.on_button_toggled(self, handler) return self:on_event(Toolbar.on_gui_button_toggled, handler) end diff --git a/exp_legacy/module/modules/gui/_role_updates.lua b/exp_legacy/module/modules/gui/_role_updates.lua index 75273b57..ac171b49 100644 --- a/exp_legacy/module/modules/gui/_role_updates.lua +++ b/exp_legacy/module/modules/gui/_role_updates.lua @@ -2,6 +2,6 @@ local Gui = require("modules/exp_gui") local Roles = require("modules.exp_legacy.expcore.roles") local Event = require("modules/exp_legacy/utils/event") ---- @diagnostic disable invisible +--- @diagnostic disable: access-invisible Event.add(Roles.events.on_role_assigned, Gui._ensure_consistency) Event.add(Roles.events.on_role_unassigned, Gui._ensure_consistency) diff --git a/exp_roles/module/globals.lua b/exp_roles/module/globals.lua index 10d74063..2a982c2e 100644 --- a/exp_roles/module/globals.lua +++ b/exp_roles/module/globals.lua @@ -4,7 +4,7 @@ However, sometimes you need globals, for example to access functions within rcon Therefore, we advise that this should be the only file in your module to expose globals ]] ---- @diagnostic disable: global-element +--- @diagnostic disable: global-in-non-module -- Access using `/sc exp_roles.foo()` exp_roles = require("modules/exp_roles/control") diff --git a/exp_scenario/module/commands/roles.lua b/exp_scenario/module/commands/roles.lua index 7adbb3a1..eb2ec833 100644 --- a/exp_scenario/module/commands/roles.lua +++ b/exp_scenario/module/commands/roles.lua @@ -55,7 +55,7 @@ Commands.new("get-roles", { "exp-commands_roles.description-get" }) end local last = #roles_formatted - --- @diagnostic disable-next-line nil-check + --- @diagnostic disable-next-line: need-check-nil roles_formatted[last] = roles_formatted[last][2] return Commands.status.success(response) diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index ad16dbfd..b83090a1 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -18,7 +18,6 @@ end --- @param surface LuaSurface --- @param position MapPosition local function degrade_tile(surface, position) - --- @diagnostic disable-next-line Incorrect Api Type: https://forums.factorio.com/viewtopic.php?f=233&t=109145&p=593761&hilit=get_tile#p593761 local tile = surface.get_tile(position) local tile_name = tile.name local degrade_tile_name = config.degrade_order[tile_name] @@ -61,7 +60,6 @@ end --- @param position MapPosition --- @return number? local function get_tile_strength(surface, position) - --- @diagnostic disable-next-line Incorrect Api Type: https://forums.factorio.com/viewtopic.php?f=233&t=109145&p=593761&hilit=get_tile#p593761 local tile = surface.get_tile(position) local tile_name = tile.name local strength = config.strengths[tile_name] diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index 4810585c..cceb15fa 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -164,7 +164,6 @@ local function clear_spawn_area(surface, offset) local get_tile = surface.get_tile -- Make sure a non water tile is used for filling - --- @diagnostic disable-next-line Incorrect Api Type: https://forums.factorio.com/viewtopic.php?f=233&t=109145&p=593761&hilit=get_tile#p593761 local starting_tile = get_tile(offset) local fill_tile = starting_tile.collides_with("player") and "landfill" or starting_tile.name local fill_radius = config.spawn_area.landfill_radius @@ -185,7 +184,6 @@ local function clear_spawn_area(surface, offset) if dst < tile_radius_sqr then -- If it is inside the decon radius always set the tile tiles_to_make[#tiles_to_make + 1] = { name = decon_tile, position = pos } - --- @diagnostic disable-next-line Incorrect Api Type: https://forums.factorio.com/viewtopic.php?f=233&t=109145&p=593761&hilit=get_tile#p593761 elseif dst < fill_radius_sqr and get_tile(pos).collides_with("player") then -- If it is inside the fill radius only set the tile if it is water tiles_to_make[#tiles_to_make + 1] = { name = fill_tile, position = pos } diff --git a/exp_scenario/module/gui/debug/expcore_gui_view.lua b/exp_scenario/module/gui/debug/expcore_gui_view.lua index 4fd18849..399656de 100644 --- a/exp_scenario/module/gui/debug/expcore_gui_view.lua +++ b/exp_scenario/module/gui/debug/expcore_gui_view.lua @@ -27,7 +27,7 @@ function Public.show(container) local left_panel_style = left_panel.style left_panel_style.width = 300 - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible for element_name in pairs(ExpElement._elements) do local header = left_panel.add{ type = "flow" }.add{ type = "label", name = header_name, caption = element_name } Gui.set_data(header, element_name) @@ -92,18 +92,18 @@ Gui.on_click( input_text_box.text = concat{ "ExpElement._elements[\"", element_name, "\"]" } input_text_box.style.font_color = Color.black - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible local define = ExpElement._elements[element_name] local content = dump({ - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible debug = define._debug, - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible has_handlers = define._has_handlers, - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible track_elements = define._track_elements, - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible elements = ExpIter._scopes[element_name], - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible data = ExpData._scopes[element_name]._raw, }) or "nil" right_panel.text = content diff --git a/exp_scenario/module/gui/debug/redmew_global_view.lua b/exp_scenario/module/gui/debug/redmew_global_view.lua index 8e387479..de43f1ac 100644 --- a/exp_scenario/module/gui/debug/redmew_global_view.lua +++ b/exp_scenario/module/gui/debug/redmew_global_view.lua @@ -24,7 +24,7 @@ function Public.show(container) local left_panel_style = left_panel.style left_panel_style.width = 300 - --- @diagnostic disable-next-line invisible + --- @diagnostic disable-next-line: access-invisible for token_id in pairs(Storage._registered) do local header = left_panel.add{ type = "flow" }.add{ type = "label", name = header_name, caption = token_id } Gui.set_data(header, token_id) From 0bb92966eb59ba765291439fe31413f2f9ce5049 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:19:42 +0000 Subject: [PATCH 05/46] Fix type annotations the linter found to be wrong Async ids come from `get_function_name`, which returns a string, but were documented as numbers. `setmetatable` in clusterio's compat module loses `LibCompat` on the module return, so the require is cast. `GuiData.__index` accepts a `DataKey` but emmylua needs that as an index signature on the class. Co-Authored-By: Claude Opus 5 (1M context) --- exp_groups/module/control.lua | 2 +- exp_gui/module/data.lua | 3 ++- exp_roles/module/control.lua | 2 +- exp_util/module/async.lua | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/exp_groups/module/control.lua b/exp_groups/module/control.lua index 0c61af71..232fd6e3 100644 --- a/exp_groups/module/control.lua +++ b/exp_groups/module/control.lua @@ -3,7 +3,7 @@ Adds permission group syncing to clusterio ]] local clusterio_api = require("modules/clusterio/api") -local compat = require("modules/clusterio/compat") +local compat = require("modules/clusterio/compat") --[[@as LibCompat]] --- Top level module table, contains event handlers and public methods --- @class ExpGroups diff --git a/exp_gui/module/data.lua b/exp_gui/module/data.lua index 8ae9e15e..dffe6e64 100644 --- a/exp_gui/module/data.lua +++ b/exp_gui/module/data.lua @@ -61,7 +61,8 @@ local GuiData = { --- @field element_data table> --- @field player_data table --- @field force_data table ---- @field global_data table +--- @field global_data table +--- @field [DataKey] any -- This class has no prototype methods -- Same as raw but __index ensures the values exist diff --git a/exp_roles/module/control.lua b/exp_roles/module/control.lua index 59b5696e..93c5cd37 100644 --- a/exp_roles/module/control.lua +++ b/exp_roles/module/control.lua @@ -12,7 +12,7 @@ made with `assign_player_local`. ]] local clusterio_api = require("modules/clusterio/api") -local compat = require("modules/clusterio/compat") +local compat = require("modules/clusterio/compat") --[[@as LibCompat]] local Async = require("modules/exp_util/async") --- @class ExpRoles diff --git a/exp_util/module/async.lua b/exp_util/module/async.lua index 3a837030..73bb7c81 100644 --- a/exp_util/module/async.lua +++ b/exp_util/module/async.lua @@ -93,7 +93,7 @@ local Async = { Async.status = {} --- @class Async.AsyncFunction ---- @field id number The id of this async function +--- @field id string The id of this async function --- @operator call: Async.AsyncReturn Async._function_prototype = {} @@ -104,10 +104,10 @@ Async._function_metatable = { } --- @class Async.AsyncReturn ---- @field func_id number The id of the async function to be called +--- @field func_id string The id of the async function to be called --- @field args any[] The arguments to call the function with --- @field tick number? If present, the function will be called on this game tick ---- @field next_id number? The id of the async function to be called with the return value +--- @field next_id string? The id of the async function to be called with the return value --- @field canceled boolean? True if the call has been canceled --- @field completed boolean? True if the call has completed --- @field return_values any? The return values of the function call From ab89b9feca56df8c58921b292fb649da0e61f104 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:28:45 +0000 Subject: [PATCH 06/46] Type vlayer forward declarations and config entries The gui element locals are declared at the top and assigned far below, so emmylua inferred them as nil at every use site. circuit_oc was reassigned from a control behaviour to a logistic section, which hid every section method behind the wrong type. allowed_items entries carry heterogeneous optional properties, now described by a class. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/config/vlayer.lua | 14 ++++++++++++++ exp_legacy/module/modules/control/vlayer.lua | 16 ++++++++-------- exp_legacy/module/modules/gui/vlayer.lua | 8 ++++---- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/exp_legacy/module/config/vlayer.lua b/exp_legacy/module/config/vlayer.lua index d80ed827..c2e0761a 100644 --- a/exp_legacy/module/config/vlayer.lua +++ b/exp_legacy/module/config/vlayer.lua @@ -35,6 +35,20 @@ return { storage_output = 1, -- >0 allows for item teleportation (allowed_items only) }, + --- @class Vlayer.ItemProperties + --- @field starting_value number + --- @field required_area number + --- @field production number? + --- @field discharge number? + --- @field capacity number? + --- @field surface_area number? + --- @field fuel_value number? + --- @field power boolean? + --- @field modded boolean? Set when the item is registered from a modded equivalent + --- @field base_game_equivalent string? + --- @field multiplier number? + + --- @type table allowed_items = { --- @setting allowed_items List of all items allowed in vlayer storage and their properties --[[ Allowed properties: diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index 39346063..37718244 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -14,10 +14,10 @@ local mega = 1000000 local vlayer = {} local vlayer_data = { entity_interfaces = { - energy = {}, - circuit = {}, - storage_input = {}, - storage_output = {}, + energy = {}, --- @type LuaEntity[] + circuit = {}, --- @type LuaEntity[] + storage_input = {}, --- @type LuaEntity[] + storage_output = {}, --- @type LuaEntity[] }, properties = { total_surface_area = 0, @@ -582,11 +582,11 @@ local function handle_circuit_interfaces() if not interface.valid then vlayer_data.entity_interfaces.circuit[index] = nil else - local circuit_oc = interface.get_or_create_control_behavior() - if circuit_oc.sections_count == 0 then - circuit_oc.add_section() + local control = interface.get_or_create_control_behavior() + if control.sections_count == 0 then + control.add_section() end - circuit_oc = circuit_oc.sections[1] + local circuit_oc = control.sections[1] local signal_index = 1 local circuit = vlayer.get_circuits() diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua index a57f090a..47d1abe1 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -22,9 +22,9 @@ local function aabb_align_expand(aabb) } end -local vlayer_container -local vlayer_gui_control_type -local vlayer_gui_control_list +local vlayer_container --- @type ExpElement +local vlayer_gui_control_type --- @type ExpElement +local vlayer_gui_control_list --- @type ExpElement local vlayer_control_type_list = { [1] = "energy", @@ -87,7 +87,7 @@ SelectArea:on_selection(function(event) local container = Gui.get_left_element(vlayer_container, player) local disp = container.frame["vlayer_st_2"].disp.table local target = vlayer_control_type_list[disp[vlayer_gui_control_type.name].selected_index] - local entities + local entities --- @type LuaEntity[] if config.power_on_space and event.surface and event.surface.platform and target == "energy" then if player.force.technologies[config.power_on_space_research.name].level >= config.power_on_space_research.level then From ecf236d831112f0b985b6ac1130e5a14a236f18c Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:30:00 +0000 Subject: [PATCH 07/46] Describe the Datastore type The registry and the prototype were both bare tables, so every `datastore:method()` call resolved to nothing. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/expcore/datastore.lua | 14 +++++++++++++- exp_legacy/module/modules/control/vlayer.lua | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/exp_legacy/module/expcore/datastore.lua b/exp_legacy/module/expcore/datastore.lua index 9d498787..07fe28d5 100644 --- a/exp_legacy/module/expcore/datastore.lua +++ b/exp_legacy/module/expcore/datastore.lua @@ -149,7 +149,19 @@ PlayerData.Statistics:combine('JoinCount') local Storage = require("modules/exp_util/storage") local DatastoreManager = {} -local Datastores = {} +local Datastores = {} --- @type table +--- @class Datastore +--- @field name string +--- @field value_name string +--- @field auto_save boolean +--- @field save_to_disk boolean +--- @field propagate_changes boolean +--- @field serializer function | false +--- @field parent Datastore | false +--- @field children table +--- @field metadata table +--- @field events table +--- @field data table local Datastore = {} local Data = {} local copy = table.deep_copy diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index 37718244..d70b15f8 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -582,7 +582,7 @@ local function handle_circuit_interfaces() if not interface.valid then vlayer_data.entity_interfaces.circuit[index] = nil else - local control = interface.get_or_create_control_behavior() + local control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]] if control.sections_count == 0 then control.add_section() end From 2032cadc1b7e4871fc8d48636c379ac38f0060ca Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:33:27 +0000 Subject: [PATCH 08/46] Fix rocket info silo pruning and unit number keys The prune loop shadowed its own parameter, so `silo_data[unit_number] = nil` wrote into the entry instead of removing it from the table. `unit_number` is optional on LuaEntity but always set for a silo. Note that `--[[ @as ]]` does not narrow for an index expression, only `@cast` does. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/gui/rocket_info.lua | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/exp_scenario/module/gui/rocket_info.lua b/exp_scenario/module/gui/rocket_info.lua index c377155a..99ac30c2 100644 --- a/exp_scenario/module/gui/rocket_info.lua +++ b/exp_scenario/module/gui/rocket_info.lua @@ -404,9 +404,9 @@ end --- @return ExpGui_RocketInfo.elements.progress_table.row_data[] function Elements.progress_table.calculate_row_data_all(silo_data) local row_data = {} - for unit_number, silo_data in pairs(silo_data) do - if silo_data.entity.valid then - row_data[unit_number] = Elements.progress_table.calculate_row_data(silo_data) + for unit_number, silo in pairs(silo_data) do + if silo.entity.valid then + row_data[unit_number] = Elements.progress_table.calculate_row_data(silo) else -- Prune silos that are no longer valid silo_data[unit_number] = nil @@ -433,7 +433,9 @@ function Elements.progress_table.add_row(progress_table, row_data) progress.style.padding = { 0, 2 } progress.style.font_color = row_data.color - rows[row_data.entity.unit_number] = { x = x, y = y, progress = progress } + local unit_number = row_data.entity.unit_number + --- @cast unit_number uint + rows[unit_number] = { x = x, y = y, progress = progress } end --- Remove a silo row from the progress table @@ -455,7 +457,9 @@ end --- @param row_data ExpGui_RocketInfo.elements.progress_table.row_data function Elements.progress_table.refresh_row(progress_table, row_data) local element_data = Elements.progress_table.data[progress_table] - local row = element_data.rows[row_data.entity.unit_number] + local unit_number = row_data.entity.unit_number + --- @cast unit_number uint + local row = element_data.rows[unit_number] row.x.caption = row_data.x row.y.caption = row_data.y row.progress.caption = row_data.caption @@ -620,7 +624,9 @@ end function Elements.container.add_silo(entity) local force = entity.force --[[ @as LuaForce ]] local silos = Elements.container._get_force_data(force).silos - silos[entity.unit_number] = { + local unit_number = entity.unit_number + --- @cast unit_number uint + silos[unit_number] = { entity = entity, launched = 0, awaiting_reset = false, @@ -632,7 +638,9 @@ end function Elements.container.increment_silo(entity) local force = entity.force --[[ @as LuaForce ]] local silos = Elements.container._get_force_data(force).silos - local silo_data = silos[entity.unit_number] + local unit_number = entity.unit_number + --- @cast unit_number uint + local silo_data = silos[unit_number] if not silo_data then return end silo_data.launched = silo_data.launched + 1 silo_data.awaiting_reset = true @@ -653,7 +661,8 @@ Gui.toolbar.create_button{ --- Record the launch and update the stats when a cargo pod finishes ascending --- @param event EventData.on_cargo_pod_finished_ascending local function on_cargo_pod_finished_ascending(event) - local force = event.cargo_pod.force --[[ @as LuaForce ]] + local force = event.cargo_pod.force + --- @cast force LuaForce local rockets_launched = force.rockets_launched -- Update the launch stats for the force @@ -669,6 +678,7 @@ local function on_cargo_pod_finished_ascending(event) -- Append the launch tick into the times array local times = Elements.container.get_launch_times(force) + --- @cast rockets_launched uint times[rockets_launched] = event.tick -- Discard the launch time that is no longer needed by any rolling average unless it is a milestone From 8ae11445853523edb3cb15d23f4700acc4262227 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:34:54 +0000 Subject: [PATCH 09/46] Fix logistic network stats for the 2.0 api `get_contents` and `targeted_items_pickup`/`_deliver` return lists of ItemWithQualityCount in 2.0, they are no longer name keyed dictionaries, so the logistorage stats were indexing by array position. PreferenceEnum is bidirectional, matching what ExpUtil.enum returns. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/expcore/player_data.lua | 2 +- exp_legacy/module/modules/graftorio/forcestats.lua | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/exp_legacy/module/expcore/player_data.lua b/exp_legacy/module/expcore/player_data.lua index b6862873..07318191 100644 --- a/exp_legacy/module/expcore/player_data.lua +++ b/exp_legacy/module/expcore/player_data.lua @@ -55,7 +55,7 @@ PlayerData:set_serializer(Datastore.name_serializer) -- use player name --- Store and enum for the data saving preference local DataSavingPreference = PlayerData:combine("DataSavingPreference") -local PreferenceEnum = { "All", "Statistics", "Settings", "Required" } +local PreferenceEnum = { "All", "Statistics", "Settings", "Required" } --- @type table for k, v in ipairs(PreferenceEnum) do PreferenceEnum[v] = k end DataSavingPreference:set_default("All") diff --git a/exp_legacy/module/modules/graftorio/forcestats.lua b/exp_legacy/module/modules/graftorio/forcestats.lua index a515869a..195f2e4c 100644 --- a/exp_legacy/module/modules/graftorio/forcestats.lua +++ b/exp_legacy/module/modules/graftorio/forcestats.lua @@ -126,19 +126,19 @@ lib.collect_loginet = function() end if config.modules.logistorage then - for name, v in pairs(network.get_contents()) do - stats.items[name] = (stats.items[name] or 0) + v + for _, item in pairs(network.get_contents()) do + stats.items[item.name] = (stats.items[item.name] or 0) + item.count end -- pickups and deliveries of items for _, point_list in pairs{ network.provider_points, network.requester_points, network.storage_points } do for _, point in pairs(point_list) do - for name, qty in pairs(point.targeted_items_pickup) do - stats.pickups[name] = (stats.pickups[name] or 0) + qty + for _, item in pairs(point.targeted_items_pickup) do + stats.pickups[item.name] = (stats.pickups[item.name] or 0) + item.count end - for name, qty in pairs(point.targeted_items_deliver) do - stats.deliveries[name] = (stats.deliveries[name] or 0) + qty + for _, item in pairs(point.targeted_items_deliver) do + stats.deliveries[item.name] = (stats.deliveries[item.name] or 0) + item.count end end end From fd2f5a95893fdfb9438c477927b5856f7bc223e0 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:36:59 +0000 Subject: [PATCH 10/46] Type the player bonus element data and config `data` on an element is the GuiData store keyed by element, not the value being stored. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/gui/player_bonus.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index e5c9ff8a..0af5b4aa 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -22,13 +22,16 @@ local Elements = {} --- @field _cost_scale number --- For perf calculate the division of scale against cost ahead of time -for _, bonus_data in pairs(config.player_bonus) do +--- @type table +local player_bonus = config.player_bonus + +for _, bonus_data in pairs(player_bonus) do bonus_data._cost_scale = bonus_data.cost / bonus_data.scale end --- Progress bar which displays how much of a bonus has been used --- @class ExpGui_PlayerBonus.elements.bonus_used: ExpElement ---- @field data number +--- @field data table --- @overload fun(parent: LuaGuiElement): LuaGuiElement Elements.bonus_used = Gui.define("player_bonus/bonus_used") :track_all_elements() @@ -441,7 +444,7 @@ end function Elements.container.calculate_cost(player) local cost = 0 local player_data = Elements.container.data[player] - for _, bonus_data in pairs(config.player_bonus) do + for _, bonus_data in pairs(player_bonus) do cost = cost + (player_data[bonus_data.name] or 0) * bonus_data._cost_scale end return cost From 164672602dd5500e85005423dcaf45766b53d78a Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:39:18 +0000 Subject: [PATCH 11/46] Make the @as casts actually apply emmylua only parses the inline cast as `--[[@as T]]`, the spaced form `--[[ @as T ]]` is treated as a plain comment, so all 149 of them were doing nothing. LuaGuiElement.style is a union because a style name can be assigned to it, so reading it back needs the cast. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/module_exports.lua | 2 +- exp_gui/module/data.lua | 2 +- exp_gui/module/elements.lua | 24 +++++----- exp_gui/module/prototype.lua | 4 +- exp_gui/module/toolbar.lua | 12 ++--- .../module/config/gui/player_list_actions.lua | 2 +- .../module/expcore/permission_groups.lua | 2 +- exp_legacy/module/modules/data/toolbar.lua | 2 +- exp_legacy/module/modules/gui/warp-list.lua | 4 +- exp_roles/module/control.lua | 4 +- exp_scenario/module/commands/artillery.lua | 2 +- exp_scenario/module/commands/lawnmower.lua | 2 +- exp_scenario/module/commands/surface.lua | 2 +- exp_scenario/module/commands/teleport.lua | 8 ++-- exp_scenario/module/commands/trains.lua | 2 +- exp_scenario/module/commands/waterfill.lua | 2 +- .../module/control/fast_deconstruction.lua | 2 +- exp_scenario/module/gui/autofill.lua | 16 +++---- exp_scenario/module/gui/elements.lua | 2 +- exp_scenario/module/gui/module_inserter.lua | 12 ++--- exp_scenario/module/gui/player_bonus.lua | 14 +++--- exp_scenario/module/gui/player_list.lua | 18 ++++---- exp_scenario/module/gui/player_stats.lua | 6 +-- exp_scenario/module/gui/production_stats.lua | 8 ++-- exp_scenario/module/gui/quick_actions.lua | 2 +- exp_scenario/module/gui/readme.lua | 26 ++++++----- .../module/gui/research_milestones.lua | 12 ++--- exp_scenario/module/gui/rocket_info.lua | 26 +++++------ .../module/gui/science_production.lua | 21 ++++----- exp_scenario/module/gui/surveillance.lua | 12 ++--- exp_scenario/module/gui/task_list.lua | 44 +++++++++---------- exp_server_ups/module/control.lua | 2 +- 32 files changed, 151 insertions(+), 148 deletions(-) diff --git a/exp_commands/module/module_exports.lua b/exp_commands/module/module_exports.lua index 44542a19..7dcfc0e2 100644 --- a/exp_commands/module/module_exports.lua +++ b/exp_commands/module/module_exports.lua @@ -159,7 +159,7 @@ Commands.server = setmetatable({ Commands.error("Command does not support rcon usage, requires LuaPlayer." .. key) error("Command does not support rcon usage, requires LuaPlayer." .. key) end, -}) --[[ @as LuaPlayer ]] +}) --[[@as LuaPlayer]] --- Status Returns. -- Return values used by command callbacks diff --git a/exp_gui/module/data.lua b/exp_gui/module/data.lua index dffe6e64..d57c8c99 100644 --- a/exp_gui/module/data.lua +++ b/exp_gui/module/data.lua @@ -154,7 +154,7 @@ end --- @param scope string --- @return GuiData function GuiData.get(scope) - return GuiData._scopes[scope] --[[ @as GuiData ]] + return GuiData._scopes[scope] --[[@as GuiData]] end --- Used to clean up data from destroyed elements diff --git a/exp_gui/module/elements.lua b/exp_gui/module/elements.lua index 368e9f4e..8f1ccb4e 100644 --- a/exp_gui/module/elements.lua +++ b/exp_gui/module/elements.lua @@ -28,7 +28,7 @@ Elements.aligned_flow = Gui.define("aligned_flow") vertically_stretchable = vertical_align ~= "center", horizontally_stretchable = horizontal_align ~= "center", } - end) --[[ @as any ]] + end) --[[@as any]] --- A solid horizontal white bar element --- @class Gui.elements.bar: ExpElement @@ -48,7 +48,7 @@ Elements.bar = Gui.define("bar") else style.horizontally_stretchable = true end - end) --[[ @as any ]] + end) --[[@as any]] --- A label which is centered --- @class Gui.elements.centered_label: ExpElement @@ -63,7 +63,7 @@ Elements.centered_label = Gui.define("centered_label") horizontal_align = "center", single_line = false, width = Gui.from_argument(1), - } --[[ @as any ]] + } --[[@as any]] --- A label which has two white bars on either side of it --- @class Gui.elements.title_label: ExpElement @@ -91,7 +91,7 @@ Elements.title_label = Gui.define("title_label") Elements.bar(flow) return label - end) --[[ @as any ]] + end) --[[@as any]] --- A fixed size vertical scroll pane --- @class Gui.elements.scroll_pane: ExpElement @@ -109,7 +109,7 @@ Elements.scroll_pane = Gui.define("scroll_pane") padding = { 1, 3 }, maximal_height = Gui.from_argument(1), horizontally_stretchable = true, - } --[[ @as any ]] + } --[[@as any]] --- A fixed size vertical scroll pane containing a table --- @class Gui.elements.scroll_table: ExpElement @@ -132,7 +132,7 @@ Elements.scroll_table = Gui.define("scroll_table") cell_padding = 1, vertical_align = "center", horizontally_stretchable = true, - } --[[ @as any ]] + } --[[@as any]] --- A container frame --- @class Gui.elements.container: ExpElement @@ -159,7 +159,7 @@ Elements.container = Gui.define("container") vertically_stretchable = false, horizontally_stretchable = false, minimal_width = Gui.from_argument(1), - } --[[ @as any ]] + } --[[@as any]] --- Get the root element of a container --- @param container LuaGuiElement @@ -183,7 +183,7 @@ Elements.subframe_base = Gui.define("container_subframe") padding = { 3, 6, 0, 6 }, use_header_filler = false, horizontally_stretchable = true, - } --[[ @as any ]] + } --[[@as any]] --- @class Gui.elements.header.opts --- @field name string? @@ -212,7 +212,7 @@ Elements.header = Gui.define("container_header") subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true return subframe - end) --[[ @as any ]] + end) --[[@as any]] --- @class Gui.elements.footer.opts --- @field name string? @@ -241,7 +241,7 @@ Elements.footer = Gui.define("container_footer") subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true return subframe - end) --[[ @as any ]] + end) --[[@as any]] --- A button used to destroy its target when clicked, intended for screen frames --- @class Gui.elements.screen_frame_close: ExpElement @@ -259,7 +259,7 @@ Elements.screen_frame_close = Gui.define("screen_frame_close") :on_click(function(def, player, element, event) --- @cast def Gui.elements.screen_frame_close Gui.destroy_if_valid(def.data[element]) - end) --[[ @as any ]] + end) --[[@as any]] --- A draggable frame with close button and button flow --- @class Gui.elements.screen_frame: ExpElement @@ -313,7 +313,7 @@ Elements.screen_frame = Gui.define("screen_frame") direction = "vertical", style = "inside_shallow_frame_packed", } - end) --[[ @as any ]] + end) --[[@as any]] --- Get the button flow for a screen frame --- @param screen_frame LuaGuiElement diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua index ea1e6a31..5b261ecd 100644 --- a/exp_gui/module/prototype.lua +++ b/exp_gui/module/prototype.lua @@ -158,7 +158,7 @@ function ExpElement.new(name) }, } - ExpElement._elements[element_name] = instance --[[ @as ExpElement ]] + ExpElement._elements[element_name] = instance --[[@as ExpElement]] return setmetatable(instance, ExpElement._metatable) end @@ -460,7 +460,7 @@ function ExpElement._prototype:raise_event(event) for _, handler in ipairs(handlers) do -- All gui elements will contain player and element, other events might have these as nil -- Therefore only the signature of on_event has these values as optional - handler(self, player --[[ @as LuaPlayer ]], event.element, event --[[ @as EventData ]]) + handler(self, player --[[@as LuaPlayer]], event.element, event --[[@as EventData]]) end end diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua index 69cc68ff..f22c4cc7 100644 --- a/exp_gui/module/toolbar.lua +++ b/exp_gui/module/toolbar.lua @@ -85,7 +85,7 @@ function Toolbar.set_button_toggled_state(define, player, state, _from_left) button.style = state and toolbar_button_active_style or toolbar_button_default_style -- Make the extra required adjustments - local style = button.style + local style = button.style --[[@as LuaStyle]] style.minimal_width = original_width style.maximal_height = original_height if button.type == "sprite-button" then @@ -393,7 +393,7 @@ end --- @param player LuaPlayer --- @param order Gui.ToolbarOrder function Toolbar.set_order(player, order) - local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]] local left_flow = Gui.get_left_flow(player) local top_flow = Gui.get_top_flow(player) @@ -466,7 +466,7 @@ end function Toolbar.get_state(player) -- Get the order of toolbar buttons local order = {} - local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]] for index, item in pairs(list.children) do order[index] = { name = item.name, favourite = elements.toolbar_list_item.data[item].set_favourite.state } end @@ -487,7 +487,7 @@ end --- @param player LuaPlayer function Toolbar._create_elements(player) -- Add any missing items to the gui - local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + local toolbar_list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]] local previous_last_index = #toolbar_list.children_names for define in pairs(Gui.top_elements) do if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then @@ -513,7 +513,7 @@ end --- @param player LuaPlayer function Toolbar._ensure_consistency(player) -- Update the toolbar buttons - local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]] + local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]] for _, button in ipairs(toolbar_buttons) do -- Update the visible state based on if the player is allowed the button local element = Gui.get_top_element(button, player) @@ -658,7 +658,7 @@ elements.set_favourite = Gui.define("set_favourite") width = 180, } :on_checked_state_changed(function(def, player, element) - local define = ExpElement.get(element.tags.element_name --[[ @as string ]]) + local define = ExpElement.get(element.tags.element_name --[[@as string]]) local top_element = Gui.get_top_element(define, player) local had_visible = Toolbar.has_visible_buttons(player) top_element.visible = element.state diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_legacy/module/config/gui/player_list_actions.lua index f692b47f..c7cb7919 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_legacy/module/config/gui/player_list_actions.lua @@ -31,7 +31,7 @@ end -- gets the action player and a coloured name for the action to be used on local function get_action_player(player) - local selected_player = get_selected_player(player) --[[ @as LuaPlayer ]] + local selected_player = get_selected_player(player) --[[@as LuaPlayer]] local selected_player_color = format_player_name(selected_player) return selected_player, selected_player_color end diff --git a/exp_legacy/module/expcore/permission_groups.lua b/exp_legacy/module/expcore/permission_groups.lua index 6df8f468..edb0a315 100644 --- a/exp_legacy/module/expcore/permission_groups.lua +++ b/exp_legacy/module/expcore/permission_groups.lua @@ -142,7 +142,7 @@ group:set_action('toggle_map_editor', false) ]] function PermissionsGroups._prototype:set_action(action, state) - local input_action = defines.input_action[action] --[[ @as defines.input_action? ]] + local input_action = defines.input_action[action] --[[@as defines.input_action?]] if input_action == nil then input_action = action end assert(type(input_action) == "number", tostring(action) .. " is not a valid input action") self.actions[input_action] = state diff --git a/exp_legacy/module/modules/data/toolbar.lua b/exp_legacy/module/modules/data/toolbar.lua index a714ae0b..177aba50 100644 --- a/exp_legacy/module/modules/data/toolbar.lua +++ b/exp_legacy/module/modules/data/toolbar.lua @@ -18,7 +18,7 @@ ToolbarState:on_load(function(player_name, value) local decompressed = helpers.json_to_table(assert(helpers.decode_string(value), "Failed String Decode")) local player = assert(game.get_player(player_name)) - Gui.toolbar.set_state(player, decompressed --[[ @as Gui.ToolbarState ]]) + Gui.toolbar.set_state(player, decompressed --[[@as Gui.ToolbarState]]) return nil -- We don't save the state, use Gui.toolbar.get_state end) diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index 39759287..49a75e27 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -273,7 +273,7 @@ local warp_textfield = Gui.define("warp_textfield") :on_confirmed(function(def, player, element) local warp_id = element.parent.caption local warp_name = element.text - local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[ @as SignalID ]] + local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[@as SignalID]] if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) @@ -293,7 +293,7 @@ local confirm_edit_button = Gui.define("confirm_edit_button") :on_click(function(def, player, element) local warp_id = element.parent.caption local warp_name = element.parent.parent["name-" .. warp_id][warp_textfield.name].text - local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[ @as SignalID ]] + local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[@as SignalID]] if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) diff --git a/exp_roles/module/control.lua b/exp_roles/module/control.lua index 93c5cd37..652921e0 100644 --- a/exp_roles/module/control.lua +++ b/exp_roles/module/control.lua @@ -284,7 +284,7 @@ local server_role = setmetatable({ --- @param player LuaPlayer | string | nil --- @return ExpRoles.Role[] function ExpRoles.get_player_roles(player) - local player_name = type(player) == "table" and player.name or player --[[ @as string? ]] + local player_name = type(player) == "table" and player.name or player --[[@as string?]] -- The server is not a player and is allowed to do anything if player_name == nil then return { server_role } end @@ -678,7 +678,7 @@ end --- @param silent boolean? --- @param sync boolean local function change_player_roles(player, roles, change_type, by_player_name, silent, sync) - local player_name = type(player) == "table" and player.name or player --[[ @as string? ]] + local player_name = type(player) == "table" and player.name or player --[[@as string?]] if not player_name then return end local role_objects = resolve_roles(roles) diff --git a/exp_scenario/module/commands/artillery.lua b/exp_scenario/module/commands/artillery.lua index 95749da9..04c94fe3 100644 --- a/exp_scenario/module/commands/artillery.lua +++ b/exp_scenario/module/commands/artillery.lua @@ -43,7 +43,7 @@ commands.artillery = Commands.new("artillery", { "exp-commands_artillery.descrip end SelectArea:start(player) return Commands.status.success{ "exp-commands_artillery.enter" } - end) --[[ @as any ]] + end) --[[@as any]] --- when an area is selected to add protection to the area SelectArea:on_selection(function(event) diff --git a/exp_scenario/module/commands/lawnmower.lua b/exp_scenario/module/commands/lawnmower.lua index 5ff1d2e1..b77014e3 100644 --- a/exp_scenario/module/commands/lawnmower.lua +++ b/exp_scenario/module/commands/lawnmower.lua @@ -22,7 +22,7 @@ commands.lawnmower = Commands.new("lawnmower", { "exp-commands_lawnmower.descrip SelectArea:start(player) return Commands.status.success{ "exp-commands_lawnmower.enter" } - end) --[[ @as any ]] + end) --[[@as any]] --- When an area is selected to be handled SelectArea:on_selection(function(event) diff --git a/exp_scenario/module/commands/surface.lua b/exp_scenario/module/commands/surface.lua index 18504724..0cb2bc43 100644 --- a/exp_scenario/module/commands/surface.lua +++ b/exp_scenario/module/commands/surface.lua @@ -69,7 +69,7 @@ commands.clear_blueprints = Commands.new("clear-blueprints", { "exp-commands_sur SelectArea:start(player) return Commands.status.success{ "exp-commands_surface.enter" } - end) --[[ @as any ]] + end) --[[@as any]] --- When an area is selected SelectArea:on_selection(function(event) diff --git a/exp_scenario/module/commands/teleport.lua b/exp_scenario/module/commands/teleport.lua index 964e8ed7..a7892047 100644 --- a/exp_scenario/module/commands/teleport.lua +++ b/exp_scenario/module/commands/teleport.lua @@ -34,7 +34,7 @@ commands.teleport = Commands.new("teleport", { "exp-commands_teleport.descriptio elseif not teleport_player(other_player, target_player.physical_surface, target_player.physical_position) then return Commands.status.error{ "exp-commands_teleport.unavailable" } end - end) --[[ @as any ]] + end) --[[@as any]] --- Teleports a player to you. --- @class ExpCommands_Teleport.commands.bring: ExpCommand @@ -49,7 +49,7 @@ commands.bring = Commands.new("bring", { "exp-commands_teleport.description-brin elseif not teleport_player(other_player, player.physical_surface, player.physical_position) then return Commands.status.error{ "exp-commands_teleport.unavailable" } end - end) --[[ @as any ]] + end) --[[@as any]] --- Teleports you to a player. --- @class ExpCommands_Teleport.commands.goto: ExpCommand @@ -64,7 +64,7 @@ commands["goto"] = Commands.new("goto", { "exp-commands_teleport.description-got elseif not teleport_player(player, other_player.physical_surface, other_player.physical_position) then return Commands.status.error{ "exp-commands_teleport.unavailable" } end - end) --[[ @as any ]] + end) --[[@as any]] --- Teleport to spawn --- @class ExpCommands_Teleport.commands.spawn: ExpCommand @@ -92,7 +92,7 @@ commands.spawn = Commands.new("spawn", { "exp-commands_teleport.description-spaw else return Commands.status.unauthorised() end - end) --[[ @as any ]] + end) --[[@as any]] return { commands = commands, diff --git a/exp_scenario/module/commands/trains.lua b/exp_scenario/module/commands/trains.lua index eb3a3144..9d79b755 100644 --- a/exp_scenario/module/commands/trains.lua +++ b/exp_scenario/module/commands/trains.lua @@ -32,7 +32,7 @@ commands.set_trains_to_automatic = Commands.new("set-trains-to-automatic", { "ex end game.print{ "exp-commands_trains.response", format_player_name(player), format_number(#trains, false) } - end) --[[ @as any ]] + end) --[[@as any]] return { commands = commands, diff --git a/exp_scenario/module/commands/waterfill.lua b/exp_scenario/module/commands/waterfill.lua index 6e1a8e24..6d9cc9d2 100644 --- a/exp_scenario/module/commands/waterfill.lua +++ b/exp_scenario/module/commands/waterfill.lua @@ -36,7 +36,7 @@ commands.waterfill = Commands.new("waterfill", { "exp-commands_waterfill.descrip SelectArea:start(player) return Commands.status.success{ "exp-commands_waterfill.enter" } end - end) --[[ @as any ]] + end) --[[@as any]] --- When an area is selected to be converted to water SelectArea:on_selection(function(event) diff --git a/exp_scenario/module/control/fast_deconstruction.lua b/exp_scenario/module/control/fast_deconstruction.lua index 5470afb6..85a825f9 100644 --- a/exp_scenario/module/control/fast_deconstruction.lua +++ b/exp_scenario/module/control/fast_deconstruction.lua @@ -84,7 +84,7 @@ local function get_player_cache(player_index) cache.tick = game.tick cache.player_index = player_index cache.player = player - cache.force = player.force --[[ @as LuaForce ]] + cache.force = player.force --[[@as LuaForce]] cache.tree_count = 0 cache.trees = {} cache.permission = get_permission(player) diff --git a/exp_scenario/module/gui/autofill.lua b/exp_scenario/module/gui/autofill.lua index c973a256..9662b3f9 100644 --- a/exp_scenario/module/gui/autofill.lua +++ b/exp_scenario/module/gui/autofill.lua @@ -49,7 +49,7 @@ Elements.toggle_section_button = Gui.define("autofill/toggle_section_button") element.sprite = "utility/expand" element.tooltip = { "exp-gui_autofill.tooltip-toggle-section-expand" } end - end) --[[ @as any ]] + end) --[[@as any]] --- Toggle if an entity will be autofilled when played --- @class ExpGui_Autofill.elements.toggle_entity_button: ExpElement @@ -88,7 +88,7 @@ Elements.toggle_entity_button = Gui.define("autofill/toggle_entity_button") style.padding = 0 style.height = 22 style.width = 22 - end) --[[ @as any ]] + end) --[[@as any]] --- Toggle if an item will be inserted into an entity --- @class ExpGui_Autofill.elements.toggle_item_button: ExpElement @@ -127,7 +127,7 @@ Elements.toggle_item_button = Gui.define("autofill/toggle_item_button") style.padding = -2 style.height = 32 style.width = 32 - end) --[[ @as any ]] + end) --[[@as any]] --- The amount of an item to insert --- @class ExpGui_Autofill.elements.amount_textfield: ExpElement @@ -165,7 +165,7 @@ Elements.amount_textfield = Gui.define("autofill/amount_textfield") element.text = tostring(clamped) player.print{ "exp-gui_autofill.invalid", clamped, rich_img("item", item_settings.name), rich_img("entity", item_settings.entity) } end - end) --[[ @as any ]] + end) --[[@as any]] --- A disabled version of the autofill settings used as a filler Elements.disabled_autofill_setting = Gui.define("autofill/empty_autofill_setting") @@ -221,9 +221,9 @@ Elements.section = Gui.define("autofill/section") end) :on_click(function(def, player, element, event) --- @cast def ExpGui_Autofill.elements.section - event.element = def.data[element] --[[ @as LuaGuiElement ]] + event.element = def.data[element] --[[@as LuaGuiElement]] Elements.toggle_section_button:raise_event(event) - end) --[[ @as any ]] + end) --[[@as any]] --- Add an item category to a section, at most three can exist --- @param section LuaGuiElement @@ -238,7 +238,7 @@ function Elements.section.add_category(section, category_name) category.style.vertical_spacing = 1 local ctn = 0 - local entity_settings = Elements.section.data[section] --[[ @as ExpGui_Autofill.entity_settings ]] + local entity_settings = Elements.section.data[section] --[[@as ExpGui_Autofill.entity_settings]] for _, item_data in pairs(entity_settings.items) do if item_data.category == category_name then Elements.toggle_item_button(category, item_data) @@ -312,7 +312,7 @@ Elements.container = Gui.define("autofill/container") end return container.parent - end) --[[ @as any ]] + end) --[[@as any]] --- Get the autofill settings for a player --- @param player LuaPlayer diff --git a/exp_scenario/module/gui/elements.lua b/exp_scenario/module/gui/elements.lua index 151bdfef..f399c82d 100644 --- a/exp_scenario/module/gui/elements.lua +++ b/exp_scenario/module/gui/elements.lua @@ -22,7 +22,7 @@ Elements.online_player_dropdown = Gui.define("player_dropdown") end) :style{ height = 24, - } --[[ @as any ]] + } --[[@as any]] --- To help with caching and avoid context changes the player list from the previous update is remembered do local _player_names = {} --- @type (string?)[] diff --git a/exp_scenario/module/gui/module_inserter.lua b/exp_scenario/module/gui/module_inserter.lua index a39bd391..68a72023 100644 --- a/exp_scenario/module/gui/module_inserter.lua +++ b/exp_scenario/module/gui/module_inserter.lua @@ -78,7 +78,7 @@ Elements.create_selection_planner = Gui.define("module_inserter/create_selection :on_click(function(def, player, element) --- @cast def ExpGui_ModuleInserter.elements.create_selection_planner SelectArea:start(player, def.data[element]) - end) --[[ @as any ]] + end) --[[@as any]] --- Used to select the machine to apply modules to --- @class ExpGui_ModuleInserter.elements.machine_selector: ExpElement @@ -98,7 +98,7 @@ Elements.machine_selector = Gui.define("module_inserter/machine_selector") :on_elem_changed(function(def, player, element, event) --- @cast def ExpGui_ModuleInserter.elements.machine_selector local element_data = def.data[element] - local machine_name = element.elem_value --[[ @as string? ]] + local machine_name = element.elem_value --[[@as string?]] if not machine_name then if element_data.on_last_row then Elements.module_table.reset_row(element_data.module_table, element) @@ -112,7 +112,7 @@ Elements.machine_selector = Gui.define("module_inserter/machine_selector") Elements.module_table.add_row(element_data.module_table) end end - end) --[[ @as any ]] + end) --[[@as any]] --- Used to select the modules to be applied Elements.module_selector = Gui.define("module_inserter/module_selector") @@ -140,7 +140,7 @@ Elements.module_table = Gui.define("module_inserter/module_table") local slots_per_row = config.module_slots_per_row + 1 return Gui.elements.scroll_table(parent, 280, slots_per_row) end) - :element_data{} --[[ @as any ]] + :element_data{} --[[@as any]] --- Get all the rows in a module table --- @param module_table LuaGuiElement @@ -334,7 +334,7 @@ SelectArea:on_selection(function(event, module_table) } for _, row in pairs(Elements.module_table.get_rows(module_table)) do - local machine_name = row.machine_selector.elem_value --[[ @as string? ]] + local machine_name = row.machine_selector.elem_value --[[@as string?]] if not machine_name then goto continue end @@ -349,7 +349,7 @@ SelectArea:on_selection(function(event, module_table) -- Get all the modules selected for i = 1, entity_prototype.module_inventory_size do local module_selector = module_selectors[i] - local module = module_selector.elem_value --[[ @as { name: string, quality: string }? ]] + local module = module_selector.elem_value --[[@as { name: string, quality: string }?]] if module then -- Module selected, add it the module arrays local no_prod_name = module.name:gsub("productivity", "efficiency") diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index 0af5b4aa..054c3088 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -47,7 +47,7 @@ Elements.bonus_used = Gui.define("player_bonus/bonus_used") font = "heading-2", color = { 1, 0, 0 }, } - :element_data(0) --[[ @as any ]] + :element_data(0) --[[@as any]] --- Value is cached to save perf do local _points_limit = {} --- @type table @@ -147,7 +147,7 @@ Elements.reset_button = Gui.define("player_bonus/reset_button") Elements.bonus_table.reset_sliders(element_data.bonus_table) local bonus_cost = Elements.bonus_table.calculate_cost(element_data.bonus_table) Elements.bonus_used.refresh(element_data.bonus_used, bonus_cost) - end) --[[ @as any ]] + end) --[[@as any]] --- Link an apply button to this reset button so that it will be disabled after being pressed --- @param reset_button LuaGuiElement @@ -188,7 +188,7 @@ Elements.apply_button = Gui.define("player_bonus/apply_button") Elements.bonus_table.save_sliders(element_data.bonus_table) Elements.container.apply_player_bonus(player) end - end) --[[ @as any ]] + end) --[[@as any]] --- Link an apply button to this reset button so that it will be disabled after being pressed --- @param apply_button LuaGuiElement @@ -209,7 +209,7 @@ Elements.bonus_table_label = Gui.define("player_bonus/table_label") } :style{ width = Gui.from_argument(3, 70), - } --[[ @as any ]] + } --[[@as any]] --- @class ExpGui_PlayerBonus.elements.bonus_slider.elements --- @field bonus_used LuaGuiElement @@ -267,7 +267,7 @@ Elements.bonus_slider = Gui.define("player_bonus/bonus_slider") element_data.label.caption = Elements.bonus_slider.calculate_slider_caption(bonus_data, value) element_data.apply_button.enabled = Elements.bonus_used.update(element_data.bonus_used, value_change * bonus_data._cost_scale) element_data.reset_button.enabled = true - end) --[[ @as any ]] + end) --[[@as any]] --- Get the caption of the slider label --- @param bonus_data ExpGui_PlayerBonus.bonus_data @@ -313,7 +313,7 @@ Elements.bonus_table = Gui.define("player_bonus/bonus_table") :draw(function(_, parent) return Gui.elements.scroll_table(parent, 300, 3) end) - :element_data{} --[[ @as any ]] + :element_data{} --[[@as any]] --- Adds a row to the milestone table --- @param bonus_table LuaGuiElement @@ -380,7 +380,7 @@ Elements.container = Gui.define("player_bonus/container") Elements.bonus_used.refresh(elements.bonus_used, bonus_cost) return Gui.elements.container.get_root_element(container) - end) --[[ @as any ]] + end) --[[@as any]] --- Set the bonus value for a player --- @param player LuaPlayer diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index 3b5379b3..a5db97b1 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -48,14 +48,14 @@ Elements.open_action_bar = Gui.define("player_list/open_action_bar") --- @cast def ExpGui_PlayerList.elements.open_action_bar Elements.container.toggle_selected_player(player, def.data[element]) Elements.player_table.refresh_player(player) - end) --[[ @as any ]] + end) --[[@as any]] --- Set whether an open action bar button shows as highlighted --- @param open_button LuaGuiElement --- @param highlighted boolean function Elements.open_action_bar.set_highlight(open_button, highlighted) open_button.style = highlighted and "tool_button" or "frame_button" - local style = open_button.style + local style = open_button.style --[[@as LuaStyle]] style.padding = -2 style.width = 8 style.height = 14 @@ -80,7 +80,7 @@ Elements.close_action_bar = Gui.define("player_list/close_action_bar") :on_click(function(_, player) Elements.container.set_selected_player(player, nil) Elements.player_table.refresh_player(player) - end) --[[ @as any ]] + end) --[[@as any]] --- Button used to confirm a reason --- @class ExpGui_PlayerList.elements.reason_confirm: ExpElement @@ -109,7 +109,7 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") element.parent.entry.text = "" Elements.container.set_selected_player(player, nil) Elements.player_table.refresh_player(player) - end) --[[ @as any ]] + end) --[[@as any]] --- Clickable player name label, left click opens the map and right click toggles the action bar --- @class ExpGui_PlayerList.elements.player_name_label: ExpElement @@ -142,7 +142,7 @@ Elements.player_name_label = Gui.define("player_list/player_name_label") Elements.container.toggle_selected_player(player, selected_player) Elements.player_table.refresh_player(player) end - end) --[[ @as any ]] + end) --[[@as any]] --- @class ExpGui_PlayerList.elements.player_table.row --- @field open_button LuaGuiElement @@ -165,7 +165,7 @@ Elements.player_table = Gui.define("player_list/player_table") end) :style{ padding = { 1, 0, 1, 2 }, - } --[[ @as any ]] + } --[[@as any]] --- Store the action and reason bars associated with a player table --- @param player_table LuaGuiElement @@ -342,7 +342,7 @@ Elements.action_bar = Gui.define("player_list/action_bar") :style{ height = 35, padding = { 1, 3 }, - } --[[ @as any ]] + } --[[@as any]] --- Update the action bar buttons to match the selection for a player, hidden when nothing is selected --- @param action_bar LuaGuiElement @@ -390,7 +390,7 @@ Elements.reason_bar = Gui.define("player_list/reason_bar") :style{ height = 35, padding = { -1, 3 }, - } --[[ @as any ]] + } --[[@as any]] --- Update the reason bar visibility to match the selection, shown only while an action awaits a reason --- @param reason_bar LuaGuiElement @@ -420,7 +420,7 @@ Elements.container = Gui.define("player_list/container") Elements.player_table.rebuild(player_table, row_data) return Gui.elements.container.get_root_element(container) - end) --[[ @as any ]] + end) --[[@as any]] --- Get or create the selection state for a player --- @param player LuaPlayer diff --git a/exp_scenario/module/gui/player_stats.lua b/exp_scenario/module/gui/player_stats.lua index c615506d..b0a57df3 100644 --- a/exp_scenario/module/gui/player_stats.lua +++ b/exp_scenario/module/gui/player_stats.lua @@ -93,7 +93,7 @@ Elements.table_label = Gui.define("player_stats/table_label") } :style{ width = Gui.from_argument("width"), - } --[[ @as any ]] + } --[[@as any]] --- Data table that shows all data for a player --- @class ExpGui_PlayerStats.elements.player_stats_table: ExpElement @@ -139,7 +139,7 @@ Elements.player_stats_table = Gui.define("player_stats/data_table") def.data[data_table] = labels return data_table - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh a data table with the most recent stats for a player --- @param data_table LuaGuiElement @@ -185,7 +185,7 @@ Elements.player_dropdown = Gui.define("player_stats/player_dropdown") local data_table = def.data[element] local target_player = ElementsExtra.online_player_dropdown.get_selected(element) Elements.player_stats_table.refresh(data_table, target_player) - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh all stats tables associated with a player dropdown function Elements.player_dropdown.refresh_online() diff --git a/exp_scenario/module/gui/production_stats.lua b/exp_scenario/module/gui/production_stats.lua index deea2314..2c480d0b 100644 --- a/exp_scenario/module/gui/production_stats.lua +++ b/exp_scenario/module/gui/production_stats.lua @@ -94,7 +94,7 @@ Elements.item_selector = Gui.define("production_stats/item_selector") element_data.on_last_row = false Elements.production_table.add_row(element_data.production_table) end - end) --[[ @as any ]] + end) --[[@as any]] --- Label used for every element in the production table Elements.table_label = Gui.define("production_stats/table_label") @@ -144,7 +144,7 @@ Elements.production_table = Gui.define("production_stats/production_table") Elements.table_label(scroll_table, { "exp-gui_production-stats.caption-net" }, { "exp-gui_production-stats.tooltip-per-second" }, "heading_2_label") return scroll_table - end) --[[ @as any ]] + end) --[[@as any]] --- Calculate the row data for a production table --- @param force LuaForce @@ -223,9 +223,9 @@ function Elements.production_table.refresh_online() local precision_index = precision_indexes[element_data.precision_dropdown.selected_index] for _, row in pairs(element_data.rows) do local item_selector = row.item_selector - local item_name = item_selector.elem_value --[[ @as string? ]] + local item_name = item_selector.elem_value --[[@as string?]] if item_name then - local row_data = Elements.production_table.calculate_row_data(player.force --[[ @as LuaForce ]], player.surface, item_name, precision_index) + local row_data = Elements.production_table.calculate_row_data(player.force --[[@as LuaForce]], player.surface, item_name, precision_index) Elements.production_table.refresh_row(production_table, item_selector, row_data) end end diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index 0eacba2c..dcc0c235 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -49,7 +49,7 @@ local function new_quick_action(command, on_click) Elements[command_name] = element Actions[command_name] = { - command = command --[[ @as ExpCommand ]], + command = command --[[@as ExpCommand]], element = element, } end diff --git a/exp_scenario/module/gui/readme.lua b/exp_scenario/module/gui/readme.lua index ed0cf6ea..09237550 100644 --- a/exp_scenario/module/gui/readme.lua +++ b/exp_scenario/module/gui/readme.lua @@ -47,7 +47,7 @@ Elements.title_table = Gui.define("readme/title_table") cell_padding = 0, vertical_align = "center", horizontally_stretchable = true, - } --[[ @as any ]] + } --[[@as any]] --- Scroll pane used for title tables --- @class ExpGui_Readme.elements.title_table_scroll: ExpElement @@ -64,7 +64,7 @@ Elements.title_table_scroll = Gui.define("readme/title_table_scroll") padding = { 1, 3 }, maximal_height = scroll_height, horizontally_stretchable = true, - } --[[ @as any ]] + } --[[@as any]] --- Sub content frame --- @class ExpGui_Readme.elements.sub_content: ExpElement @@ -80,7 +80,7 @@ Elements.sub_content = Gui.define("readme/sub_content") horizontal_align = "center", padding = { 2, 2 }, top_margin = 2, - } --[[ @as any ]] + } --[[@as any]] --- @class ExpGui_Readme.elements.join_server.elements --- @field server_id string @@ -119,7 +119,7 @@ Elements.join_server = Gui.define("readme/join_server") --- @cast def ExpGui_Readme.elements.join_server local server_id = def.data[button].server_id External.request_connection(player, server_id, true) - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh join server button --- @param button LuaGuiElement @@ -244,7 +244,7 @@ define_tab( ) return container - end) --[[ @as any ]] + end) --[[@as any]] ) --- Rules tab @@ -261,14 +261,15 @@ define_tab( local rules = Gui.elements.scroll_table(container, scroll_height, 1) rules.style = "bordered_table" - rules.style.cell_padding = 4 + local rules_style = rules.style --[[@as LuaStyle]] + rules_style.cell_padding = 4 for i = 1, 15 do Gui.elements.centered_label(rules, frame_width - 30, { "exp-gui_readme.rules-" .. i }) end return container - end) --[[ @as any ]] + end) --[[@as any]] ) --- Commands tab @@ -287,7 +288,8 @@ define_tab( local commands = Gui.elements.scroll_table(container, scroll_height, 2) commands.style = "bordered_table" - commands.style.cell_padding = 0 + local commands_style = commands.style --[[@as LuaStyle]] + commands_style.cell_padding = 0 for name, command in pairs(Commands.list_for_player(player)) do Gui.elements.centered_label(commands, 120, name) @@ -295,7 +297,7 @@ define_tab( end return container - end) --[[ @as any ]] + end) --[[@as any]] ) --- Servers tab @@ -338,7 +340,7 @@ define_tab( end return container - end) --[[ @as any ]] + end) --[[@as any]] ) --- Backers tab @@ -433,7 +435,7 @@ define_tab( end return container - end) --[[ @as any ]] + end) --[[@as any]] ) --- @class (exact) ExpGui_Readme.elements.readme_data.param table @@ -599,7 +601,7 @@ define_tab( end return container - end) --[[ @as any ]] + end) --[[@as any]] ) --- @class ExpGui_Readme.elements.container.elements diff --git a/exp_scenario/module/gui/research_milestones.lua b/exp_scenario/module/gui/research_milestones.lua index d1c1a5d9..2e886f32 100644 --- a/exp_scenario/module/gui/research_milestones.lua +++ b/exp_scenario/module/gui/research_milestones.lua @@ -72,7 +72,7 @@ Elements.clock_label = Gui.define("research_milestones/clock_label") type = "label", caption = research_time_format_nil, style = "heading_2_label", - } --[[ @as any ]] + } --[[@as any]] --- Update the clock label for all online players function Elements.clock_label.refresh_online() @@ -95,7 +95,7 @@ Elements.milestone_table_label = Gui.define("research_milestones/table_label") minimal_width = Gui.from_argument(2, 70), horizontal_align = Gui.from_argument(3, "right"), font_color = font_color.neutral, - } --[[ @as any ]] + } --[[@as any]] --- @class ExpGui_ResearchMilestones.elements.milestone_table.row_elements --- @field name LuaGuiElement @@ -124,7 +124,7 @@ Elements.milestone_table = Gui.define("research_milestones/milestone_table") Elements.milestone_table_label(milestone_table, { "exp-gui_research-milestones.caption-difference" }) return milestone_table end) - :element_data{} --[[ @as any ]] + :element_data{} --[[@as any]] do local _row_data = {} --- @type ExpGui_ResearchMilestones.elements.milestone_table.row_data @@ -224,7 +224,7 @@ end --- Refresh all the labels on the table --- @param milestone_table LuaGuiElement function Elements.milestone_table.refresh(milestone_table) - local force = Gui.get_player(milestone_table).force --[[ @as LuaForce ]] + local force = Gui.get_player(milestone_table).force --[[@as LuaForce]] local start_index = Elements.container.calculate_starting_research_index(force) for row_index = 1, display_size do local row_data = Elements.milestone_table.calculate_row_data(force, start_index + row_index - 1) @@ -234,7 +234,7 @@ end --- Refresh all tables for a player function Elements.milestone_table.refresh_player(player) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local start_index = Elements.container.calculate_starting_research_index(force) for _, milestone_table in Elements.milestone_table:online_elements(player) do for row_index = 1, display_size do @@ -272,7 +272,7 @@ Elements.container = Gui.define("research_milestones/container") local force = Gui.get_player(parent).force def.data[force] = def.data[force] or {} -- used by start index and row data - local force = Gui.get_player(parent).force --[[ @as LuaForce ]] + local force = Gui.get_player(parent).force --[[@as LuaForce]] local start_index = Elements.container.calculate_starting_research_index(force) for research_index = start_index, start_index + display_size - 1 do local row_data = Elements.milestone_table.calculate_row_data(force, research_index) diff --git a/exp_scenario/module/gui/rocket_info.lua b/exp_scenario/module/gui/rocket_info.lua index 99ac30c2..7265238d 100644 --- a/exp_scenario/module/gui/rocket_info.lua +++ b/exp_scenario/module/gui/rocket_info.lua @@ -90,7 +90,7 @@ Elements.toggle_section_button = Gui.define("rocket_info/toggle_section_button") element.sprite = "utility/expand" element.tooltip = { "exp-gui_rocket-info.tooltip-toggle-section-expand" } end - end) --[[ @as any ]] + end) --[[@as any]] --- A clickable coordinate label which opens the silo location on the map when pressed --- @class ExpGui_RocketInfo.elements.position_label: ExpElement @@ -114,7 +114,7 @@ Elements.position_label = Gui.define("rocket_info/position_label") local entity = def.data[element] if not entity or not entity.valid then return end player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface } - end) --[[ @as any ]] + end) --[[@as any]] --- Data table showing the launch statistics for a force --- @class ExpGui_RocketInfo.elements.stats_table: ExpElement @@ -125,7 +125,7 @@ Elements.stats_table = Gui.define("rocket_info/stats_table") :element_data{} :draw(function(def, parent) return Gui.elements.scroll_table(parent, 215, 2) - end) --[[ @as any ]] + end) --[[@as any]] --- @class ExpGui_RocketInfo.elements.stats_table.row_data --- @field key string Unique key used to look up the value label @@ -218,7 +218,7 @@ end --- Refresh the stats table for a player, adding any rows that do not yet exist --- @param player LuaPlayer function Elements.stats_table.refresh_player(player) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local row_data = Elements.stats_table.calculate_row_data(force) for _, stats_table in Elements.stats_table:online_elements(player) do Elements.stats_table.refresh(stats_table, row_data) @@ -243,7 +243,7 @@ Elements.milestones_table = Gui.define("rocket_info/milestones_table") :element_data{} :draw(function(def, parent) return Gui.elements.scroll_table(parent, 215, 2) - end) --[[ @as any ]] + end) --[[@as any]] --- @class ExpGui_RocketInfo.elements.milestones_table.row_data --- @field milestone number The milestone this row represents @@ -315,7 +315,7 @@ end --- Refresh the milestones table for a player, adding rows as new milestones become visible --- @param player LuaPlayer function Elements.milestones_table.refresh_player(player) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local row_data = Elements.stats_table.calculate_row_data(force) for _, stats_table in Elements.stats_table:online_elements(player) do Elements.stats_table.refresh(stats_table, row_data) @@ -359,7 +359,7 @@ Elements.progress_table = Gui.define("rocket_info/progress_table") def.data[progress_table] = { rows = {}, no_silos = no_silos } return progress_table - end) --[[ @as any ]] + end) --[[@as any]] --- @class ExpGui_RocketInfo.elements.progress_table.row_data --- @field entity LuaEntity @@ -500,7 +500,7 @@ end --- Refresh the progress table for a player, reconciling rows with the current set of silos --- @param player LuaPlayer function Elements.progress_table.refresh_player(player) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local silos = Elements.container.get_silos(force) local row_data = Elements.progress_table.calculate_row_data_all(silos) for _, progress_table in Elements.progress_table:online_elements(player) do @@ -543,7 +543,7 @@ Elements.container = Gui.define("rocket_info/container") container.style.padding = 0 local player = Gui.get_player(parent) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local force_data = def._get_force_data(force) if config.stats.show_stats then @@ -622,7 +622,7 @@ end --- Add a silo to the list of current silos for a force --- @param entity LuaEntity function Elements.container.add_silo(entity) - local force = entity.force --[[ @as LuaForce ]] + local force = entity.force --[[@as LuaForce]] local silos = Elements.container._get_force_data(force).silos local unit_number = entity.unit_number --- @cast unit_number uint @@ -636,7 +636,7 @@ end --- Increment the launch count for a silo --- @param entity LuaEntity function Elements.container.increment_silo(entity) - local force = entity.force --[[ @as LuaForce ]] + local force = entity.force --[[@as LuaForce]] local silos = Elements.container._get_force_data(force).silos local unit_number = entity.unit_number --- @cast unit_number uint @@ -696,7 +696,7 @@ end --- @param event EventData.on_rocket_launch_ordered local function on_rocket_launch_ordered(event) Elements.container.increment_silo(event.rocket_silo) - Elements.progress_table.refresh_force(event.rocket_silo.force --[[ @as LuaForce ]]) + Elements.progress_table.refresh_force(event.rocket_silo.force --[[@as LuaForce]]) end --- Refresh the gui when a player joins the game as it may be outdated @@ -714,7 +714,7 @@ local function on_built(event) local entity = event.entity if not entity.valid or entity.name ~= "rocket-silo" then return end Elements.container.add_silo(entity) - Elements.progress_table.refresh_force(entity.force --[[ @as LuaForce ]]) + Elements.progress_table.refresh_force(entity.force --[[@as LuaForce]]) end --- Refresh the progress for all forces that own at least one silo diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index f3ceb24a..7d3a829c 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -79,7 +79,7 @@ Elements.production_label = Gui.define("science_production/production_label") def.data[label] = suffix return label - end) --[[ @as any ]] + end) --[[@as any]] --- @class Elements.production_label.display_data --- @field caption LocalisedString @@ -144,12 +144,12 @@ Elements.no_production_label = Gui.define("science_production/no_production_labe padding = { 2, 4 }, single_line = false, width = 200, - } --[[ @as any ]] + } --[[@as any]] --- Refresh a no production label --- @param no_production_label LuaGuiElement function Elements.no_production_label.refresh(no_production_label) - local force = Gui.get_player(no_production_label).force --[[ @as LuaForce ]] + local force = Gui.get_player(no_production_label).force --[[@as LuaForce]] no_production_label.visible = not Elements.container.has_production(force) end @@ -157,7 +157,7 @@ end function Elements.no_production_label.refresh_online() local force_data = {} for player, no_production_label in Elements.no_production_label:online_elements() do - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local visible = force_data[force.name] if visible == nil then visible = not Elements.container.has_production(force) @@ -196,7 +196,7 @@ Elements.science_table = Gui.define("science_production/science_table") science_table.style.column_alignments[3] = "right" return science_table end) - :element_data{} --[[ @as any ]] + :element_data{} --[[@as any]] --- Calculate the data needed to add or refresh a row --- @param force LuaForce @@ -312,7 +312,8 @@ function Elements.science_table.refresh_row(science_table, row_data) -- Update the icon local icon = row.icon icon.style = row_data.icon_style - icon.style.height = 55 + local icon_style = icon.style --[[@as LuaStyle]] + icon_style.height = 55 -- Update the element visibility row.net_suffix.visible = true @@ -360,7 +361,7 @@ Elements.eta_label = Gui.define("science_production/eta_label") caption = clock_time_format_nil, tooltip = long_time_format_nil, style = "frame_title", - } --[[ @as any ]] + } --[[@as any]] --- @class Elements.eta_label.display_data --- @field caption LocalisedString @@ -409,7 +410,7 @@ end --- Refresh an eta label --- @param eta_label LuaGuiElement function Elements.eta_label.refresh(eta_label) - local force = Gui.get_player(eta_label).force --[[ @as LuaForce ]] + local force = Gui.get_player(eta_label).force --[[@as LuaForce]] local display_data = Elements.eta_label.calculate_display_data(force) eta_label.caption = display_data.caption eta_label.tooltip = display_data.tooltip @@ -441,7 +442,7 @@ Elements.container = Gui.define("science_production/container") local container = Gui.elements.container(parent) Gui.elements.header(container, { caption = { "exp-gui_science-production.caption-main" } }) - local force = Gui.get_player(parent).force --[[ @as LuaForce ]] + local force = Gui.get_player(parent).force --[[@as LuaForce]] local science_table = Elements.science_table(container) for _, science_pack in ipairs(config) do --- @cast science_pack any @@ -460,7 +461,7 @@ Elements.container = Gui.define("science_production/container") end return Gui.elements.container.get_root_element(container) - end) --[[ @as any ]] + end) --[[@as any]] --- Cached mostly because they are long names local _fp_one_minute = defines.flow_precision_index.one_minute diff --git a/exp_scenario/module/gui/surveillance.lua b/exp_scenario/module/gui/surveillance.lua index 0c1e79e2..0fd65924 100644 --- a/exp_scenario/module/gui/surveillance.lua +++ b/exp_scenario/module/gui/surveillance.lua @@ -25,7 +25,7 @@ Elements.player_dropdown = Gui.define("surveillance/player_dropdown") local camera = def.data[element] local target_player = assert(ElementsExtra.online_player_dropdown.get_selected(element)) Elements.camera.set_target_player(camera, target_player) - end) --[[ @as any ]] + end) --[[@as any]] --- Button which sets the target of a camera to the current location --- @class ExpGui_Surveillance.elements.set_location_button: ExpElement @@ -48,7 +48,7 @@ Elements.set_location_button = Gui.define("surveillance/set_location_button") --- @cast def ExpGui_Surveillance.elements.set_location_button local camera = def.data[element] Elements.camera.set_target_position(camera, player.physical_surface_index, player.physical_position) - end) --[[ @as any ]] + end) --[[@as any]] --- @class ExpGui_Surveillance.elements.type_dropdown.data --- @field player_dropdown LuaGuiElement @@ -87,7 +87,7 @@ Elements.type_dropdown = Gui.define("surveillance/type_dropdown") local target_player = ElementsExtra.online_player_dropdown.get_selected(element_data.player_dropdown) Elements.camera.set_target_player(element_data.camera, target_player) end - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh all online type dropdowns by cycling the associated player dropdown function Elements.type_dropdown.refresh_online() @@ -131,7 +131,7 @@ Elements.zoom_out_button = Gui.define("surveillance/zoom_out_button") if camera.zoom > 0.2 then camera.zoom = camera.zoom - 0.05 end - end) --[[ @as any ]] + end) --[[@as any]] --- Buttons which increases zoom by 5% --- @class ExpGui_Surveillance.elements.zoom_in_button: ExpElement @@ -156,7 +156,7 @@ Elements.zoom_in_button = Gui.define("surveillance/zoom_in_button") if camera.zoom < 2.0 then camera.zoom = camera.zoom + 0.05 end - end) --[[ @as any ]] + end) --[[@as any]] --- Camera which tracks a target with a physical_position and surface_index --- @class ExpGui_Surveillance.elements.camera: ExpElement @@ -176,7 +176,7 @@ Elements.camera = Gui.define("surveillance/camera") } :element_data( Gui.from_argument(1) - ) --[[ @as any ]] + ) --[[@as any]] --- Set the target player for the camera --- @param camera LuaGuiElement diff --git a/exp_scenario/module/gui/task_list.lua b/exp_scenario/module/gui/task_list.lua index e777aa3d..e39efa89 100644 --- a/exp_scenario/module/gui/task_list.lua +++ b/exp_scenario/module/gui/task_list.lua @@ -105,7 +105,7 @@ Elements.new_task_button = Gui.define("task_list/new_task_button") body = "", new = true, }) - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the visibility based on player permissions --- @param new_task_button LuaGuiElement @@ -154,7 +154,7 @@ Elements.view_task_button = Gui.define("task_list/view_task_button") --- @cast def ExpGui_TaskList.element.view_task_button local elements = def.data[view_task_button] Elements.container.open_view_task(elements.container, elements.task) - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the title and tooltip of the view task button --- @param view_task_button LuaGuiElement @@ -187,19 +187,19 @@ Elements.no_tasks_header = Gui.define("task_list/no_tasks_header") :style{ padding = { 2, 0 }, bottom_margin = 0, - } --[[ @as any ]] + } --[[@as any]] --- Refresh the visibility of the no tasks label --- @param no_tasks_header LuaGuiElement function Elements.no_tasks_header.refresh(no_tasks_header) - local force = Gui.get_player(no_tasks_header).force --[[ @as LuaForce ]] + local force = Gui.get_player(no_tasks_header).force --[[@as LuaForce]] no_tasks_header.visible = not Elements.container.has_tasks(force) end --- Refresh the visibility of the no tasks label --- @param player LuaPlayer function Elements.no_tasks_header.refresh_player(player) - local visible = not Elements.container.has_tasks(player.force --[[ @as LuaForce ]]) + local visible = not Elements.container.has_tasks(player.force --[[@as LuaForce]]) for _, no_tasks_header in Elements.no_tasks_header:tracked_elements(player) do no_tasks_header.visible = visible end @@ -246,7 +246,7 @@ Elements.task_list = Gui.define("task_list/task_list") return task_list end) - :element_data{} --[[ @as any ]] + :element_data{} --[[@as any]] --- Adds a task to the task list --- @param task_list LuaGuiElement @@ -330,7 +330,7 @@ Elements.task_footer = Gui.define("task_list/task_footer") height = 0, padding = 5, use_header_filler = false, - } --[[ @as any ]] + } --[[@as any]] --- @class ExpGui_TaskList.elements.close_task_button.elements --- @field container LuaGuiElement @@ -354,7 +354,7 @@ Elements.close_task_button = Gui.define("task_list/close_task_button") --- @cast def ExpGui_TaskList.element.close_task_button local elements = def.data[close_task_button] Elements.container.close_footers(elements.container) - end) --[[ @as any ]] + end) --[[@as any]] --- @class ExpGui_TaskList.elements.task_button.elements --- @field task ExpGui_TaskList.Task? @@ -379,7 +379,7 @@ Elements.edit_task_button = Gui.define("task_list/edit_task_button") --- @cast def ExpGui_TaskList.element.edit_task_button local elements = def.data[edit_task_button] Elements.container.open_edit_task(elements.container, assert(elements.task)) - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the edit button --- @param edit_task_button LuaGuiElement @@ -415,9 +415,9 @@ Elements.delete_task_button = Gui.define("task_list/delete_task_button") :on_click(function(def, player, delete_task_button) --- @cast def ExpGui_TaskList.element.delete_task_button local elements = def.data[delete_task_button] - Elements.container.remove_task(player.force --[[ @as LuaForce ]], elements.task) + Elements.container.remove_task(player.force --[[@as LuaForce]], elements.task) Elements.container.close_footers(elements.container) - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the delete button --- @param delete_task_button LuaGuiElement @@ -477,7 +477,7 @@ Elements.view_task_footer = Gui.define("task_list/view_task_footer") } return view_task_footer - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the view task with new task details --- @param view_task_footer LuaGuiElement @@ -534,7 +534,7 @@ Elements.task_message_textfield = Gui.define("task_list/task_message_textfield") --- @cast def ExpGui_TaskList.elements.task_message_textfield local confirm_task_button = def.data[task_message_textfield].confirm_task_button confirm_task_button.enabled = string.len(task_message_textfield.text) > 5 - end) --[[ @as any ]] + end) --[[@as any]] --- Set the confirm task button to update on text changed --- @param task_message_textfield LuaGuiElement @@ -590,13 +590,13 @@ Elements.confirm_task_button = Gui.define("task_list/confirm_task_button") Elements.container.close_footers(elements.container) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] if task.new then Elements.container.add_task(force, task) else Elements.container.refresh_force_online(force, task) end - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the confirm task button --- @param confirm_task_button LuaGuiElement @@ -649,7 +649,7 @@ Elements.discard_task_button = Gui.define("task_list/discard_task_button") else Elements.container.open_view_task(elements.container, task) end - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the discard task button --- @param discard_task_button LuaGuiElement @@ -696,7 +696,7 @@ Elements.edit_task_footer = Gui.define("task_list/edit_task_footer") } return edit_task_footer - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the view task with new task details --- @param edit_task_footer LuaGuiElement @@ -781,7 +781,7 @@ Elements.container = Gui.define("task_list/container") -- Add tasks to the list if there are any local player = Gui.get_player(parent) - local force = player.force --[[ @as LuaForce ]] + local force = player.force --[[@as LuaForce]] local tasks = def.data[force] if tasks then for _, task in ipairs(tasks) do @@ -796,11 +796,11 @@ Elements.container = Gui.define("task_list/container") elements.edit_task_footer.visible = false -- Set the data and return - def.data[root] = elements --[[ @as any ]] + def.data[root] = elements --[[@as any]] return root end) :global_data{ next_task_id = 1 } - :force_data{} --[[ @as any ]] + :force_data{} --[[@as any]] --- Check if a force has tasks --- @param force LuaForce @@ -885,7 +885,7 @@ end --- Refresh all tasks for a player --- @param player LuaPlayer function Elements.container.refresh_player(player) - local tasks = Elements.container.data[ player.force --[[ @as LuaForce ]] ] + local tasks = Elements.container.data[ player.force --[[@as LuaForce]] ] for _, container in Elements.container:tracked_elements(player) do local elements = Elements.container.data[container] Elements.task_list.refresh_tasks(elements.task_list, tasks) @@ -908,7 +908,7 @@ end --- Add the element to the left flow with a toolbar button Gui.add_left_element(Elements.container, function(player) - return Elements.container.has_tasks(player.force --[[ @as LuaForce ]]) + return Elements.container.has_tasks(player.force --[[@as LuaForce]]) end) Gui.toolbar.create_button{ diff --git a/exp_server_ups/module/control.lua b/exp_server_ups/module/control.lua index b39eab29..6d51c9d3 100644 --- a/exp_server_ups/module/control.lua +++ b/exp_server_ups/module/control.lua @@ -42,7 +42,7 @@ Elements.server_ups = Gui.define("server_ups") if not existing or not existing.valid then def.data[player] = element -- Only set if previous is invalid end - end) --[[ @as any ]] + end) --[[@as any]] --- Refresh the caption for all online players --- @param ups number The UPS to be displayed From 589b876865e84e1488dc66ba1d1d0da66b163634 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:40:16 +0000 Subject: [PATCH 12/46] Fix a nil config read and two key types `config.temp_warning_limit` does not exist, the warnings config defines `script_warning_limit`, and the locale expects it as `__3__/__4__`, so the command was printing nil. map_tags is keyed by the force name joined to the tag number, and custom_color is the Color union. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/expcore/roles.lua | 2 +- exp_scenario/module/commands/protected_tags.lua | 2 +- exp_scenario/module/commands/warnings.lua | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index b84875f2..940d5a67 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -200,7 +200,7 @@ function Roles.debug() local output = "" for index, role_name in ipairs(Roles.config.order) do local role = Roles.config.roles[role_name] - local color = role.custom_color or Colours.white + local color = role.custom_color or Colours.white --[[@as Color.struct]] local color_str = string.format("[color=%d, %d, %d]", color.r, color.g, color.b) output = output .. string.format("\n%s %s) %s[/color]", color_str, index, serpent.line(role)) end diff --git a/exp_scenario/module/commands/protected_tags.lua b/exp_scenario/module/commands/protected_tags.lua index 1667b008..fce30257 100644 --- a/exp_scenario/module/commands/protected_tags.lua +++ b/exp_scenario/module/commands/protected_tags.lua @@ -7,7 +7,7 @@ local Storage = require("modules/exp_util/storage") --- Storage variables local active_players = {} --- @type table Stores all players in in protected mode -local map_tags = {} --- @type table Stores all protected map tags +local map_tags = {} --- @type table Stores all protected map tags Storage.register({ active_players = active_players, diff --git a/exp_scenario/module/commands/warnings.lua b/exp_scenario/module/commands/warnings.lua index bcc8c38d..14be9aad 100644 --- a/exp_scenario/module/commands/warnings.lua +++ b/exp_scenario/module/commands/warnings.lua @@ -35,7 +35,7 @@ Commands.new("get-warnings", { "exp-commands_warnings.description-get" }) local warnings = Warnings.get_warnings(player) local script_warnings = Warnings.get_script_warnings(player) local other_player_name = format_player_name(other_player) - Commands.print{ "exp-commands_warnings.player-title", other_player_name, #warnings, #script_warnings, config.temp_warning_limit } + Commands.print{ "exp-commands_warnings.player-title", other_player_name, #warnings, #script_warnings, config.script_warning_limit } for _, warning in pairs(warnings) do local by_player_name_formatted = format_player_name(warning.by_player_name) Commands.print{ "exp-commands_warnings.list-element-player", by_player_name_formatted, warning.reason } @@ -47,12 +47,12 @@ Commands.new("get-warnings", { "exp-commands_warnings.description-get" }) for player_name, player_warnings in pairs(warnings) do local player_name_formatted = format_player_name(player_name) local script_warning_count = script_warnings[player_name] and #script_warnings[player_name] or 0 - Commands.print{ "exp-commands_warnings.list-element", player_name_formatted, #player_warnings, script_warning_count, config.temp_warning_limit } + Commands.print{ "exp-commands_warnings.list-element", player_name_formatted, #player_warnings, script_warning_count, config.script_warning_limit } end for player_name, player_warnings in pairs(script_warnings) do if not warnings[player_name] then local player_name_formatted = format_player_name(player_name) - Commands.print{ "exp-commands_warnings.list-element", player_name_formatted, 0, #player_warnings, config.temp_warning_limit } + Commands.print{ "exp-commands_warnings.list-element", player_name_formatted, 0, #player_warnings, config.script_warning_limit } end end end From 1387b8a2dbbc5ebd6d7368d594a35d83ae046715 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:43:55 +0000 Subject: [PATCH 13/46] Fix gui element registration and iter removal The top, left and relative element tables are keyed by the define, but the duplicate registration assert looked up `define.name`, so it never fired. GuiIter.remove_element indexed `registered_scopes` by player index instead of the scope elements it had just fetched, so it removed nothing. Co-Authored-By: Claude Opus 5 (1M context) --- exp_gui/module/control.lua | 6 +++--- exp_gui/module/data.lua | 3 +++ exp_gui/module/iter.lua | 4 +--- exp_gui/module/toolbar.lua | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index 56cf2dd3..c6f84e3c 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -88,7 +88,7 @@ end --- @param define ExpElement --- @param visible Gui.VisibleCallback | boolean | nil function Gui.add_top_element(define, visible) - assert(Gui.top_elements[define.name] == nil, "Element is already added to the top flow") + assert(Gui.top_elements[define] == nil, "Element is already added to the top flow") Gui.top_elements[define] = visible or false end @@ -96,7 +96,7 @@ end --- @param define ExpElement --- @param visible Gui.VisibleCallback | boolean | nil function Gui.add_left_element(define, visible) - assert(Gui.left_elements[define.name] == nil, "Element is already added to the left flow") + assert(Gui.left_elements[define] == nil, "Element is already added to the left flow") Gui.left_elements[define] = visible or false end @@ -105,7 +105,7 @@ end --- @param define ExpElement --- @param visible Gui.VisibleCallback | boolean | nil function Gui.add_relative_element(define, visible) - assert(Gui.relative_elements[define.name] == nil, "Element is already added to the relative flow") + assert(Gui.relative_elements[define] == nil, "Element is already added to the relative flow") Gui.relative_elements[define] = visible or false end diff --git a/exp_gui/module/data.lua b/exp_gui/module/data.lua index d57c8c99..3e92cd14 100644 --- a/exp_gui/module/data.lua +++ b/exp_gui/module/data.lua @@ -90,13 +90,16 @@ function GuiData._metatable.__index(self, key) assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.") local object_name = key.object_name --- @diagnostic disable-line assign-type-mismatch if object_name == "LuaGuiElement" then + --- @cast key LuaGuiElement local data = self._raw.element_data local player_elements = data and data[key.player_index] return player_elements and player_elements[key.index] elseif object_name == "LuaPlayer" then + --- @cast key LuaPlayer local data = self._raw.player_data return data and data[key.index] elseif object_name == "LuaForce" then + --- @cast key LuaForce local data = self._raw.force_data return data and data[key.index] else diff --git a/exp_gui/module/iter.lua b/exp_gui/module/iter.lua index 9d8db2ea..ad1d609c 100644 --- a/exp_gui/module/iter.lua +++ b/exp_gui/module/iter.lua @@ -166,7 +166,6 @@ end function GuiIter.get_tracked_elements(scope, filter) local class_name = ExpUtil.get_class_name(filter) if class_name == "nil" then - --- @cast filter nil return GuiIter.all_elements(scope) elseif class_name == "LuaPlayer" then --- @cast filter LuaPlayer @@ -189,7 +188,6 @@ end function GuiIter.get_online_elements(scope, filter) local class_name = ExpUtil.get_class_name(filter) if class_name == "nil" then - --- @cast filter nil return GuiIter.filtered_elements(scope, game.connected_players) elseif class_name == "LuaPlayer" then --- @cast filter LuaPlayer @@ -235,7 +233,7 @@ end function GuiIter.remove_element(scope, player_index, element_index) local scope_elements = registered_scopes[scope] if not scope_elements then return end - local player_elements = registered_scopes[player_index] + local player_elements = scope_elements[player_index] if not player_elements then return end player_elements[element_index] = nil end diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua index f22c4cc7..a1f438db 100644 --- a/exp_gui/module/toolbar.lua +++ b/exp_gui/module/toolbar.lua @@ -350,7 +350,7 @@ local function move_toolbar_button(player, item, offset) -- Swap the position in the list local list = assert(item.parent) - local other_item = list.children[new_index] + local other_item = assert(list.children[new_index]) list.swap_children(old_index, new_index) -- Swap the position in the top flow, offset by 1 because of settings button @@ -499,7 +499,8 @@ function Toolbar._create_elements(player) -- Reset the state of the previous last child local children = toolbar_list.children if previous_last_index > 0 then - elements.toolbar_list_item.data[children[previous_last_index]].move_item_down.enabled = true + local previous_last = assert(children[previous_last_index]) + elements.toolbar_list_item.data[previous_last].move_item_down.enabled = true end -- Set the state of the move buttons for the first and last element From d78ae40c5d6849e87079ce8929a9fa24b871bce4 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:45:36 +0000 Subject: [PATCH 14/46] Correct partial table types `_display_data` is a cache keyed by force name, not a single record. The auto complete accumulator starts empty, so its fields are optional. Game commands have no usage beyond their own help text. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/search.lua | 3 ++- exp_scenario/module/control/fast_deconstruction.lua | 3 ++- exp_scenario/module/gui/module_inserter.lua | 4 ++-- exp_scenario/module/gui/research_milestones.lua | 1 + exp_scenario/module/gui/science_production.lua | 2 +- exp_util/module/module_exports.lua | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/exp_commands/module/search.lua b/exp_commands/module/search.lua index 56fe8b05..d2e3fef9 100644 --- a/exp_commands/module/search.lua +++ b/exp_commands/module/search.lua @@ -49,7 +49,8 @@ function Search.prepare(custom_commands) command_objects[name] = { name = name, description = locale_desc, - help_text = locale_desc, + help_text = locale_desc, + usage = locale_desc, aliases = {}, } end diff --git a/exp_scenario/module/control/fast_deconstruction.lua b/exp_scenario/module/control/fast_deconstruction.lua index 85a825f9..acd83069 100644 --- a/exp_scenario/module/control/fast_deconstruction.lua +++ b/exp_scenario/module/control/fast_deconstruction.lua @@ -77,7 +77,8 @@ local function get_player_cache(player_index) -- Create a new cache if the previous on is in use if not cache or cache.task and not cache.task.completed then - cache = {} --[[@as any]] + --- @diagnostic disable-next-line: missing-fields + cache = {} --[[@as TreeDeconCache]] end local player = assert(game.get_player(player_index)) diff --git a/exp_scenario/module/gui/module_inserter.lua b/exp_scenario/module/gui/module_inserter.lua index 68a72023..a263932f 100644 --- a/exp_scenario/module/gui/module_inserter.lua +++ b/exp_scenario/module/gui/module_inserter.lua @@ -277,8 +277,8 @@ local function apply_planners_in_area(player, area, machine_name, planner_with_p -- Bounding box table to be reused in the loop below --- @type BoundingBox local param_area = { - left_top = {}, - right_bottom = {} + left_top = { x = 0, y = 0 }, + right_bottom = { x = 0, y = 0 }, } -- Update area param table to be reused in the loop below diff --git a/exp_scenario/module/gui/research_milestones.lua b/exp_scenario/module/gui/research_milestones.lua index 2e886f32..3863af68 100644 --- a/exp_scenario/module/gui/research_milestones.lua +++ b/exp_scenario/module/gui/research_milestones.lua @@ -128,6 +128,7 @@ Elements.milestone_table = Gui.define("research_milestones/milestone_table") do local _row_data = {} --- @type ExpGui_ResearchMilestones.elements.milestone_table.row_data + --- @diagnostic disable-next-line: missing-fields local empty_row_data = { color = font_color.positive } --- Get the row data for a force and research diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index 7d3a829c..c654d924 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -416,7 +416,7 @@ function Elements.eta_label.refresh(eta_label) eta_label.tooltip = display_data.tooltip end -do local _display_data = {} --- @type Elements.eta_label.display_data +do local _display_data = {} --- @type table --- Refresh the eta label for all online players function Elements.eta_label.refresh_online() -- Refresh the row data for online forces diff --git a/exp_util/module/module_exports.lua b/exp_util/module/module_exports.lua index e6394bae..5bddd019 100644 --- a/exp_util/module/module_exports.lua +++ b/exp_util/module/module_exports.lua @@ -207,7 +207,7 @@ end function ExpUtil.auto_complete(options, input, use_key, rtn_key, use_pattern) input = input:lower() local plain = use_pattern ~= true - local found = {} --- @type { length: number, index: number, start: boolean, value: any } + local found = {} --- @type { length: number?, index: number?, start: boolean?, value: any } for k, v in pairs(options) do local str = use_key and k or v local index = str:lower():find(input, nil, plain) From 19fd1eabbeb5c2e880782cf6b7a01bb34f8a5014 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:46:18 +0000 Subject: [PATCH 15/46] Correct container data keys and callable overloads Autofill stores the whole per entity map against a player. The readme container is called as a define, so it needs the overload the other element classes carry. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/gui/autofill.lua | 2 +- exp_scenario/module/gui/readme.lua | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/exp_scenario/module/gui/autofill.lua b/exp_scenario/module/gui/autofill.lua index 9662b3f9..3dc16175 100644 --- a/exp_scenario/module/gui/autofill.lua +++ b/exp_scenario/module/gui/autofill.lua @@ -265,7 +265,7 @@ end --- Container added to the left gui flow --- @class ExpGui_Autofill.elements.container: ExpElement ---- @field data table +--- @field data table> Elements.container = Gui.define("autofill/container") :draw(function(def, parent) --- @cast def ExpGui_Autofill.elements.container diff --git a/exp_scenario/module/gui/readme.lua b/exp_scenario/module/gui/readme.lua index 09237550..5a7749b6 100644 --- a/exp_scenario/module/gui/readme.lua +++ b/exp_scenario/module/gui/readme.lua @@ -361,32 +361,32 @@ define_tab( roles = { "Senior Administrator", "Administrator" }, title = { "exp-gui_readme.backers-management" }, width = 230, - players = {}, + players = {}, --- @type string[] }, { roles = { "Board Member", "Senior Backer" }, title = { "exp-gui_readme.backers-board" }, width = 145, - players = {}, + players = {}, --- @type string[] }, { roles = { "Sponsor", "Supporter" }, title = { "exp-gui_readme.backers-backers" }, width = 196, - players = {}, + players = {}, --- @type string[] }, { roles = { "Moderator", "Trainee" }, title = { "exp-gui_readme.backers-staff" }, width = 235, - players = {}, + players = {}, --- @type string[] }, { roles = {}, time = 3 * 3600 * 60, title = { "exp-gui_readme.backers-active" }, width = 235, - players = {}, + players = {}, --- @type string[] }, } @@ -610,6 +610,7 @@ define_tab( --- Main readme container --- @class ExpGui_Readme.elements.container: ExpElement --- @field data table +--- @overload fun(parent: LuaGuiElement): LuaGuiElement Elements.container = Gui.define("readme/container") :track_all_elements() :draw(function(def, parent) From 4745b6de0b7d6bbf0fb27ffc6f952337a411e5d8 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:47:20 +0000 Subject: [PATCH 16/46] Give the role methods an explicit self emmylua takes self from the owner table, so `@param self ExpRoles.Role` only applies with dot syntax. Call sites still use a colon. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/expcore/roles.lua | 2 +- exp_roles/module/control.lua | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index 940d5a67..da506d02 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -200,7 +200,7 @@ function Roles.debug() local output = "" for index, role_name in ipairs(Roles.config.order) do local role = Roles.config.roles[role_name] - local color = role.custom_color or Colours.white --[[@as Color.struct]] + local color = (role.custom_color or Colours.white) --[[@as Color.struct]] local color_str = string.format("[color=%d, %d, %d]", color.r, color.g, color.b) output = output .. string.format("\n%s %s) %s[/color]", color_str, index, serpent.line(role)) end diff --git a/exp_roles/module/control.lua b/exp_roles/module/control.lua index 652921e0..f0622a0b 100644 --- a/exp_roles/module/control.lua +++ b/exp_roles/module/control.lua @@ -399,7 +399,7 @@ end --- @param self ExpRoles.Role --- @param action string --- @return boolean -function Role:is_allowed(action) +function Role.is_allowed(self, action) local allowed = self.allowed_actions return allowed["core.admin"] or allowed[permission_from_action(action)] or false end @@ -408,7 +408,7 @@ end --- @param self ExpRoles.Role --- @param name string --- @return boolean -function Role:has_flag(name) +function Role.has_flag(self, name) return self.allowed_actions[permission_from_flag(name)] or false end @@ -416,7 +416,7 @@ end --- @param self ExpRoles.Role --- @param online boolean? When given, filter by connected state --- @return LuaPlayer[] -function Role:get_players(online) +function Role.get_players(self, online) local players = {} for player_name, role_names in pairs(ExpRoles.config.players) do for _, role_name in pairs(role_names) do @@ -437,7 +437,7 @@ end --- @param self ExpRoles.Role --- @param message LocalisedString --- @return number # Number of players the message was sent to -function Role:print(message) +function Role.print(self, message) local players = self:get_players(true) for _, player in pairs(players) do player.print(message) From b07052307d7e4fc908268c555048cda8021283e1 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:48:21 +0000 Subject: [PATCH 17/46] Fix stale vlayer signals and index types `LogisticFilter.signal` became `value` in 2.0, so the clear loop broke on the first slot and outdated signals were left behind. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/search.lua | 2 +- exp_legacy/module/modules/control/vlayer.lua | 2 +- exp_scenario/module/commands/roles.lua | 2 +- exp_scenario/module/control/discord_alerts.lua | 2 +- exp_scenario/module/gui/production_stats.lua | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/exp_commands/module/search.lua b/exp_commands/module/search.lua index d2e3fef9..8e0176e9 100644 --- a/exp_commands/module/search.lua +++ b/exp_commands/module/search.lua @@ -3,7 +3,7 @@ local Search = {} local Storage = require("modules/exp_util/storage") --- Setup the storage to contain the pending translations and the completed ones -local pending = {} --- @type { [1]: string, [2]: string }[] +local pending = {} --- @type table local translations = {} --- @type table> Storage.register({ pending, diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index d70b15f8..3730834d 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -617,7 +617,7 @@ local function handle_circuit_interfaces() -- Clear remaining signals to prevent outdated values being present (caused by count > 0 check) for clear_index = signal_index, #circuit do - if not circuit_oc.get_slot(clear_index).signal then + if not circuit_oc.get_slot(clear_index).value then break -- There are no more signals to clear end diff --git a/exp_scenario/module/commands/roles.lua b/exp_scenario/module/commands/roles.lua index eb2ec833..7cbfd8d0 100644 --- a/exp_scenario/module/commands/roles.lua +++ b/exp_scenario/module/commands/roles.lua @@ -42,7 +42,7 @@ Commands.new("get-roles", { "exp-commands_roles.description-get" }) --- @cast other_player LuaPlayer? local roles = get_roles_ordered() local roles_formatted = { "" } --- @type LocalisedString - local response = { "exp-commands_roles.list-roles", roles_formatted } + local response = { "exp-commands_roles.list-roles", roles_formatted } --[[@as LocalisedString]] if other_player then roles = get_player_roles(other_player) response[1] = "exp-commands_roles.list-player" diff --git a/exp_scenario/module/control/discord_alerts.lua b/exp_scenario/module/control/discord_alerts.lua index 5fc894f1..249a3e2e 100644 --- a/exp_scenario/module/control/discord_alerts.lua +++ b/exp_scenario/module/control/discord_alerts.lua @@ -31,7 +31,7 @@ local function append_playtime(player_name) end --- Get the player name from an event ---- @param event { player_index: number, by_player_name: string? } +--- @param event { player_index: uint, by_player_name: string? } --- @return string, string local function get_player_name(event) local player = game.players[event.player_index] diff --git a/exp_scenario/module/gui/production_stats.lua b/exp_scenario/module/gui/production_stats.lua index 2c480d0b..10963298 100644 --- a/exp_scenario/module/gui/production_stats.lua +++ b/exp_scenario/module/gui/production_stats.lua @@ -9,6 +9,7 @@ local Roles = require("modules/exp_legacy/expcore/roles") local Elements = {} --- The flow precision values in the same order as production_precision_dropdown.items +--- @type defines.flow_precision_index[] local precision_indexes = { defines.flow_precision_index.five_seconds, defines.flow_precision_index.one_minute, From 5aeafa0e11bc2b146afb8b8de9cdc65376cc0aa2 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:50:18 +0000 Subject: [PATCH 18/46] Fix child lookups, cache keys and loadstring `loadstring` was removed in Lua 5.2, `load` takes a string the same way. Warp gui child lookups are optional, so they use the same assert the rest of the repo does. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/expcore/roles.lua | 2 +- exp_legacy/module/modules/gui/warp-list.lua | 12 ++++++++---- exp_scenario/module/gui/debug/model.lua | 2 +- exp_scenario/module/gui/science_production.lua | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index da506d02..d3698f3e 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -894,7 +894,7 @@ role:set_permission_group('Admin') function Roles._prototype:set_permission_group(name, use_factorio_api) ExpUtil.assert_not_runtime() if use_factorio_api then - self.permission_group = { true, name } + self.permission_group = { true, name } --[[@as [boolean, string] ]] else local group = Groups.get_group_by_name(name) if not group then return end diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index 49a75e27..10fcd593 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -291,9 +291,13 @@ local confirm_edit_button = Gui.define("confirm_edit_button") } :style(Styles.sprite22) :on_click(function(def, player, element) - local warp_id = element.parent.caption - local warp_name = element.parent.parent["name-" .. warp_id][warp_textfield.name].text - local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[@as SignalID]] + local parent = assert(element.parent) + local grandparent = assert(parent.parent) + local warp_id = parent.caption --[[@as string]] + local name_flow = assert(grandparent["name-" .. warp_id]) + local icon_flow = assert(grandparent["icon-" .. warp_id]) + local warp_name = assert(name_flow[warp_textfield.name]).text + local warp_icon = assert(icon_flow[warp_icon_editing.name]).elem_value --[[@as SignalID]] if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) @@ -778,7 +782,7 @@ Event.on_nth_tick(math.floor(60 / config.update_smoothing), function() end -- Check if the force has any warps - local closest_warp = nil + local closest_warp = nil --- @type { warp_id: string, name: string }? local closest_distance = nil if #warp_ids > 0 then local surface = player.surface diff --git a/exp_scenario/module/gui/debug/model.lua b/exp_scenario/module/gui/debug/model.lua index 52ab850f..387aed88 100644 --- a/exp_scenario/module/gui/debug/model.lua +++ b/exp_scenario/module/gui/debug/model.lua @@ -4,7 +4,7 @@ local ExpUtil = require("modules/exp_util") local concat = table.concat local inspect = table.inspect local pcall = pcall -local loadstring = loadstring --- @diagnostic disable-line +local loadstring = load local rawset = rawset local Public = {} diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index c654d924..d1647ff1 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -327,7 +327,7 @@ function Elements.science_table.refresh_row(science_table, row_data) Elements.production_label.refresh(row.used, row_data.used) end -do local _row_data = {} --- @type table +do local _row_data = {} --- @type table> --- Refresh the production tables for all online players function Elements.science_table.refresh_online() -- Refresh the row data for online forces From f39da0c585253ebc4ad2161cdc47b46d05b2e600 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:52:59 +0000 Subject: [PATCH 19/46] Type the last of the forward declarations Locals declared ahead of an assignment inside a callback were inferred as nil at every use site. The role event handlers are given a class so the table is not unified with the other module handler tables. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/search.lua | 2 +- exp_legacy/module/config/warnings.lua | 1 + exp_legacy/module/expcore/roles.lua | 5 +++-- exp_legacy/module/modules/control/warnings.lua | 2 +- exp_roles/module/events.lua | 7 ++++++- exp_scenario/module/control/deconstruction_log.lua | 4 +++- exp_scenario/module/gui/player_list.lua | 2 +- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/exp_commands/module/search.lua b/exp_commands/module/search.lua index 8e0176e9..a75de6a8 100644 --- a/exp_commands/module/search.lua +++ b/exp_commands/module/search.lua @@ -66,7 +66,7 @@ function Search.on_player_locale_changed(event) local ids = player.request_translations(required_translations) assert(ids, "Translation ids was nil") for i, command_name in ipairs(command_names) do - pending[ids[i]] = { locale, command_name } + pending[assert(ids[i])] = { locale, command_name } end end end diff --git a/exp_legacy/module/config/warnings.lua b/exp_legacy/module/config/warnings.lua index e098dc2f..b60e003b 100644 --- a/exp_legacy/module/config/warnings.lua +++ b/exp_legacy/module/config/warnings.lua @@ -2,6 +2,7 @@ -- @config Warnings return { + --- @type (LocalisedString[] | fun(player: LuaPlayer, by_player_name: string, number_of_warnings: number))[] actions = { --- @setting actions what actions are taking at number of warnings -- if a localized string is used then __1__ will by_player_name and __2__ will be the current warning count (auto inserted) { "warnings.received", "" }, diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index d3698f3e..529752d2 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -1115,8 +1115,9 @@ local function role_update(event) -- Updates the players permission group local highest = Roles.get_player_highest_role(player) if highest.permission_group then - if highest.permission_group[1] then - local group = game.permissions.get_group(highest.permission_group[2]) + local permission_group = highest.permission_group --[[@as [boolean, string] ]] + if permission_group[1] then + local group = game.permissions.get_group(permission_group[2]) if group then Groups.add_to_permission_group_async(group, player) end diff --git a/exp_legacy/module/modules/control/warnings.lua b/exp_legacy/module/modules/control/warnings.lua index 628fc8d0..6a0fa116 100644 --- a/exp_legacy/module/modules/control/warnings.lua +++ b/exp_legacy/module/modules/control/warnings.lua @@ -107,7 +107,7 @@ function Warnings.add_warning(player, by_player_name, reason) reason = reason or "None given." - local warning_count + local warning_count --- @type number PlayerWarnings:update(player.name, function(_, warnings) local warning = { by_player_name = by_player_name, diff --git a/exp_roles/module/events.lua b/exp_roles/module/events.lua index 6537b5c1..12d8456d 100644 --- a/exp_roles/module/events.lua +++ b/exp_roles/module/events.lua @@ -9,7 +9,10 @@ had, and event_handler expects `events` to be the handlers to register. local clusterio_api = require("modules/clusterio/api") local ExpRoles = require("modules/exp_roles/control") -return { +--- @class ExpRoles.EventHandlers +--- @field on_load fun() +--- @field events table +local handlers = { on_load = ExpRoles.on_load, events = { [clusterio_api.events.on_server_startup] = ExpRoles.on_server_startup, @@ -17,3 +20,5 @@ return { [defines.events.on_player_joined_game] = ExpRoles.on_player_joined_game, }, } + +return handlers diff --git a/exp_scenario/module/control/deconstruction_log.lua b/exp_scenario/module/control/deconstruction_log.lua index 087030ab..98ceb9ea 100644 --- a/exp_scenario/module/control/deconstruction_log.lua +++ b/exp_scenario/module/control/deconstruction_log.lua @@ -137,7 +137,9 @@ local function on_player_ammo_inventory_changed(event) if not player or not player.character then return end local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo)) - local item = character_ammo[player.character.selected_gun_index] + local gun_index = player.character.selected_gun_index + --- @cast gun_index uint + local item = character_ammo[gun_index] if not item or not item.valid or not item.valid_for_read then return end diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index a5db97b1..bb3a70e3 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -102,7 +102,7 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") local action_name = Elements.container.get_selected_action(player) local button_data = action_name and config.buttons[action_name] if button_data and button_data.reason_callback then - local reason = element.parent.entry.text + local reason = assert(assert(element.parent).entry).text --[[@as string?]] if reason == nil or not reason:find("%S") then reason = "no reason given" end button_data.reason_callback(player, reason) end From 1868616a4d8d34549a6fbd890e674353bcb45d3e Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:53:23 +0000 Subject: [PATCH 20/46] Note the emmylua setup for contributors Co-Authored-By: Claude Opus 5 (1M context) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d1e0254..2d2fcc38 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ All are welcome to make bug reports, feature requests, and pull requests for our For developers wanting to add features please follow these guidelines: - All lua code is documented using ldoc. +- Lua is checked with [emmylua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust), configured by `.emmyrc.json`. Install the EmmyLua extension rather than sumneko, and let the factoriomod-debug extension write your machine local library paths to `.luarc.json`, which is git ignored. Note that an inline cast must be written `--[[@as T]]`, the spaced form is not parsed. - Changes should be made on your own fork and merged into `main` through a pull request. - Each pull request should be limited to one feature or a few bug fixes and link to the related issue page. - Pull requests are automatically linted and documentation checked. From 3f46055b704a3475b022f46ab36665b53a816f8e Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:00:47 +0000 Subject: [PATCH 21/46] Enable need-check-nil and start the burn down `Roles.new_role` returned the result of `error`, which made it nullable, and `set_permission_group` returned nil for an unknown group, which broke the config chain with an index error rather than saying so. Positions and bounding boxes read back from the game always use the named members, so they are narrowed once where they enter a function. Co-Authored-By: Claude Opus 5 (1M context) --- .emmyrc.json | 2 -- exp_legacy/module/expcore/roles.lua | 5 ++--- exp_scenario/module/control/mine_depletion.lua | 16 +++++++++------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index 62b2e74a..c103023e 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -18,7 +18,6 @@ }, "$comment-disable": "Carried over from the luals config, which had these as editor only rather than checked in ci", "$comment-unnecessary-if": "Disabled by the config fmtk generates for itself, the api union types make it unreliable", - "$comment-need-check-nil": "To be enabled once the backlog is cleared, a third of it is MapPosition and BoundingBox being aliased as struct|[double, double]", "diagnostics": { "globals": [ "__DebugAdapter", "__Profiler" ], "disable": [ @@ -27,7 +26,6 @@ "invert-if", "missing-parameter", "missing-return-value", - "need-check-nil", "param-type-mismatch", "preferred-local-alias", "redefined-local", diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index 529752d2..540c42ea 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -719,7 +719,7 @@ local role = Roles.new_role('Moderator', 'Mod') ]] function Roles.new_role(name, short_hand) ExpUtil.assert_not_runtime() - if Roles.config.roles[name] then return error("Role name is non unique") end + if Roles.config.roles[name] then error("Role name is non unique") end local role = setmetatable({ name = name, short_hand = short_hand or name, @@ -896,8 +896,7 @@ function Roles._prototype:set_permission_group(name, use_factorio_api) if use_factorio_api then self.permission_group = { true, name } --[[@as [boolean, string] ]] else - local group = Groups.get_group_by_name(name) - if not group then return end + assert(Groups.get_group_by_name(name), "Permission group not found: " .. name) self.permission_group = name end return self diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 894c3dfb..996a6019 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -68,12 +68,13 @@ local function try_deconstruct_output_chest(entity) end -- Get all adjacent mining drills and inserters + local target_position = target.position --[[@as MapPosition.struct]] local entities = target.surface.find_entities_filtered{ type = { "mining-drill", "inserter" }, to_be_deconstructed = false, area = { - { target.position.x - 1, target.position.y - 1 }, - { target.position.x + 1, target.position.y + 1 } + { target_position.x - 1, target_position.y - 1 }, + { target_position.x + 1, target_position.y + 1 } }, } @@ -159,14 +160,14 @@ local function try_deconstruct_miner(entity) end -- Build pipes if the miner used fluid - local position = entity.position + local position = entity.position --[[@as MapPosition.struct]] local create_entity_position = { x = position.x, y = position.y } local create_entity_param = { name = "entity-ghost", inner_name = "pipe", force = entity.force, position = create_entity_position } local create_entity = surface.create_entity create_entity(create_entity_param) -- Find all the entities to connect to - local bounding_box = entity.bounding_box + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local search_area = { { bounding_box.left_top.x - 1, bounding_box.left_top.y - 1 }, { bounding_box.right_bottom.x + 1, bounding_box.right_bottom.y + 1 }, @@ -246,7 +247,7 @@ local function on_resource_depleted(event) end -- Find all mining drills within the area - local position = resource.position + local position = resource.position --[[@as MapPosition.struct]] local drills = resource.surface.find_entities_filtered{ type = "mining-drill", area = { @@ -258,8 +259,9 @@ local function on_resource_depleted(event) -- Check which could have reached this resource for _, drill in pairs(drills) do local radius = drill.prototype.mining_drill_radius - local dx = math.abs(drill.position.x - position.x) - local dy = math.abs(drill.position.y - position.y) + local drill_position = drill.position --[[@as MapPosition.struct]] + local dx = math.abs(drill_position.x - position.x) + local dy = math.abs(drill_position.y - position.y) if dx <= radius and dy <= radius then try_deconstruct_miner(drill) end From a4213dadf508db851ec5aed51205b2c49592d52d Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:02:07 +0000 Subject: [PATCH 22/46] Narrow positions and bounding boxes at the boundary Values read back from the game always use the named members, so they are narrowed once where they enter a function rather than at each use. `LuaControl.force` is a ForceID union, so reading a force only method off it needs the cast the rest of the repo already uses. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/commands/artillery.lua | 11 +++++---- .../module/control/station_auto_name.lua | 10 ++++---- exp_util/module/flying_text.lua | 24 ++++++++++++------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/exp_scenario/module/commands/artillery.lua b/exp_scenario/module/commands/artillery.lua index 04c94fe3..5a2e16bf 100644 --- a/exp_scenario/module/commands/artillery.lua +++ b/exp_scenario/module/commands/artillery.lua @@ -15,11 +15,11 @@ local abs = math.abs local commands = {} --- @param player LuaPlayer ---- @param area BoundingBox +--- @param area ExpUtil_AABB.Box --- @return boolean local function location_break(player, area) local surface = player.surface -- Allow remote view - local is_charted = player.force.is_chunk_charted + local is_charted = (player.force --[[@as LuaForce]]).is_chunk_charted if is_charted(surface, { x = floor(area.left_top.x / 32), y = floor(area.left_top.y / 32) }) then return true elseif is_charted(surface, { x = floor(area.left_top.x / 32), y = floor(area.right_bottom.y / 32) }) then @@ -63,13 +63,14 @@ SelectArea:on_selection(function(event) } local count = 0 - local hits = {} --- @type MapPosition[] + local hits = {} --- @type MapPosition.struct[] for _, entity in ipairs(entities) do local skip = false + local entity_position = entity.position --[[@as MapPosition.struct]] for _, pos in ipairs(hits) do - local x = abs(entity.position.x - pos.x) - local y = abs(entity.position.y - pos.y) + local x = abs(entity_position.x - pos.x) + local y = abs(entity_position.y - pos.y) if x * x + y * y < 36 then skip = true break diff --git a/exp_scenario/module/control/station_auto_name.lua b/exp_scenario/module/control/station_auto_name.lua index fb9b8905..de8e5595 100644 --- a/exp_scenario/module/control/station_auto_name.lua +++ b/exp_scenario/module/control/station_auto_name.lua @@ -57,7 +57,7 @@ local function rename_station(event) -- Find the closest resource local icon = "" local item_name = "" - local bounding_box = entity.bounding_box + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local resources = entity.surface.find_entities_filtered{ position = entity.position, radius = 250, type = "resource" } if #resources > 0 then local closest_recourse --- @type LuaEntity? @@ -66,8 +66,9 @@ local function rename_station(event) -- Check which recourse is closest for _, resource in ipairs(resources) do - local dx = px - resource.bounding_box.left_top.x - local dy = py - resource.bounding_box.left_top.y + local resource_box = resource.bounding_box --[[@as ExpUtil_AABB.Box]] + local dx = px - resource_box.left_top.x + local dy = py - resource_box.left_top.y local distance = (dx * dx) + (dy * dy) if distance < closest_distance then closest_distance = distance @@ -78,7 +79,8 @@ local function rename_station(event) -- Set the item name and icon if closest_recourse then item_name = closest_recourse.name:gsub("^%l", string.upper):gsub("-", " ") -- remove dashes and making first letter capital - local product = closest_recourse.prototype.mineable_properties.products[1] + local products = assert(closest_recourse.prototype.mineable_properties.products) + local product = assert(products[1]) icon = string.format("[img=%s.%s]", product.type, product.name) end end diff --git a/exp_util/module/flying_text.lua b/exp_util/module/flying_text.lua index f7ecaec3..73e8d9f0 100644 --- a/exp_util/module/flying_text.lua +++ b/exp_util/module/flying_text.lua @@ -42,12 +42,14 @@ end --- @param options FlyingText.create_above_entity_param function FlyingText.create_above_entity(options) local entity = assert(options.target_entity, "A target entity is required") - local size_y = entity.bounding_box.left_top.y - entity.bounding_box.right_bottom.y + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y + local position = entity.position --[[@as MapPosition.struct]] local offset = options.offset or { x = 0, y = 0 } options.position = { - x = offset.x + entity.position.x, - y = offset.y + entity.position.y + size_y * 0.25, + x = offset.x + position.x, + y = offset.y + position.y + size_y * 0.25, } FlyingText.create(options) @@ -62,12 +64,14 @@ end function FlyingText.create_above_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end - local size_y = entity.bounding_box.left_top.y - entity.bounding_box.right_bottom.y + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y + local position = entity.position --[[@as MapPosition.struct]] local offset = options.offset or { x = 0, y = 0 } options.position = { - x = offset.x + entity.position.x, - y = offset.y + entity.position.y + size_y * 0.25, + x = offset.x + position.x, + y = offset.y + position.y + size_y * 0.25, } FlyingText.create(options) @@ -82,13 +86,15 @@ end function FlyingText.create_as_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end - local size_y = entity.bounding_box.left_top.y - entity.bounding_box.right_bottom.y + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y + local position = entity.position --[[@as MapPosition.struct]] local offset = options.offset or { x = 0, y = 0 } options.color = player.chat_color options.position = { - x = offset.x + entity.position.x, - y = offset.y + entity.position.y + size_y * 0.25, + x = offset.x + position.x, + y = offset.y + position.y + size_y * 0.25, } FlyingText.create(options) From bad00a9cefd491e4a750d3f2b9f8d2d52e617469 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:04:22 +0000 Subject: [PATCH 23/46] Describe the task list element data by key type A union of table types makes every lookup optional. Separate index signatures on one class say the same thing without the nil. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/gui/task_list.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/exp_scenario/module/gui/task_list.lua b/exp_scenario/module/gui/task_list.lua index e39efa89..339a1bab 100644 --- a/exp_scenario/module/gui/task_list.lua +++ b/exp_scenario/module/gui/task_list.lua @@ -252,7 +252,7 @@ Elements.task_list = Gui.define("task_list/task_list") --- @param task_list LuaGuiElement --- @param task ExpGui_TaskList.Task function Elements.task_list.add_task(task_list, task) - local container = assert(task_list.parent.parent.parent) + local container = assert(assert(assert(task_list.parent).parent).parent) local view_task_buttons = Elements.task_list.data[task_list] view_task_buttons[task.id] = Elements.view_task_button(task_list, task, container) end @@ -532,7 +532,7 @@ Elements.task_message_textfield = Gui.define("task_list/task_message_textfield") } :on_text_changed(function(def, player, task_message_textfield) --- @cast def ExpGui_TaskList.elements.task_message_textfield - local confirm_task_button = def.data[task_message_textfield].confirm_task_button + local confirm_task_button = assert(def.data[task_message_textfield].confirm_task_button) confirm_task_button.enabled = string.len(task_message_textfield.text) > 5 end) --[[@as any]] @@ -549,7 +549,7 @@ end function Elements.task_message_textfield.refresh(task_message_textfield, task) local elements = Elements.task_message_textfield.data[task_message_textfield] local message = task.new and "" or task.title .. "\n" .. task.body - elements.confirm_task_button.enabled = string.len(message) > 5 + assert(elements.confirm_task_button).enabled = string.len(message) > 5 task_message_textfield.text = message task_message_textfield.focus() end @@ -755,9 +755,14 @@ end --- @field view_task_footer LuaGuiElement --- @field edit_task_footer LuaGuiElement +--- @class ExpGui_TaskList.elements.container.data +--- @field global_data { next_task_id: number } +--- @field [LuaGuiElement] ExpGui_TaskList.elements.container.elements +--- @field [LuaForce] ExpGui_TaskList.Task[] + --- Container added to the left gui flow --- @class ExpGui_TaskList.elements.container: ExpElement ---- @field data table | table | table<"global_data", { next_task_id: number }> +--- @field data ExpGui_TaskList.elements.container.data Elements.container = Gui.define("task_list/container") :track_all_elements() :draw(function(def, parent) From ce3b4905540c7757ee88e8d95f774ba65928e439 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:04:44 +0000 Subject: [PATCH 24/46] Assert the debug frames exist `debug.getinfo` is optional, but these helpers always ask for a frame which is on the stack. Co-Authored-By: Claude Opus 5 (1M context) --- exp_util/module/module_exports.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exp_util/module/module_exports.lua b/exp_util/module/module_exports.lua index 5bddd019..5203d97e 100644 --- a/exp_util/module/module_exports.lua +++ b/exp_util/module/module_exports.lua @@ -92,7 +92,7 @@ local assert_argument_fmt = "Bad argument #%d to %s; %s expected to be of type % function ExpUtil.assert_argument_type(arg_value, type_name, arg_index, arg_name) local failed, actual_type = check_type(arg_value, type_name) if failed then - local func_name = getinfo(2, "n").name or "" + local func_name = assert(getinfo(2, "n")).name or "" error(assert_argument_fmt:format(arg_index, func_name, arg_name or "Argument", type_name, actual_type), 2) end end @@ -136,7 +136,7 @@ end --- @param level number? The level of the stack to get the file of, a value of 1 is the caller of this function --- @return string # The relative filepath of the given stack frame function ExpUtil.safe_file_path(level) - local debug_info = getinfo((level or 1) + 1, "Sn") + local debug_info = assert(getinfo((level or 1) + 1, "Sn")) local safe_source = debug_info.source:find("@__level__") return safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source end @@ -145,7 +145,7 @@ end --- @param level number? The level of the stack to get the module of, a value of 1 is the caller of this function --- @return string # The name of the module at the given stack frame function ExpUtil.get_module_name(level) - local file_within_module = getinfo((level or 1) + 1, "S").short_src:sub(19, -5) + local file_within_module = assert(getinfo((level or 1) + 1, "S")).short_src:sub(19, -5) local next_slash = file_within_module:find("/") if next_slash then return file_within_module:sub(1, next_slash - 1) @@ -159,7 +159,7 @@ end --- @param raw boolean? When true there will not be any < > around the name --- @return string # The name of the function at the given stack frame or provided as an argument function ExpUtil.get_function_name(func, raw) - local debug_info = getinfo(func, "Sn") + local debug_info = assert(getinfo(func, "Sn")) local safe_source = debug_info.source:find("@__level__") local file_name = safe_source == 1 and debug_info.source:sub(12, -5) or debug_info.source local func_name = debug_info.name or debug_info.linedefined @@ -172,7 +172,7 @@ end --- @param raw boolean? When true there will not be any < > around the name --- @return string # The relative filepath of the given stack frame function ExpUtil.get_current_line(level, raw) - local debug_info = getinfo((level or 1) + 1, "Snl") + local debug_info = assert(getinfo((level or 1) + 1, "Snl")) local safe_source = debug_info.source:find("@__level__") local file_path = safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source if raw then return file_path .. ":" .. debug_info.currentline end From 7bb874f73fa0dfc36e7e7e89aa0aa2734b7fe7c2 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:05:50 +0000 Subject: [PATCH 25/46] Describe the legacy role type get_player_roles always returns at least the default or root role, so get_player_highest_role never returns nil, and callers were right to treat it that way. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/expcore/roles.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index 540c42ea..ebcdfc2d 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -117,6 +117,21 @@ local Groups = require("modules.exp_legacy.expcore.permission_groups") local Colours = ExpUtil.color local write_json = ExpUtil.write_json +--- @class Roles.Role +--- @field name string +--- @field short_hand string +--- @field index number Position within the role order +--- @field allowed_actions table +--- @field allow_all_actions boolean +--- @field flags table +--- @field disallowed_actions table? +--- @field permission_group ([boolean, string] | string)? +--- @field custom_tag string? +--- @field custom_color Color? +--- @field parent string? +--- @field block_auto_assign boolean? +--- @field auto_assign_condition function? + local Roles = { _prototype = {}, config = { @@ -348,6 +363,7 @@ end local role = Roles.get_player_highest_role(game.player) ]] +--- @return Roles.Role function Roles.get_player_highest_role(player) local roles = Roles.get_player_roles(player) local highest @@ -357,7 +373,7 @@ function Roles.get_player_highest_role(player) end end - return highest + return assert(highest, "Player has no roles") end --- Assignment. From d25fc42538e1f512ffad2753634cda047665a2cd Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:07:11 +0000 Subject: [PATCH 26/46] Assert vlayer runtime invariants and type its storage The interfaces are chests and constant combinators, so the inventory, logistic sections and first circuit section always exist. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/modules/control/vlayer.lua | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index 3730834d..2378c537 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -28,10 +28,10 @@ local vlayer_data = { capacity = 0, }, storage = { - items = {}, - power_items = {}, + items = {}, --- @type table + power_items = {}, --- @type table energy = 0, - unallocated = {}, + unallocated = {}, --- @type table }, surface = table.deep_copy(config.surface), } @@ -45,7 +45,7 @@ for name, properties in pairs(config.allowed_items) do if properties.power then vlayer_data.storage.power_items[name] = { - value = properties.fuel_value * 1000000, + value = assert(properties.fuel_value) * 1000000, count = 0, } end @@ -291,7 +291,7 @@ function vlayer.remove_item(item_name, count) if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then -- Remove from the unallocated storage first - remove_unallocated = math.min(count, vlayer_data.storage.unallocated[item_name]) + remove_unallocated = math.min(count, assert(vlayer_data.storage.unallocated[item_name])) if remove_unallocated > 0 then vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - count @@ -361,7 +361,7 @@ local function handle_input_interfaces() if not interface.valid then vlayer_data.entity_interfaces.storage_input[index] = nil else - local inventory = interface.get_inventory(defines.inventory.chest) + local inventory = assert(interface.get_inventory(defines.inventory.chest)) for _, v in pairs(inventory.get_contents()) do if config.allowed_items[v.name] then @@ -384,8 +384,9 @@ local function handle_input_interfaces() if count_deduct and count_add then if config.allowed_items[v.name].modded then + local modded_item = assert(config.modded_items[v.name]) if config.modded_auto_downgrade then - vlayer.insert_item(config.modded_items[v.name].base_game_equivalent, count_add * config.modded_items[v.name].multiplier) + vlayer.insert_item(modded_item.base_game_equivalent, count_add * modded_item.multiplier) else vlayer.insert_item(v.name, count_add) end @@ -439,14 +440,14 @@ local function handle_output_interfaces() if not interface.valid then vlayer_data.entity_interfaces.storage_output[index] = nil else - local inventory = interface.get_inventory(defines.inventory.chest) - local inventory_request_sections = interface.get_logistic_sections().sections + local inventory = assert(interface.get_inventory(defines.inventory.chest)) + local inventory_request_sections = assert(interface.get_logistic_sections()).sections for i = 1, #inventory_request_sections do for _, v in pairs(inventory_request_sections[i].filters) do if v.value and config.allowed_items[v.value.name] then local current_amount = inventory.get_item_count(v.value.name) - local request_amount = math.min(v.min - current_amount, vlayer_data.storage.items[v.value.name]) + local request_amount = math.min(assert(v.min) - current_amount, vlayer_data.storage.items[v.value.name]) if request_amount > 0 and inventory.can_insert{ name = v.value.name, count = request_amount } then local removed_item_count = vlayer.remove_item(v.value.name, request_amount) @@ -586,7 +587,7 @@ local function handle_circuit_interfaces() if control.sections_count == 0 then control.add_section() end - local circuit_oc = control.sections[1] + local circuit_oc = assert(control.sections[1]) local signal_index = 1 local circuit = vlayer.get_circuits() From 981eed7f3413068af099a614abab577ebd6cac99 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:07:39 +0000 Subject: [PATCH 27/46] Assert module inserter row and selector lookups Rows and selectors are created alongside the machine selector, so they exist for anything already in the table. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/gui/module_inserter.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exp_scenario/module/gui/module_inserter.lua b/exp_scenario/module/gui/module_inserter.lua index a263932f..74e1b287 100644 --- a/exp_scenario/module/gui/module_inserter.lua +++ b/exp_scenario/module/gui/module_inserter.lua @@ -177,7 +177,7 @@ end --- @param machine_selector LuaGuiElement function Elements.module_table.remove_row(module_table, machine_selector) local rows = Elements.module_table.data[module_table] - local row = rows[machine_selector.index] + local row = assert(rows[machine_selector.index]) rows[machine_selector.index] = nil Gui.destroy_if_valid(machine_selector) for _, separator in pairs(row.row_separators) do @@ -193,7 +193,7 @@ end --- @param machine_selector LuaGuiElement function Elements.module_table.reset_row(module_table, machine_selector) local rows = Elements.module_table.data[module_table] - local row = rows[machine_selector.index] + local row = assert(rows[machine_selector.index]) for _, separator in pairs(row.row_separators) do separator.visible = false @@ -211,9 +211,9 @@ end --- @param machine_name string function Elements.module_table.refresh_row(module_table, machine_selector, machine_name) local rows = Elements.module_table.data[module_table] - local row = rows[machine_selector.index] + local row = assert(rows[machine_selector.index]) - local active_module_count = prototypes.entity[machine_name].module_inventory_size + local active_module_count = assert(prototypes.entity[machine_name].module_inventory_size) local visible_row_count = math.ceil(active_module_count / config.module_slots_per_row) local visible_module_count = visible_row_count * config.module_slots_per_row local module_elem_value = { name = config.machines[machine_name].module } @@ -348,7 +348,7 @@ SelectArea:on_selection(function(event, module_table) -- Get all the modules selected for i = 1, entity_prototype.module_inventory_size do - local module_selector = module_selectors[i] + local module_selector = assert(module_selectors[i]) local module = module_selector.elem_value --[[@as { name: string, quality: string }?]] if module then -- Module selected, add it the module arrays @@ -427,7 +427,7 @@ local function on_entity_settings_pasted(event) -- Attempt to rotate a machine to match the source machine if (source.name == destination.name or source.prototype.fast_replaceable_group == destination.prototype.fast_replaceable_group) then if source.supports_direction and destination.supports_direction and source.type ~= "transport-belt" then - local destination_box = destination.bounding_box + local destination_box = destination.bounding_box --[[@as ExpUtil_AABB.Box]] local ltx = destination_box.left_top.x local lty = destination_box.left_top.y From b75e283fc028aa42a2cb8d73ce8eba0c02adbff3 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:08:44 +0000 Subject: [PATCH 28/46] Narrow the remaining position, box and colour reads Color.struct leaves every channel optional, so the rainbow command declares its own fully populated colour. Co-Authored-By: Claude Opus 5 (1M context) --- .../module/commands/protected_entities.lua | 12 +++++++----- exp_scenario/module/commands/rainbow.lua | 14 ++++++++++---- exp_scenario/module/control/degrading_tiles.lua | 7 ++++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/exp_scenario/module/commands/protected_entities.lua b/exp_scenario/module/commands/protected_entities.lua index 34426b24..b84d14d7 100644 --- a/exp_scenario/module/commands/protected_entities.lua +++ b/exp_scenario/module/commands/protected_entities.lua @@ -37,7 +37,7 @@ end --- Get the key used in protected_areas --- TODO expose this from EntityProtection ---- @param area BoundingBox +--- @param area ExpUtil_AABB.Box --- @return string local function get_area_key(area) return format_string("%i,%i", floor(area.left_top.x), floor(area.left_top.y)) @@ -49,13 +49,15 @@ end local function show_protected_entity(player, entity) local key = get_entity_key(entity) if renders[player.index][key] then return end - local rb = entity.selection_box.right_bottom + local selection_box = entity.selection_box --[[@as ExpUtil_AABB.Box]] + local rb = selection_box.right_bottom + local position = entity.position --[[@as MapPosition.struct]] renders[player.index][key] = rendering.draw_sprite{ sprite = "utility/notification", target = entity, target_offset = { - (rb.x - entity.position.x) * 0.75, - (rb.y - entity.position.y) * 0.75, + (rb.x - position.x) * 0.75, + (rb.y - position.y) * 0.75, }, x_scale = 2, y_scale = 2, @@ -67,7 +69,7 @@ end --- Show a protected area to a player --- @param player LuaPlayer --- @param surface LuaSurface ---- @param area BoundingBox +--- @param area ExpUtil_AABB.Box local function show_protected_area(player, surface, area) local key = get_area_key(area) if renders[player.index][key] then return end diff --git a/exp_scenario/module/commands/rainbow.lua b/exp_scenario/module/commands/rainbow.lua index 1084f84c..c5fd1a36 100644 --- a/exp_scenario/module/commands/rainbow.lua +++ b/exp_scenario/module/commands/rainbow.lua @@ -6,6 +6,12 @@ local Commands = require("modules/exp_commands") local format_player_name = Commands.format_player_name_locale local format_text = Commands.format_rich_text_color +--- A colour with every channel set, Color.struct leaves them optional +--- @class ExpCommands_Rainbow.Color +--- @field r number +--- @field g number +--- @field b number + --- Wraps one component into the next --- @param c1 number --- @param c2 number @@ -21,8 +27,8 @@ local function step_component(c1, c2) end --- Wraps all components of a colour ensuring it remains valid ---- @param color Color ---- @return Color +--- @param color ExpCommands_Rainbow.Color +--- @return ExpCommands_Rainbow.Color local function step_color(color) color.r, color.g = step_component(color.r, color.g) color.g, color.b = step_component(color.g, color.b) @@ -32,9 +38,9 @@ local function step_color(color) end --- Get the next colour in the rainbow by the given step ---- @param color Color +--- @param color ExpCommands_Rainbow.Color --- @param step number ---- @return Color +--- @return ExpCommands_Rainbow.Color local function next_color(color, step) step = step or 0.1 local new_color = { r = 0, g = 0, b = 0 } diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index b83090a1..9eec9c26 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -32,8 +32,9 @@ local function degrade_entity(entity) local tiles = {} local surface = entity.surface - local left_top = entity.bounding_box.left_top - local right_bottom = entity.bounding_box.right_bottom + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local left_top = bounding_box.left_top + local right_bottom = bounding_box.right_bottom for x = left_top.x, right_bottom.x do for y = left_top.y, right_bottom.y do local tile = surface.get_tile(x, y) @@ -84,7 +85,7 @@ local function on_player_changed_position(event) if player.controller_type ~= defines.controllers.character then return end local surface = player.physical_surface - local position = player.physical_position + local position = player.physical_position --[[@as MapPosition.struct]] local strength = get_tile_strength(surface, position) if not strength then return end From 7d8de94c2f8e874314fd5e8c74eb3e3c396f9664 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:09:31 +0000 Subject: [PATCH 29/46] Assert row lookups and the position shorthand spawn_area accepts both position forms on purpose, so it asserts that one of the two is present rather than narrowing the type. Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/control/degrading_tiles.lua | 4 ++-- exp_scenario/module/control/spawn_area.lua | 8 ++++---- exp_scenario/module/gui/production_stats.lua | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index 9eec9c26..6a8282b0 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -16,7 +16,7 @@ end --- Replace a tile with the next tile in the degrade chain --- @param surface LuaSurface ---- @param position MapPosition +--- @param position MapPosition.struct local function degrade_tile(surface, position) local tile = surface.get_tile(position) local tile_name = tile.name @@ -58,7 +58,7 @@ end --- Gets the average tile strengths around position --- @param surface LuaSurface ---- @param position MapPosition +--- @param position MapPosition.struct --- @return number? local function get_tile_strength(surface, position) local tile = surface.get_tile(position) diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index cceb15fa..365aba9d 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -10,8 +10,8 @@ local config = require("modules.exp_legacy.config.spawn_area") --- @return MapPosition.struct local function apply_offset(position, offset) return { - x = (position.x or position[1]) + (offset.x or offset[1]), - y = (position.y or position[2]) + (offset.y or offset[2]) + x = assert(position.x or position[1]) + assert(offset.x or offset[1]), + y = assert(position.y or position[2]) + assert(offset.y or offset[2]) } end @@ -21,8 +21,8 @@ end --- @param x_index number --- @param y_index number local function apply_offset_to_array(positions, offset, x_index, y_index) - local x = (offset.x or offset[1]) - local y = (offset.y or offset[2]) + local x = assert(offset.x or offset[1]) + local y = assert(offset.y or offset[2]) for _, position in ipairs(positions) do position[x_index] = position[x_index] + x position[y_index] = position[y_index] + y diff --git a/exp_scenario/module/gui/production_stats.lua b/exp_scenario/module/gui/production_stats.lua index 10963298..bc6cac8a 100644 --- a/exp_scenario/module/gui/production_stats.lua +++ b/exp_scenario/module/gui/production_stats.lua @@ -184,7 +184,7 @@ end --- @param item_selector LuaGuiElement function Elements.production_table.remove_row(production_table, item_selector) local rows = Elements.production_table.data[production_table].rows - local row = rows[item_selector.index] + local row = assert(rows[item_selector.index]) rows[item_selector.index] = nil Gui.destroy_if_valid(item_selector) for _, element in pairs(row) do @@ -197,7 +197,7 @@ end --- @param item_selector LuaGuiElement function Elements.production_table.reset_row(production_table, item_selector) local rows = Elements.production_table.data[production_table].rows - local row = rows[item_selector.index] + local row = assert(rows[item_selector.index]) row.production.caption = "0.00" row.consumption.caption = "0.00" row.net.caption = "0.00" @@ -210,7 +210,7 @@ end --- @param row_data ExpGui_ProductionStats.elements.production_table.row_data function Elements.production_table.refresh_row(production_table, item_selector, row_data) local rows = Elements.production_table.data[production_table].rows - local row = rows[item_selector.index] + local row = assert(rows[item_selector.index]) row.production.caption = row_data.production row.consumption.caption = row_data.consumption row.net.caption = row_data.net From d44381bac105979d5b6370c4c64333007e7114b8 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:09:58 +0000 Subject: [PATCH 30/46] Assert the vlayer gui lookups Dropdowns are siblings of the button that reads them, and the loop bounds come from the interface count. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/modules/gui/vlayer.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua index 47d1abe1..d2daf0ed 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -90,7 +90,7 @@ SelectArea:on_selection(function(event) local entities --- @type LuaEntity[] if config.power_on_space and event.surface and event.surface.platform and target == "energy" then - if player.force.technologies[config.power_on_space_research.name].level >= config.power_on_space_research.level then + if (player.force --[[@as LuaForce]]).technologies[config.power_on_space_research.name].level >= config.power_on_space_research.level then entities = event.surface.find_entities_filtered{ area = area, name = "constant-combinator", force = player.force } else player.print{ "vlayer.power-on-space-research", config.power_on_space_research.name, config.power_on_space_research.level } @@ -116,7 +116,7 @@ SelectArea:on_selection(function(event) local e_pos = { x = string.format("%.1f", e.position.x), y = string.format("%.1f", e.position.y) } local e_circ = nil -- e.get_wire_connectors{ or_create = false } - if e.name and e.name == "steel-chest" and (not e.get_inventory(defines.inventory.chest).is_empty()) then + if e.name and e.name == "steel-chest" and (not assert(e.get_inventory(defines.inventory.chest)).is_empty()) then player.print{ "vlayer.steel-chest-empty" } return end @@ -325,7 +325,9 @@ local function vlayer_gui_list_refresh(player) local interface = vlayer.get_interfaces()[vlayer_control_type_list[target]] for i = 1, vlayer.get_interface_counts()[vlayer_control_type_list[target]], 1 do - table.insert(full_list, i .. " X " .. interface[i].position.x .. " Y " .. interface[i].position.y) + local entity = assert(interface[i]) + local entity_position = entity.position --[[@as MapPosition.struct]] + table.insert(full_list, i .. " X " .. entity_position.x .. " Y " .. entity_position.y) end disp[vlayer_gui_control_list.name].items = full_list @@ -379,8 +381,8 @@ local vlayer_gui_control_see = Gui.define("vlayer_gui_control_see") }:style{ width = 200, }:on_click(function(def, player, element, event) - local target = element.parent[vlayer_gui_control_type.name].selected_index - local n = element.parent[vlayer_gui_control_list.name].selected_index + local target = assert(assert(element.parent)[vlayer_gui_control_type.name]).selected_index + local n = assert(assert(element.parent)[vlayer_gui_control_list.name]).selected_index if target and vlayer_control_type_list[target] and n > 0 then local i = vlayer.get_interfaces() @@ -423,8 +425,8 @@ local vlayer_gui_control_remove = Gui.define("vlayer_gui_control_remove") }:style{ width = 200, }:on_click(function(def, player, element) - local target = element.parent[vlayer_gui_control_type.name].selected_index - local n = element.parent[vlayer_gui_control_list.name].selected_index + local target = assert(assert(element.parent)[vlayer_gui_control_type.name]).selected_index + local n = assert(assert(element.parent)[vlayer_gui_control_list.name]).selected_index if target and vlayer_control_type_list[target] and n > 0 then local i = vlayer.get_interfaces() From aca8bc79ae24356c6da0cd6c2b7c51a9148a7d98 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:10:05 +0000 Subject: [PATCH 31/46] Assert the vlayer gui lookups Dropdowns are siblings of the button that reads them, and the loop bounds come from the interface count. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/modules/gui/vlayer.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua index d2daf0ed..c83c8729 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -432,7 +432,8 @@ local vlayer_gui_control_remove = Gui.define("vlayer_gui_control_remove") local i = vlayer.get_interfaces() if i and i[vlayer_control_type_list[target]] then - local interface_type, interface_surface, interface_position = vlayer.remove_interface(i[vlayer_control_type_list[target]][n].surface, i[vlayer_control_type_list[target]][n].position) + local entity = assert(i[vlayer_control_type_list[target]][n]) + local interface_type, interface_surface, interface_position = vlayer.remove_interface(entity.surface, entity.position) if interface_type then game.print{ "vlayer.interface-result", player.name, pos_to_gps_string(interface_position, interface_surface.name), { "vlayer.result-remove" }, { "vlayer.control-type-" .. interface_type } } From 0b31a7080147b077159bbbecb3437f0ca52ddcc7 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:10:42 +0000 Subject: [PATCH 32/46] Type forward declared handlers and assert element parents The player list action setter runs during control setup, before any of the callbacks that use it. Co-Authored-By: Claude Opus 5 (1M context) --- exp_gui/module/toolbar.lua | 13 +++++++------ .../module/config/gui/player_list_actions.lua | 3 ++- exp_legacy/module/modules/gui/warp-list.lua | 18 +++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/exp_gui/module/toolbar.lua b/exp_gui/module/toolbar.lua index a1f438db..e4a3ebe2 100644 --- a/exp_gui/module/toolbar.lua +++ b/exp_gui/module/toolbar.lua @@ -335,9 +335,10 @@ Below here is the toolbar settings GUI and its associated functions --- @param dst LuaGuiElement local function copy_style(src, dst) dst.style = src.style.name - dst.style.height = toolbar_button_small - dst.style.width = toolbar_button_small - dst.style.padding = -2 + local style = dst.style --[[@as LuaStyle]] + style.height = toolbar_button_small + style.width = toolbar_button_small + style.padding = -2 end --- Reorder the buttons relative to each other, this will update the datastore @@ -365,7 +366,7 @@ local function move_toolbar_button(player, item, offset) local other_element = Gui.get_left_element(other_left_element, player) local left_index = element.get_index_in_parent() local other_index = other_element.get_index_in_parent() - element.parent.swap_children(left_index, other_index) + assert(element.parent).swap_children(left_index, other_index) end -- If we are moving in/out of first/last place we need to update the move buttons @@ -620,7 +621,7 @@ elements.move_item_up = Gui.define("move_item_up") size = toolbar_button_small, }) :on_click(function(def, player, element) - local item = assert(element.parent.parent) + local item = assert(assert(element.parent).parent) move_toolbar_button(player, item, -1) end) @@ -635,7 +636,7 @@ elements.move_item_down = Gui.define("move_item_down") size = toolbar_button_small, }) :on_click(function(def, player, element) - local item = assert(element.parent.parent) + local item = assert(assert(element.parent).parent) move_toolbar_button(player, item, 1) end) diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_legacy/module/config/gui/player_list_actions.lua index c7cb7919..c6bb968c 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_legacy/module/config/gui/player_list_actions.lua @@ -15,7 +15,8 @@ local Colors = require("modules/exp_util/include/color") local format_player_name = ExpUtil.format_player_name_locale --- Accessors injected by the gui so the actions can read the selected player and set the selected action -local get_selected_player, set_selected_action +local get_selected_player --- @type fun(player: LuaPlayer): LuaPlayer +local set_selected_action --- @type fun(player: LuaPlayer, action: string?) local function set_accessors(player_getter, action_setter) get_selected_player, set_selected_action = player_getter, action_setter end diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index 10fcd593..bcb974ef 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -180,7 +180,7 @@ local warp_icon_button = Gui.define("warp_icon_button") :style(Styles.sprite32) :on_click(function(def, player, element) if element.type == "choose-elem-button" then return end - local warp_id = element.parent.caption + local warp_id = assert(element.parent).caption Warps.teleport_player(warp_id, player) -- Reset the warp cooldown if the player does not have unlimited warps @@ -226,7 +226,7 @@ local warp_label = Gui.define("warp_label") horizontally_stretchable = true, } :on_click(function(def, player, element) - local warp_id = element.parent.caption + local warp_id = assert(element.parent).caption local warp = Warps.get_warp(warp_id) player.set_controller{ type = defines.controllers.remote, position = warp.position, surface = warp.surface } end) @@ -271,9 +271,9 @@ local warp_textfield = Gui.define("warp_textfield") right_margin = 2, } :on_confirmed(function(def, player, element) - local warp_id = element.parent.caption + local warp_id = assert(element.parent).caption local warp_name = element.text - local warp_icon = element.parent.parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[@as SignalID]] + local warp_icon = assert(element.parent).parent["icon-" .. warp_id][warp_icon_editing.name].elem_value --[[@as SignalID]] if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) @@ -315,7 +315,7 @@ local cancel_edit_button = Gui.define("cancel_edit_button") } :style(Styles.sprite22) :on_click(function(def, player, element) - local warp_id = element.parent.caption + local warp_id = assert(element.parent).caption -- Check if this is the first edit, if so remove the warp. local warp = Warps.get_warp(warp_id) if warp.updates == 1 then @@ -337,7 +337,7 @@ local remove_warp_button = Gui.define("remove_warp_button") } :style(Styles.sprite22) :on_click(function(def, player, element) - local warp_id = element.parent.caption + local warp_id = assert(element.parent).caption Warps.remove_warp(warp_id) end) @@ -353,7 +353,7 @@ local edit_warp_button = Gui.define("edit_warp_button") } :style(Styles.sprite22) :on_click(function(def, player, element) - local warp_id = element.parent.caption + local warp_id = assert(element.parent).caption Warps.set_editing(warp_id, player.name, true) end) @@ -433,8 +433,8 @@ local function update_warp_elements(element, warp, warp_player_is_on, on_cooldow -- Check if button element is valid if not element or not element.valid then return end - local label_style = element.parent.parent["name-" .. warp.warp_id][warp_label.name].style - local warp_status_element = element.parent.parent["name-" .. warp.warp_id][warp_status.name] + local label_style = assert(element.parent).parent["name-" .. warp.warp_id][warp_label.name].style + local warp_status_element = assert(element.parent).parent["name-" .. warp.warp_id][warp_status.name] -- If player is not on a warp if not warp_player_is_on then From f9ce3c4145aca7094a12ff06bc70329c9a53f0ee Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:11:16 +0000 Subject: [PATCH 33/46] Assert row lookups and narrow force and style reads Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/modules/gui/warp-list.lua | 4 ++-- exp_scenario/module/commands/ratio.lua | 4 ++-- exp_scenario/module/control/death_markers.lua | 2 +- exp_scenario/module/control/spawn_area.lua | 2 +- exp_scenario/module/gui/research_milestones.lua | 2 +- exp_scenario/module/gui/science_production.lua | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index bcb974ef..29c5a18a 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -799,7 +799,7 @@ Event.on_nth_tick(math.floor(60 / config.update_smoothing), function() end -- Check if the player is within range - local warp_pos = warp.position + local warp_pos = warp.position --[[@as MapPosition.struct]] if warp.surface == surface then local dx, dy = px - warp_pos.x, py - warp_pos.y local dist = (dx * dx) + (dy * dy) @@ -842,7 +842,7 @@ end) Event.add(defines.events.on_player_created, function(event) -- If the force has no spawn then make a spawn warp local player = game.players[event.player_index] - local force = player.force + local force = player.force --[[@as LuaForce]] local spawn_id = Warps.get_spawn_warp_id(force.name) if not spawn_id then local spawn_position = force.get_spawn_position(player.surface) diff --git a/exp_scenario/module/commands/ratio.lua b/exp_scenario/module/commands/ratio.lua index 2e12e444..201dd339 100644 --- a/exp_scenario/module/commands/ratio.lua +++ b/exp_scenario/module/commands/ratio.lua @@ -29,7 +29,7 @@ Commands.new("ratio", { "exp-commands_ratio.description" }) local amount_of_machines = 1 if items_per_second then - amount_of_machines = math.ceil(products[1].amount * crafts_per_second) + amount_of_machines = math.ceil(assert(assert(products[1]).amount) * crafts_per_second) end for _, ingredient in ipairs(ingredients) do @@ -43,7 +43,7 @@ Commands.new("ratio", { "exp-commands_ratio.description" }) for i, product in ipairs(products) do Commands.print{ product.type == "item" and "exp-commands_ratio.item-out" or "exp-commands_ratio.fluid-out", - math.round(product.amount * crafts_per_second, 3), + math.round(assert(product.amount) * crafts_per_second, 3), product.name } end diff --git a/exp_scenario/module/control/death_markers.lua b/exp_scenario/module/control/death_markers.lua index efb25694..24a74f30 100644 --- a/exp_scenario/module/control/death_markers.lua +++ b/exp_scenario/module/control/death_markers.lua @@ -31,7 +31,7 @@ local function create_map_tag(corpse_data) message = message .. " at " .. time end - corpse_data.tag = player.force.add_chart_tag(corpse_data.corpse.surface, { + corpse_data.tag = (player.force --[[@as LuaForce]]).add_chart_tag(corpse_data.corpse.surface, { position = corpse_data.corpse.position, icon = config.map_icon, text = message, diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index 365aba9d..6ac8bdf4 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -276,7 +276,7 @@ local function on_player_created(event) if config.resource_patches.enabled then create_resource_patches(surface, offset) end if config.turrets.enabled then update_turrets() end - player.force.set_spawn_position(offset, surface) + (player.force --[[@as LuaForce]]).set_spawn_position(offset, surface) player.teleport(offset, surface) end diff --git a/exp_scenario/module/gui/research_milestones.lua b/exp_scenario/module/gui/research_milestones.lua index 3863af68..e830ee0f 100644 --- a/exp_scenario/module/gui/research_milestones.lua +++ b/exp_scenario/module/gui/research_milestones.lua @@ -204,7 +204,7 @@ end --- @param row_index number --- @param row_data ExpGui_ResearchMilestones.elements.milestone_table.row_data function Elements.milestone_table.refresh_row(milestone_table, row_index, row_data) - local row = Elements.milestone_table.data[milestone_table][row_index] + local row = assert(Elements.milestone_table.data[milestone_table][row_index]) row.name.caption = row_data.name row.target.caption = row_data.target row.achieved.caption = row_data.achieved diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index d1647ff1..54c69fd3 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -193,7 +193,7 @@ Elements.science_table = Gui.define("science_production/science_table") local science_table = Gui.elements.scroll_table(parent, 190, 4) local no_production_label = Elements.no_production_label(science_table) Elements.no_production_label.refresh(no_production_label) - science_table.style.column_alignments[3] = "right" + (science_table.style --[[@as LuaStyle]]).column_alignments[3] = "right" return science_table end) :element_data{} --[[@as any]] @@ -280,7 +280,7 @@ function Elements.science_table.add_row(science_table, row_data) column_count = 2, } delta_table.style.padding = 0 - delta_table.style.column_alignments[1] = "right" + (delta_table.style --[[@as LuaStyle]]).column_alignments[1] = "right" -- Draw the net production label local net = Elements.production_label(science_table, row_data.net) From 3c0b5069fce6d420ac95a0818c7b5ddc7aa6123e Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:11:46 +0000 Subject: [PATCH 34/46] Assert optional api results across the scenario modules Co-Authored-By: Claude Opus 5 (1M context) --- exp_scenario/module/commands/waterfill.lua | 2 +- exp_scenario/module/control/custom_start.lua | 2 +- exp_scenario/module/control/damage_popups.lua | 5 +++-- exp_scenario/module/control/deconstruction_log.lua | 2 +- exp_scenario/module/control/extra_logging.lua | 4 ++-- exp_util/module/selection.lua | 6 +++--- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/exp_scenario/module/commands/waterfill.lua b/exp_scenario/module/commands/waterfill.lua index 6d9cc9d2..a9313f92 100644 --- a/exp_scenario/module/commands/waterfill.lua +++ b/exp_scenario/module/commands/waterfill.lua @@ -70,7 +70,7 @@ SelectArea:on_selection(function(event) if #chests > 0 then for _, chest in pairs(chests) do tile_count = tile_count + 1 - if chest.get_inventory(defines.inventory.chest).is_empty() and tile_count <= item_count_total then + if assert(chest.get_inventory(defines.inventory.chest)).is_empty() and tile_count <= item_count_total then tiles_to_make[tile_count] = { name = tile_to_apply, position = { chest.position.x, chest.position.y } } chest.destroy() else diff --git a/exp_scenario/module/control/custom_start.lua b/exp_scenario/module/control/custom_start.lua index 1c4e64ff..62d59a1a 100644 --- a/exp_scenario/module/control/custom_start.lua +++ b/exp_scenario/module/control/custom_start.lua @@ -8,7 +8,7 @@ local floor = math.floor --- Give a player their starting items --- @param player LuaPlayer local function give_starting_items(player) - local get_prod_stats = player.force.get_item_production_statistics(player.physical_surface) + local get_prod_stats = (player.force --[[@as LuaForce]]).get_item_production_statistics(player.physical_surface) local get_input_count = get_prod_stats.get_input_count local insert_param = { name = "", count = 0 } local insert = player.insert diff --git a/exp_scenario/module/control/damage_popups.lua b/exp_scenario/module/control/damage_popups.lua index 54dfc0e1..146a94fe 100644 --- a/exp_scenario/module/control/damage_popups.lua +++ b/exp_scenario/module/control/damage_popups.lua @@ -26,11 +26,12 @@ local function on_entity_damaged(event) -- Outputs the message as floating text if message then + local entity_position = entity.position --[[@as MapPosition.struct]] local entity_radius = max(1, entity.get_radius()) local offset = (random() - 0.5) * entity_radius * config.damage_location_variance - local position = { x = entity.position.x + offset, y = entity.position.y - entity_radius } + local position = { x = entity_position.x + offset, y = entity_position.y - entity_radius } - local health_percentage = entity.get_health_ratio() + local health_percentage = assert(entity.get_health_ratio()) local color = { r = 1 - health_percentage, g = health_percentage, b = 0 } FlyingText.create{ diff --git a/exp_scenario/module/control/deconstruction_log.lua b/exp_scenario/module/control/deconstruction_log.lua index 98ceb9ea..5d1743be 100644 --- a/exp_scenario/module/control/deconstruction_log.lua +++ b/exp_scenario/module/control/deconstruction_log.lua @@ -42,7 +42,7 @@ local function format_position(pos) end --- Convert an area to a string ---- @param area BoundingBox +--- @param area ExpUtil_AABB.Box --- @return string local function format_area(area) return format_string("%.1f,%.1f,%.1f,%.1f", area.left_top.x, area.left_top.y, area.right_bottom.x, area.right_bottom.y) diff --git a/exp_scenario/module/control/extra_logging.lua b/exp_scenario/module/control/extra_logging.lua index d469c390..07055245 100644 --- a/exp_scenario/module/control/extra_logging.lua +++ b/exp_scenario/module/control/extra_logging.lua @@ -23,7 +23,7 @@ end --- @param event EventData.on_cargo_pod_finished_ascending local function on_cargo_pod_finished_ascending(event) if event.launched_by_rocket then - local force = event.cargo_pod.force + local force = event.cargo_pod.force --[[@as LuaForce]] if force.rockets_launched >= config.rocket_launch_display_rate and force.rockets_launched % config.rocket_launch_display_rate == 0 then add_log_line("[ROCKET]", force.rockets_launched, "rockets launched") elseif config.rocket_launch_display[force.rockets_launched] then @@ -38,7 +38,7 @@ local function on_pre_player_died(event) local cause = event.cause if cause then if cause.type == "character" then - add_log_line("[DEATH]", player.name, "died because of", cause.player.name) + add_log_line("[DEATH]", player.name, "died because of", assert(cause.player).name) else add_log_line("[DEATH]", player.name, "died because of", cause.name) end diff --git a/exp_util/module/selection.lua b/exp_util/module/selection.lua index c6695ca7..461963f6 100644 --- a/exp_util/module/selection.lua +++ b/exp_util/module/selection.lua @@ -54,7 +54,7 @@ local selection_tool = { name = "selection-tool" } --- @param player LuaPlayer --- @return boolean? local function has_selection_tool_in_hand(player) - return player.cursor_stack and player.cursor_stack.valid_for_read and player.cursor_stack.name == "selection-tool" + return assert(player.cursor_stack) and assert(player.cursor_stack).valid_for_read and assert(player.cursor_stack).name == "selection-tool" end --- Give a selection tool to a player if they don't have one @@ -62,7 +62,7 @@ end local function give_selection_tool(player) if has_selection_tool_in_hand(player) then return end player.clear_cursor() - player.cursor_stack.set_stack(selection_tool) + assert(player.cursor_stack).set_stack(selection_tool) -- This does not work for selection planners, will make a feature request for it --player.cursor_stack_temporary = true @@ -84,7 +84,7 @@ end local function remove_selection_tool(player, old_character) -- Remove the selection tool if has_selection_tool_in_hand(player) then - player.cursor_stack.clear() + assert(player.cursor_stack).clear() else player.remove_item(selection_tool) end From 231c3a9ea09567afbb3e2976583917aa5a23472d Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:13:10 +0000 Subject: [PATCH 35/46] Narrow the gui player lookups Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/search.lua | 2 +- exp_gui/module/control.lua | 8 +++++--- exp_gui/module/data.lua | 6 +++--- exp_util/module/flying_text.lua | 10 +++++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/exp_commands/module/search.lua b/exp_commands/module/search.lua index a75de6a8..f4ddbaea 100644 --- a/exp_commands/module/search.lua +++ b/exp_commands/module/search.lua @@ -49,7 +49,7 @@ function Search.prepare(custom_commands) command_objects[name] = { name = name, description = locale_desc, - help_text = locale_desc, + help_text = locale_desc, usage = locale_desc, aliases = {}, } diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index c6f84e3c..526ffa06 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -46,8 +46,10 @@ end --- @return LuaPlayer function Gui.get_player(input) if type(input) == "table" and not input.player_index then + --- @cast input { element: LuaGuiElement } return assert(game.get_player(input.element.player_index)) end + --- @cast input LuaGuiElement | { player_index: uint } return assert(game.get_player(input.player_index)) end @@ -114,7 +116,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_top_element(define, player) - return assert(player_elements[player.index].top[define.name], "Element is not on the top flow") + return assert(assert(player_elements[player.index]).top[define.name], "Element is not on the top flow") end --- Register a element define to be drawn to the left flow on join @@ -122,7 +124,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_left_element(define, player) - return assert(player_elements[player.index].left[define.name], "Element is not on the left flow") + return assert(assert(player_elements[player.index]).left[define.name], "Element is not on the left flow") end --- Register a element define to be drawn to the relative flow on join @@ -130,7 +132,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_relative_element(define, player) - return assert(player_elements[player.index].relative[define.name], "Element is not on the relative flow") + return assert(assert(player_elements[player.index]).relative[define.name], "Element is not on the relative flow") end --- Ensure all the correct elements are visible and exist diff --git a/exp_gui/module/data.lua b/exp_gui/module/data.lua index 3e92cd14..327a11f1 100644 --- a/exp_gui/module/data.lua +++ b/exp_gui/module/data.lua @@ -61,7 +61,7 @@ local GuiData = { --- @field element_data table> --- @field player_data table --- @field force_data table ---- @field global_data table +--- @field global_data table --- @field [DataKey] any -- This class has no prototype methods -- Same as raw but __index ensures the values exist @@ -99,7 +99,7 @@ function GuiData._metatable.__index(self, key) local data = self._raw.player_data return data and data[key.index] elseif object_name == "LuaForce" then - --- @cast key LuaForce + --- @cast key LuaForce local data = self._raw.force_data return data and data[key.index] else @@ -172,7 +172,7 @@ local function on_object_destroyed(event) for _, scope in pairs(registered_scopes) do local data = scope._raw.element_data local player_elements = data and data[player_index] - if player_elements then + if data and player_elements then player_elements[element_index] = nil if not next(player_elements) then data[player_index] = nil diff --git a/exp_util/module/flying_text.lua b/exp_util/module/flying_text.lua index 73e8d9f0..84f87564 100644 --- a/exp_util/module/flying_text.lua +++ b/exp_util/module/flying_text.lua @@ -44,7 +44,7 @@ function FlyingText.create_above_entity(options) local entity = assert(options.target_entity, "A target entity is required") local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position --[[@as MapPosition.struct]] local offset = options.offset or { x = 0, y = 0 } options.position = { @@ -64,9 +64,9 @@ end function FlyingText.create_above_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position --[[@as MapPosition.struct]] local offset = options.offset or { x = 0, y = 0 } options.position = { @@ -86,9 +86,9 @@ end function FlyingText.create_as_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position --[[@as MapPosition.struct]] local offset = options.offset or { x = 0, y = 0 } options.color = player.chat_color From b5bbf7333210d0c6d663ab887b5b1897cf25b0c1 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:14:12 +0000 Subject: [PATCH 36/46] Patch MapPosition and BoundingBox to the named form The api documents both as a union with a positional array because both are accepted as input, which makes every read of `.x` or `.left_top` optional. Everything read back from the game uses the named form, so the positional variant is removed and we now always write it that way too. The spawn area config offsets and the mine depletion search areas were the only positional writes left, and apply_offset no longer needs its fallback. Co-Authored-By: Claude Opus 5 (1M context) --- exp_legacy/module/config/spawn_area.lua | 12 ++++++------ exp_scenario/module/control/mine_depletion.lua | 8 ++++---- exp_scenario/module/control/spawn_area.lua | 8 ++++---- type.patch.lua | 8 ++++++++ 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 type.patch.lua diff --git a/exp_legacy/module/config/spawn_area.lua b/exp_legacy/module/config/spawn_area.lua index 7f95462a..72ee8c8e 100644 --- a/exp_legacy/module/config/spawn_area.lua +++ b/exp_legacy/module/config/spawn_area.lua @@ -188,7 +188,7 @@ return { amount = 4000, size = { 26, 27 }, -- offset = {-64,-32} - offset = { -64, -64 }, + offset = { x = -64, y = -64 }, }, { enabled = false, @@ -196,7 +196,7 @@ return { amount = 4000, size = { 26, 27 }, -- offset = {-64, 0} - offset = { 64, -64 }, + offset = { x = 64, y = -64 }, }, { enabled = false, @@ -204,7 +204,7 @@ return { amount = 4000, size = { 22, 20 }, -- offset = {-64, 32} - offset = { -64, 64 }, + offset = { x = -64, y = 64 }, }, { enabled = false, @@ -212,7 +212,7 @@ return { amount = 4000, size = { 22, 20 }, -- offset = {-64, -64} - offset = { 64, 64 }, + offset = { x = 64, y = 64 }, }, { enabled = false, @@ -220,7 +220,7 @@ return { amount = 4000, size = { 22, 20 }, -- offset = {-64, -96} - offset = { 0, 64 }, + offset = { x = 0, y = 64 }, }, }, }, @@ -233,7 +233,7 @@ return { num_patches = 4, amount = 4000000, -- offset = {-80, -12}, - offset = { -12, 64 }, + offset = { x = -12, y = 64 }, -- offset_next = {0, 6} offset_next = { 6, 0 }, }, diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 996a6019..0f54a676 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -73,8 +73,8 @@ local function try_deconstruct_output_chest(entity) type = { "mining-drill", "inserter" }, to_be_deconstructed = false, area = { - { target_position.x - 1, target_position.y - 1 }, - { target_position.x + 1, target_position.y + 1 } + left_top = { x = target_position.x - 1, y = target_position.y - 1 }, + right_bottom = { x = target_position.x + 1, y = target_position.y + 1 }, }, } @@ -251,8 +251,8 @@ local function on_resource_depleted(event) local drills = resource.surface.find_entities_filtered{ type = "mining-drill", area = { - { position.x - max_mining_radius, position.y - max_mining_radius }, - { position.x + max_mining_radius, position.y + max_mining_radius }, + left_top = { x = position.x - max_mining_radius, y = position.y - max_mining_radius }, + right_bottom = { x = position.x + max_mining_radius, y = position.y + max_mining_radius }, }, } diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index 6ac8bdf4..4d96e90c 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -10,8 +10,8 @@ local config = require("modules.exp_legacy.config.spawn_area") --- @return MapPosition.struct local function apply_offset(position, offset) return { - x = assert(position.x or position[1]) + assert(offset.x or offset[1]), - y = assert(position.y or position[2]) + assert(offset.y or offset[2]) + x = position.x + offset.x, + y = position.y + offset.y, } end @@ -21,8 +21,8 @@ end --- @param x_index number --- @param y_index number local function apply_offset_to_array(positions, offset, x_index, y_index) - local x = assert(offset.x or offset[1]) - local y = assert(offset.y or offset[2]) + local x = offset.x + local y = offset.y for _, position in ipairs(positions) do position[x_index] = position[x_index] + x position[y_index] = position[y_index] + y diff --git a/type.patch.lua b/type.patch.lua new file mode 100644 index 00000000..191d59d8 --- /dev/null +++ b/type.patch.lua @@ -0,0 +1,8 @@ +---@meta + +--- The api documents MapPosition and BoundingBox as a union of the named form +--- and a positional array, because both are accepted as input. Everything read +--- back from the game uses the named form, and we always write it that way too, +--- so the positional variant is removed here. +---@alias MapPosition MapPosition.struct +---@alias BoundingBox BoundingBox.struct From 300196bc76e8709e3f8c3cf69a6213ba2ffc9e15 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:14:51 +0000 Subject: [PATCH 37/46] Assert the remaining optional reads in exp_util and commands Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/commands/authorities.lua | 4 ++-- exp_scenario/module/commands/kill.lua | 2 +- exp_util/module/module_exports.lua | 13 +++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/exp_commands/module/commands/authorities.lua b/exp_commands/module/commands/authorities.lua index 0001b16d..c1ec3309 100644 --- a/exp_commands/module/commands/authorities.lua +++ b/exp_commands/module/commands/authorities.lua @@ -28,13 +28,13 @@ end) --- Allow a player access to system commands, use for debug purposes only --- @param player_name string? The name of the player to give access to, default is the current player function Commands.unlock_system_commands(player_name) - system_players[player_name or game.player.name] = true + system_players[player_name or assert(game.player).name] = true end --- Remove access from system commands for a player, use for debug purposes only --- @param player_name string? The name of the player to give access to, default is the current player function Commands.lock_system_commands(player_name) - system_players[player_name or game.player.name] = nil + system_players[player_name or assert(game.player).name] = nil end --- Get a list of all players who have system commands unlocked diff --git a/exp_scenario/module/commands/kill.lua b/exp_scenario/module/commands/kill.lua index 17d291fd..a71528ed 100644 --- a/exp_scenario/module/commands/kill.lua +++ b/exp_scenario/module/commands/kill.lua @@ -25,7 +25,7 @@ Commands.new("kill", { "exp-commands_kill.description" }) if script.active_mods["space-age"] then other_player.surface.create_entity{ name = "lightning", position = { other_player.position.x, other_player.position.y - 16 }, target = other_player.character } end - other_player.character.die() + assert(other_player.character).die() else return Commands.status.unauthorised{ "exp-commands_kill.lower-role" } end diff --git a/exp_util/module/module_exports.lua b/exp_util/module/module_exports.lua index 5203d97e..98f49a43 100644 --- a/exp_util/module/module_exports.lua +++ b/exp_util/module/module_exports.lua @@ -319,15 +319,15 @@ function ExpUtil.extract_time_units(ticks, units) -- Remove units that are not requested if not units.days then - rtn.hours = rtn.hours + rtn.days * 24 + rtn.hours = assert(rtn.hours) + assert(rtn.days) * 24 rtn.days = nil end if not units.hours then - rtn.minutes = rtn.minutes + rtn.hours * 60 + rtn.minutes = assert(rtn.minutes) + assert(rtn.hours) * 60 rtn.hours = nil end if not units.minutes then - rtn.seconds = rtn.seconds + rtn.minutes * 60 + rtn.seconds = assert(rtn.seconds) + assert(rtn.minutes) * 60 rtn.minutes = nil end if not units.seconds then @@ -487,7 +487,7 @@ function ExpUtil.get_storage_for_stack(options) -- Find a valid entity from the search results local current, count, entities = cache.current, cache.count, cache.entities for i = 1, cache.count do - local entity = entities[((current + i - 1) % count) + 1] + local entity = assert(entities[((current + i - 1) % count) + 1]) if entity.can_insert(item) then cache.current = current + 1 return entity @@ -551,7 +551,7 @@ function ExpUtil.move_items_to_surface(options) options.item = item entity = ExpUtil.get_storage_for_stack(options) entity.insert(options.item) - options.item.clear() + assert(options.item).clear() end end return entity @@ -603,7 +603,8 @@ end --- @param n number --- @return string function ExpUtil.comma_value(n) -- credit http://richard.warburton.it - local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") + local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") + assert(left and num and right) return left .. (num:reverse():gsub("(%d%d%d)", "%1, "):reverse()) .. right end From 76ecc37d3f380aa983b5c0fac241bb09e9cc4f91 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:16:38 +0000 Subject: [PATCH 38/46] Type the legacy role registry Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/commands/help.lua | 4 ++-- exp_legacy/module/expcore/roles.lua | 19 +++++++++++-------- exp_util/module/module_exports.lua | 3 ++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/exp_commands/module/commands/help.lua b/exp_commands/module/commands/help.lua index 602b84c4..6a4b3752 100644 --- a/exp_commands/module/commands/help.lua +++ b/exp_commands/module/commands/help.lua @@ -48,7 +48,7 @@ local function format_as_pages(commands, page_size) end local aliases = #command.aliases > 0 and { "exp-commands_help.aliases", table.concat(command.aliases, ", ") } or "" - pages[current_page][page_length] = { "exp-commands_help.format", command.name, description, aliases } + assert(pages[current_page])[page_length] = { "exp-commands_help.format", command.name, description, aliases } end return pages, total @@ -71,7 +71,7 @@ Commands.new("commands", { "exp-commands_help.description" }) page = as_number end - keyword = keyword:lower() + keyword = assert(keyword):lower() local pages, found if cache and cache.keyword == keyword then -- Cached value found, no search is needed diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index ebcdfc2d..22ca6384 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -117,6 +117,7 @@ local Groups = require("modules.exp_legacy.expcore.permission_groups") local Colours = ExpUtil.color local write_json = ExpUtil.write_json + --- @class Roles.Role --- @field name string --- @field short_hand string @@ -131,12 +132,13 @@ local write_json = ExpUtil.write_json --- @field parent string? --- @field block_auto_assign boolean? --- @field auto_assign_condition function? +--- @field set_allow_all fun(self: Roles.Role, state: boolean?): Roles.Role Defined on Roles._prototype local Roles = { _prototype = {}, config = { - order = {}, -- Contains the order of the roles, lower index is better - roles = {}, -- Contains the raw info for the roles, indexed by role name + order = {}, --- @type string[] Contains the order of the roles, lower index is better + roles = {}, --- @type table Contains the raw info for the roles, indexed by role name flags = {}, -- Contains functions that run when a flag is added/removed from a player internal = {}, -- Contains all internally accessed roles, such as root, default players = {}, -- Contains the roles that players have @@ -214,7 +216,7 @@ game.player.print(Roles.debug()) function Roles.debug() local output = "" for index, role_name in ipairs(Roles.config.order) do - local role = Roles.config.roles[role_name] + local role = assert(Roles.config.roles[role_name]) local color = (role.custom_color or Colours.white) --[[@as Color.struct]] local color_str = string.format("[color=%d, %d, %d]", color.r, color.g, color.b) output = output .. string.format("\n%s %s) %s[/color]", color_str, index, serpent.line(role)) @@ -302,6 +304,7 @@ local role = Roles.get_role_by_name(2) ]] function Roles.get_role_by_order(index) local name = Roles.config.order[index] + if not name then return end return Roles.config.roles[name] end @@ -412,7 +415,7 @@ function Roles.assign_player(player, roles, by_player_name, skip_checks, silent) -- If the player has a role that needs to defer the role changes, save the roles that need to be assigned later into a table if valid_player and Roles.player_has_flag(valid_player, "defer_role_changes") then - local assign_later = Roles.config.deferred_roles[valid_player.name] or {} + local assign_later = Roles.config.deferred_roles[assert(valid_player).name] or {} for _, role in ipairs(role_objects) do local role_change = assign_later[role.name] if role_change then @@ -428,7 +431,7 @@ function Roles.assign_player(player, roles, by_player_name, skip_checks, silent) end end - Roles.config.deferred_roles[valid_player.name] = assign_later + Roles.config.deferred_roles[assert(valid_player).name] = assign_later return end @@ -475,7 +478,7 @@ function Roles.unassign_player(player, roles, by_player_name, skip_checks, silen -- If the player has a role that needs to defer the role changes, save the roles that need to be unassigned later into a table local defer_changes = Roles.player_has_flag(player, "defer_role_changes") if defer_changes then - local assign_later = Roles.config.deferred_roles[valid_player.name] or {} + local assign_later = Roles.config.deferred_roles[assert(valid_player).name] or {} for _, role in ipairs(role_objects) do local role_change = assign_later[role.name] if role_change then @@ -491,7 +494,7 @@ function Roles.unassign_player(player, roles, by_player_name, skip_checks, silen end end - Roles.config.deferred_roles[valid_player.name] = assign_later + Roles.config.deferred_roles[assert(valid_player).name] = assign_later end -- Remove the player from roles @@ -671,7 +674,7 @@ function Roles.define_role_order(order) -- Re-links roles to they parents as this is called at the end of the config for index, role_name in pairs(Roles.config.order) do - local role = Roles.config.roles[role_name] + local role = assert(Roles.config.roles[role_name]) if not role then error("Role with name " .. role_name .. " has not beed defined, either define it or remove it from the order list.", 2) end diff --git a/exp_util/module/module_exports.lua b/exp_util/module/module_exports.lua index 98f49a43..7a38da3f 100644 --- a/exp_util/module/module_exports.lua +++ b/exp_util/module/module_exports.lua @@ -551,7 +551,8 @@ function ExpUtil.move_items_to_surface(options) options.item = item entity = ExpUtil.get_storage_for_stack(options) entity.insert(options.item) - assert(options.item).clear() + local item_stack = options.item --[[@as LuaItemStack]] + item_stack.clear() end end return entity From f5656025ee5cfa2f4fb2b89e74d6c424d99af5b6 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:19:42 +0000 Subject: [PATCH 39/46] Clear the last of the nil checks Guards now cover the statements that follow them, and a couple of reads are restructured so the narrowing survives. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/commands/help.lua | 2 +- exp_legacy/module/expcore/external.lua | 12 +++++++----- exp_legacy/module/modules/control/protection.lua | 2 +- exp_legacy/module/modules/control/spectate.lua | 2 +- exp_legacy/module/modules/gui/warp-list.lua | 4 ++-- exp_scenario/module/control/spawn_area.lua | 13 ++++++++----- exp_scenario/module/gui/player_list.lua | 2 +- exp_scenario/module/gui/science_production.lua | 12 ++++++++---- exp_util/module/module_exports.lua | 4 ++-- 9 files changed, 31 insertions(+), 22 deletions(-) diff --git a/exp_commands/module/commands/help.lua b/exp_commands/module/commands/help.lua index 6a4b3752..2a6c3b15 100644 --- a/exp_commands/module/commands/help.lua +++ b/exp_commands/module/commands/help.lua @@ -71,7 +71,7 @@ Commands.new("commands", { "exp-commands_help.description" }) page = as_number end - keyword = assert(keyword):lower() + keyword = tostring(keyword):lower() local pages, found if cache and cache.keyword == keyword then -- Cached value found, no search is needed diff --git a/exp_legacy/module/expcore/external.lua b/exp_legacy/module/expcore/external.lua index f4065c53..9511659b 100644 --- a/exp_legacy/module/expcore/external.lua +++ b/exp_legacy/module/expcore/external.lua @@ -14,7 +14,8 @@ end ]] -local ext, var +local ext --- @type table +local var --- @type table local concat = table.concat local External = {} @@ -29,12 +30,13 @@ end ]] function External.valid() - if storage.ext == nil then return false end - if ext == storage.ext and var == ext.var then + local stored = storage.ext + if stored == nil then return false end + if ext == stored and var == ext.var then return var ~= nil else - ext = storage.ext - var = ext.var + ext = stored + var = stored.var return var ~= nil end end diff --git a/exp_legacy/module/modules/control/protection.lua b/exp_legacy/module/modules/control/protection.lua index 79415756..506d889d 100644 --- a/exp_legacy/module/modules/control/protection.lua +++ b/exp_legacy/module/modules/control/protection.lua @@ -34,7 +34,7 @@ for _, config_key in ipairs{ "always_protected_names", "always_protected_types", end -- Require roles if a permission is assigned in the config -local Roles +local Roles --- @type table if config.ignore_permission then Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles end diff --git a/exp_legacy/module/modules/control/spectate.lua b/exp_legacy/module/modules/control/spectate.lua index 1c1d1c70..dc2784f8 100644 --- a/exp_legacy/module/modules/control/spectate.lua +++ b/exp_legacy/module/modules/control/spectate.lua @@ -4,7 +4,7 @@ local Gui = require("modules/exp_gui") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event ----- Locals ----- -local follow_label -- Gui constructor +local follow_label --- @type ExpElement Gui constructor local following = {} local spectating = {} local Public = {} diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index 29c5a18a..ec7777c4 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -673,7 +673,7 @@ warp_list_container = Gui.define("warp_list_container") -- Draw the scroll table for the warps local scroll_table = Gui.elements.scroll_table(container, 250, 3, "scroll") -- Set the scroll panel to always show the scrollbar (not doing this will result in a changing gui size) - scroll_table.parent.vertical_scroll_policy = "always" + assert(scroll_table.parent).vertical_scroll_policy = "always" -- Change the style of the scroll table local scroll_table_style = scroll_table.style @@ -874,7 +874,7 @@ local function role_update_event(event) -- Check if user has permission to add warps local allow_add_warp = check_player_permissions(player, "allow_add_warp") -- Update container size depending on whether the player is allowed to add warps - frame.parent.style.width = allow_add_warp and 268 or 220 + assert(frame.parent).style.width = allow_add_warp and 268 or 220 -- Update the warps, in case the user can now edit them local scroll_table = frame.scroll.table diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index 4d96e90c..d9767f95 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -149,11 +149,13 @@ local function create_entities(surface, offset) local pos = apply_offset({ entity_details[2], entity_details[3] }, offset) local entity = surface.create_entity{ name = entity_details[1], position = pos, force = "neutral" } - if entity and config.entities.protected then - protect_entity(entity) - end + if entity then + if config.entities.protected then + protect_entity(entity) + end - entity.operable = config.entities.operable + entity.operable = config.entities.operable + end end end @@ -276,7 +278,8 @@ local function on_player_created(event) if config.resource_patches.enabled then create_resource_patches(surface, offset) end if config.turrets.enabled then update_turrets() end - (player.force --[[@as LuaForce]]).set_spawn_position(offset, surface) + local force = player.force --[[@as LuaForce]] + force.set_spawn_position(offset, surface) player.teleport(offset, surface) end diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index bb3a70e3..28912d85 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -106,7 +106,7 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") if reason == nil or not reason:find("%S") then reason = "no reason given" end button_data.reason_callback(player, reason) end - element.parent.entry.text = "" + assert(assert(element.parent).entry).text = "" Elements.container.set_selected_player(player, nil) Elements.player_table.refresh_player(player) end) --[[@as any]] diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index 54c69fd3..97ac2a41 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -193,7 +193,8 @@ Elements.science_table = Gui.define("science_production/science_table") local science_table = Gui.elements.scroll_table(parent, 190, 4) local no_production_label = Elements.no_production_label(science_table) Elements.no_production_label.refresh(no_production_label) - (science_table.style --[[@as LuaStyle]]).column_alignments[3] = "right" + local science_table_style = science_table.style --[[@as LuaStyle]] + science_table_style.column_alignments[3] = "right" return science_table end) :element_data{} --[[@as any]] @@ -280,7 +281,8 @@ function Elements.science_table.add_row(science_table, row_data) column_count = 2, } delta_table.style.padding = 0 - (delta_table.style --[[@as LuaStyle]]).column_alignments[1] = "right" + local delta_table_style = delta_table.style --[[@as LuaStyle]] + delta_table_style.column_alignments[1] = "right" -- Draw the net production label local net = Elements.production_label(science_table, row_data.net) @@ -429,8 +431,10 @@ do local _display_data = {} --- @type table Date: Fri, 7 Aug 2026 14:38:19 +0000 Subject: [PATCH 40/46] Enable the diagnostics which already pass These were carried over as disabled from the luals config but report nothing, so they only ever masked a future regression. Co-Authored-By: Claude Opus 5 (1M context) --- .emmyrc.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index c103023e..88091ed7 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -22,18 +22,13 @@ "globals": [ "__DebugAdapter", "__Profiler" ], "disable": [ "assign-type-mismatch", - "await-in-sync", - "invert-if", "missing-parameter", - "missing-return-value", "param-type-mismatch", "preferred-local-alias", "redefined-local", "redundant-parameter", "redundant-return-value", "return-type-mismatch", - "unbalanced-assignments", - "undefined-doc-param", "unnecessary-assert", "unnecessary-if", "unused" From f907007d14587eb678d696353eec200bebebbddf Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:41:01 +0000 Subject: [PATCH 41/46] Fix arity and return value mismatches `assert` returns every argument it is given, so `return assert(x, msg)` was leaking the message as a second return value. `get_tile` is documented as taking x and y. The async function class declared `@operator call` with no parameters, which made every async call look over supplied. Co-Authored-By: Claude Opus 5 (1M context) --- .emmyrc.json | 5 ----- exp_gui/module/control.lua | 10 +++++----- exp_gui/module/elements.lua | 4 ++-- exp_gui/module/prototype.lua | 2 +- exp_legacy/module/config/chat_reply.lua | 2 ++ exp_legacy/module/expcore/external.lua | 6 +++--- exp_legacy/module/expcore/roles.lua | 2 +- exp_scenario/module/control/degrading_tiles.lua | 4 ++-- exp_scenario/module/control/spawn_area.lua | 4 ++-- exp_util/module/async.lua | 2 +- 10 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index 88091ed7..dbaab2ba 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -22,13 +22,8 @@ "globals": [ "__DebugAdapter", "__Profiler" ], "disable": [ "assign-type-mismatch", - "missing-parameter", "param-type-mismatch", "preferred-local-alias", - "redefined-local", - "redundant-parameter", - "redundant-return-value", - "return-type-mismatch", "unnecessary-assert", "unnecessary-if", "unused" diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index 526ffa06..a911318d 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -47,10 +47,10 @@ end function Gui.get_player(input) if type(input) == "table" and not input.player_index then --- @cast input { element: LuaGuiElement } - return assert(game.get_player(input.element.player_index)) + return (assert(game.get_player(input.element.player_index))) end --- @cast input LuaGuiElement | { player_index: uint } - return assert(game.get_player(input.player_index)) + return (assert(game.get_player(input.player_index))) end --- Toggle the enable state of an element @@ -116,7 +116,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_top_element(define, player) - return assert(assert(player_elements[player.index]).top[define.name], "Element is not on the top flow") + return (assert(assert(player_elements[player.index]).top[define.name], "Element is not on the top flow")) end --- Register a element define to be drawn to the left flow on join @@ -124,7 +124,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_left_element(define, player) - return assert(assert(player_elements[player.index]).left[define.name], "Element is not on the left flow") + return (assert(assert(player_elements[player.index]).left[define.name], "Element is not on the left flow")) end --- Register a element define to be drawn to the relative flow on join @@ -132,7 +132,7 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_relative_element(define, player) - return assert(assert(player_elements[player.index]).relative[define.name], "Element is not on the relative flow") + return (assert(assert(player_elements[player.index]).relative[define.name], "Element is not on the relative flow")) end --- Ensure all the correct elements are visible and exist diff --git a/exp_gui/module/elements.lua b/exp_gui/module/elements.lua index 8f1ccb4e..a2f4ab0d 100644 --- a/exp_gui/module/elements.lua +++ b/exp_gui/module/elements.lua @@ -52,7 +52,7 @@ Elements.bar = Gui.define("bar") --- A label which is centered --- @class Gui.elements.centered_label: ExpElement ---- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement +--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString?, tooltip: LocalisedString?): LuaGuiElement Elements.centered_label = Gui.define("centered_label") :draw{ type = "label", @@ -319,7 +319,7 @@ Elements.screen_frame = Gui.define("screen_frame") --- @param screen_frame LuaGuiElement --- @return LuaGuiElement function Elements.screen_frame.get_button_flow(screen_frame) - return assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow") + return (assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow")) end --- Get the root element of a screen frame diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua index 5b261ecd..237c683a 100644 --- a/exp_gui/module/prototype.lua +++ b/exp_gui/module/prototype.lua @@ -166,7 +166,7 @@ end --- @param name string --- @return ExpElement function ExpElement.get(name) - return assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name)) + return (assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name))) end --- Create a new instance of this element definition diff --git a/exp_legacy/module/config/chat_reply.lua b/exp_legacy/module/config/chat_reply.lua index 1321f96d..5eabb22d 100644 --- a/exp_legacy/module/config/chat_reply.lua +++ b/exp_legacy/module/config/chat_reply.lua @@ -24,6 +24,7 @@ local afk_time_units = { } return { + --- @type table messages = { --- @setting messages will trigger when ever the word is said ["discord"] = { "info.discord" }, ["expgaming"] = { "info.website" }, @@ -66,6 +67,7 @@ return { command_admin_only = false, --- @setting command_admin_only when true will only allow chat commands for admins command_permission = "command/chat-commands", --- @setting command_permission the permission used to allow command prefixes command_prefix = "!", --- @setting command_prefix prefix used for commands below and to print to all players (if enabled above) + --- @type table commands = { --- @setting commands will trigger only when command prefix is given ["dev"] = { "exp_chat-auto-reply.reply-dev" }, ["magic"] = { "exp_chat-auto-reply.reply-magic" }, diff --git a/exp_legacy/module/expcore/external.lua b/exp_legacy/module/expcore/external.lua index 9511659b..be8fb078 100644 --- a/exp_legacy/module/expcore/external.lua +++ b/exp_legacy/module/expcore/external.lua @@ -50,7 +50,7 @@ local servers = External.get_servers() ]] function External.get_servers() assert(ext, "No external data was found, use External.valid() to ensure external data exists.") - return assert(ext.servers, "No server list was found, please ensure that the external service is running") + return (assert(ext.servers, "No server list was found, please ensure that the external service is running")) end --[[-- Gets a table of all the servers filtered by name, key is the server id, value is the server details @@ -85,7 +85,7 @@ function External.get_current_server() assert(ext, "No external data was found, use External.valid() to ensure external data exists.") local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running") local server_id = assert(ext.current, "No current id was found, please ensure that the external service is running") - return assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)) + return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))) end --[[-- Gets the details of the given server @@ -99,7 +99,7 @@ local server = External.get_server_details('eu-01') function External.get_server_details(server_id) assert(ext, "No external data was found, use External.valid() to ensure external data exists.") local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running") - return assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)) + return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))) end --[[-- Gets the status of the given server diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index 22ca6384..a99e41cd 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -376,7 +376,7 @@ function Roles.get_player_highest_role(player) end end - return assert(highest, "Player has no roles") + return (assert(highest, "Player has no roles")) end --- Assignment. diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index 6a8282b0..6d7b951f 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -18,7 +18,7 @@ end --- @param surface LuaSurface --- @param position MapPosition.struct local function degrade_tile(surface, position) - local tile = surface.get_tile(position) + local tile = surface.get_tile(position.x, position.y) local tile_name = tile.name local degrade_tile_name = config.degrade_order[tile_name] if not degrade_tile_name then return end @@ -61,7 +61,7 @@ end --- @param position MapPosition.struct --- @return number? local function get_tile_strength(surface, position) - local tile = surface.get_tile(position) + local tile = surface.get_tile(position.x, position.y) local tile_name = tile.name local strength = config.strengths[tile_name] if not strength then return end diff --git a/exp_scenario/module/control/spawn_area.lua b/exp_scenario/module/control/spawn_area.lua index d9767f95..aab08d17 100644 --- a/exp_scenario/module/control/spawn_area.lua +++ b/exp_scenario/module/control/spawn_area.lua @@ -166,7 +166,7 @@ local function clear_spawn_area(surface, offset) local get_tile = surface.get_tile -- Make sure a non water tile is used for filling - local starting_tile = get_tile(offset) + local starting_tile = get_tile(offset.x, offset.y) local fill_tile = starting_tile.collides_with("player") and "landfill" or starting_tile.name local fill_radius = config.spawn_area.landfill_radius local fill_radius_sqr = fill_radius ^ 2 @@ -186,7 +186,7 @@ local function clear_spawn_area(surface, offset) if dst < tile_radius_sqr then -- If it is inside the decon radius always set the tile tiles_to_make[#tiles_to_make + 1] = { name = decon_tile, position = pos } - elseif dst < fill_radius_sqr and get_tile(pos).collides_with("player") then + elseif dst < fill_radius_sqr and get_tile(pos.x, pos.y).collides_with("player") then -- If it is inside the fill radius only set the tile if it is water tiles_to_make[#tiles_to_make + 1] = { name = fill_tile, position = pos } end diff --git a/exp_util/module/async.lua b/exp_util/module/async.lua index 73bb7c81..e3847436 100644 --- a/exp_util/module/async.lua +++ b/exp_util/module/async.lua @@ -94,7 +94,7 @@ Async.status = {} --- @class Async.AsyncFunction --- @field id string The id of this async function ---- @operator call: Async.AsyncReturn +--- @overload fun(...: any): Async.AsyncReturn Async._function_prototype = {} Async._function_metatable = { From 619ed4b39f4cebe8bd7f260d61e94d69da3f06ea Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:46:14 +0000 Subject: [PATCH 42/46] Remove asserts the types already cover The nil check burn down added guards which turned out redundant once the surrounding annotations landed. `_has_handlers` is set to true when the first handler registers, so it is a boolean rather than the literal false it was inferred as. Co-Authored-By: Claude Opus 5 (1M context) --- exp_commands/module/module_exports.lua | 16 ++++++++++------ exp_gui/module/control.lua | 9 ++++++--- exp_gui/module/elements.lua | 7 ++++--- exp_gui/module/prototype.lua | 5 ++++- exp_legacy/module/expcore/external.lua | 9 ++++++--- exp_legacy/module/expcore/roles.lua | 19 ++++++++++--------- exp_legacy/module/modules/control/vlayer.lua | 2 +- exp_legacy/module/modules/gui/vlayer.lua | 9 ++++----- exp_legacy/module/modules/gui/warp-list.lua | 8 ++++---- exp_scenario/module/commands/connect.lua | 2 +- exp_scenario/module/commands/reports.lua | 8 ++++---- exp_scenario/module/commands/search.lua | 2 +- .../module/control/discord_alerts.lua | 2 +- exp_scenario/module/gui/player_list.lua | 4 ++-- exp_scenario/module/gui/quick_actions.lua | 2 +- .../module/gui/research_milestones.lua | 5 ++--- .../module/gui/science_production.lua | 4 ++-- exp_scenario/module/gui/task_list.lua | 2 +- exp_util/module/module_exports.lua | 8 ++++---- 19 files changed, 68 insertions(+), 55 deletions(-) diff --git a/exp_commands/module/module_exports.lua b/exp_commands/module/module_exports.lua index 7dcfc0e2..af8fffc4 100644 --- a/exp_commands/module/module_exports.lua +++ b/exp_commands/module/module_exports.lua @@ -170,7 +170,8 @@ Commands.server = setmetatable({ --- @param msg LocalisedString? An optional message to be included when a command completes (only has an effect in command callbacks) --- @return Commands.Status, LocalisedString # Should be returned directly without modification function Commands.status.success(msg) - return Commands.status.success, msg == nil and { "exp-commands.success" } or msg + if msg == nil then return Commands.status.success, { "exp-commands.success" } end + return Commands.status.success, msg end --- Used to signal an error has occurred in a command, data type parser, or permission authority @@ -178,7 +179,8 @@ end --- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided --- @return Commands.Status, LocalisedString # Should be returned directly without modification function Commands.status.error(msg) - return Commands.status.error, { "exp-commands.error", msg == nil and { "exp-commands.error-default" } or msg } + if msg == nil then msg = { "exp-commands.error-default" } end + return Commands.status.error, { "exp-commands.error", msg } end --- Used to signal the player is unauthorised to use a command, primarily used by permission authorities but can be used in a command callback @@ -186,7 +188,8 @@ end --- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided --- @return Commands.Status, LocalisedString # Should be returned directly without modification function Commands.status.unauthorised(msg) - return Commands.status.unauthorised, { "exp-commands.unauthorized", msg == nil and { "exp-commands.unauthorized-default" } or msg } + if msg == nil then msg = { "exp-commands.unauthorized-default" } end + return Commands.status.unauthorised, { "exp-commands.unauthorized", msg } end --- Used to signal the player provided invalid input to an command, primarily used by data type parsers but can be used in a command callback @@ -194,15 +197,16 @@ end --- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided --- @return Commands.Status, LocalisedString # Should be returned directly without modification function Commands.status.invalid_input(msg) - return Commands.status.invalid_input, msg == nil and { "exp-commands.invalid-input" } or msg + if msg == nil then return Commands.status.invalid_input, { "exp-commands.invalid-input" } end + return Commands.status.invalid_input, msg end --- Used to signal an internal error has occurred, this is reserved for internal use only ---- @param msg LocalisedString A message detailing the error which has occurred, will be logged and outputted +--- @param msg LocalisedString? A message detailing the error which has occurred, will be logged and outputted --- @return Commands.Status, LocalisedString # Should be returned directly without modification --- @package function Commands.status.internal_error(msg) - return Commands.status.internal_error, { "exp-commands.internal-error", msg } + return Commands.status.internal_error, { "exp-commands.internal-error", msg or "" } end --- @type table diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index a911318d..0b5003a3 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -116,7 +116,8 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_top_element(define, player) - return (assert(assert(player_elements[player.index]).top[define.name], "Element is not on the top flow")) + local value = assert(assert(player_elements[player.index]).top[define.name], "Element is not on the top flow") + return value end --- Register a element define to be drawn to the left flow on join @@ -124,7 +125,8 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_left_element(define, player) - return (assert(assert(player_elements[player.index]).left[define.name], "Element is not on the left flow")) + local value = assert(assert(player_elements[player.index]).left[define.name], "Element is not on the left flow") + return value end --- Register a element define to be drawn to the relative flow on join @@ -132,7 +134,8 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_relative_element(define, player) - return (assert(assert(player_elements[player.index]).relative[define.name], "Element is not on the relative flow")) + local value = assert(assert(player_elements[player.index]).relative[define.name], "Element is not on the relative flow") + return value end --- Ensure all the correct elements are visible and exist diff --git a/exp_gui/module/elements.lua b/exp_gui/module/elements.lua index a2f4ab0d..aa343a1d 100644 --- a/exp_gui/module/elements.lua +++ b/exp_gui/module/elements.lua @@ -165,7 +165,7 @@ Elements.container = Gui.define("container") --- @param container LuaGuiElement --- @return LuaGuiElement function Elements.container.get_root_element(container) - return container.parent + return assert(container.parent) end --- A frame within a container @@ -319,14 +319,15 @@ Elements.screen_frame = Gui.define("screen_frame") --- @param screen_frame LuaGuiElement --- @return LuaGuiElement function Elements.screen_frame.get_button_flow(screen_frame) - return (assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow")) + local value = assert(Elements.screen_frame.data[assert(screen_frame.parent)], "Screen frame has no button flow") + return value end --- Get the root element of a screen frame --- @param screen_frame LuaGuiElement --- @return LuaGuiElement function Elements.screen_frame.get_root_element(screen_frame) - return screen_frame.parent + return assert(screen_frame.parent) end return Elements diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua index 237c683a..ee4a4fa0 100644 --- a/exp_gui/module/prototype.lua +++ b/exp_gui/module/prototype.lua @@ -43,6 +43,8 @@ ExpElement.events = {} --- @field _force_data ExpElement.PostDrawCallback? --- @field _global_data ExpElement.PostDrawCallback? --- @field _events table[]> +--- @field _has_handlers boolean +--- @field _track_elements boolean --- @overload fun(parent: LuaGuiElement, ...: any): LuaGuiElement ExpElement._prototype = { _track_elements = false, @@ -166,7 +168,8 @@ end --- @param name string --- @return ExpElement function ExpElement.get(name) - return (assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name))) + local value = assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name)) + return value end --- Create a new instance of this element definition diff --git a/exp_legacy/module/expcore/external.lua b/exp_legacy/module/expcore/external.lua index be8fb078..c139179c 100644 --- a/exp_legacy/module/expcore/external.lua +++ b/exp_legacy/module/expcore/external.lua @@ -50,7 +50,8 @@ local servers = External.get_servers() ]] function External.get_servers() assert(ext, "No external data was found, use External.valid() to ensure external data exists.") - return (assert(ext.servers, "No server list was found, please ensure that the external service is running")) + local value = assert(ext.servers, "No server list was found, please ensure that the external service is running") + return value end --[[-- Gets a table of all the servers filtered by name, key is the server id, value is the server details @@ -85,7 +86,8 @@ function External.get_current_server() assert(ext, "No external data was found, use External.valid() to ensure external data exists.") local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running") local server_id = assert(ext.current, "No current id was found, please ensure that the external service is running") - return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))) + local value = assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)) + return value end --[[-- Gets the details of the given server @@ -99,7 +101,8 @@ local server = External.get_server_details('eu-01') function External.get_server_details(server_id) assert(ext, "No external data was found, use External.valid() to ensure external data exists.") local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running") - return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))) + local value = assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)) + return value end --[[-- Gets the status of the given server diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index a99e41cd..b010ad50 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -216,7 +216,7 @@ game.player.print(Roles.debug()) function Roles.debug() local output = "" for index, role_name in ipairs(Roles.config.order) do - local role = assert(Roles.config.roles[role_name]) + local role = Roles.config.roles[role_name] local color = (role.custom_color or Colours.white) --[[@as Color.struct]] local color_str = string.format("[color=%d, %d, %d]", color.r, color.g, color.b) output = output .. string.format("\n%s %s) %s[/color]", color_str, index, serpent.line(role)) @@ -369,14 +369,15 @@ local role = Roles.get_player_highest_role(game.player) --- @return Roles.Role function Roles.get_player_highest_role(player) local roles = Roles.get_player_roles(player) - local highest + local highest --- @type Roles.Role? for _, role in ipairs(roles) do if not highest or role.index < highest.index then highest = role end end - return (assert(highest, "Player has no roles")) + local value = assert(highest, "Player has no roles") + return value end --- Assignment. @@ -415,7 +416,7 @@ function Roles.assign_player(player, roles, by_player_name, skip_checks, silent) -- If the player has a role that needs to defer the role changes, save the roles that need to be assigned later into a table if valid_player and Roles.player_has_flag(valid_player, "defer_role_changes") then - local assign_later = Roles.config.deferred_roles[assert(valid_player).name] or {} + local assign_later = Roles.config.deferred_roles[valid_player.name] or {} for _, role in ipairs(role_objects) do local role_change = assign_later[role.name] if role_change then @@ -431,7 +432,7 @@ function Roles.assign_player(player, roles, by_player_name, skip_checks, silent) end end - Roles.config.deferred_roles[assert(valid_player).name] = assign_later + Roles.config.deferred_roles[valid_player.name] = assign_later return end @@ -477,8 +478,8 @@ function Roles.unassign_player(player, roles, by_player_name, skip_checks, silen -- If the player has a role that needs to defer the role changes, save the roles that need to be unassigned later into a table local defer_changes = Roles.player_has_flag(player, "defer_role_changes") - if defer_changes then - local assign_later = Roles.config.deferred_roles[assert(valid_player).name] or {} + if defer_changes and valid_player then + local assign_later = Roles.config.deferred_roles[valid_player.name] or {} for _, role in ipairs(role_objects) do local role_change = assign_later[role.name] if role_change then @@ -494,7 +495,7 @@ function Roles.unassign_player(player, roles, by_player_name, skip_checks, silen end end - Roles.config.deferred_roles[assert(valid_player).name] = assign_later + Roles.config.deferred_roles[valid_player.name] = assign_later end -- Remove the player from roles @@ -674,7 +675,7 @@ function Roles.define_role_order(order) -- Re-links roles to they parents as this is called at the end of the config for index, role_name in pairs(Roles.config.order) do - local role = assert(Roles.config.roles[role_name]) + local role = Roles.config.roles[role_name] if not role then error("Role with name " .. role_name .. " has not beed defined, either define it or remove it from the order list.", 2) end diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua index 2378c537..72afba6c 100644 --- a/exp_legacy/module/modules/control/vlayer.lua +++ b/exp_legacy/module/modules/control/vlayer.lua @@ -291,7 +291,7 @@ function vlayer.remove_item(item_name, count) if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then -- Remove from the unallocated storage first - remove_unallocated = math.min(count, assert(vlayer_data.storage.unallocated[item_name])) + remove_unallocated = math.min(count, vlayer_data.storage.unallocated[item_name]) if remove_unallocated > 0 then vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - count diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua index c83c8729..dd603bc0 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -381,14 +381,13 @@ local vlayer_gui_control_see = Gui.define("vlayer_gui_control_see") }:style{ width = 200, }:on_click(function(def, player, element, event) - local target = assert(assert(element.parent)[vlayer_gui_control_type.name]).selected_index - local n = assert(assert(element.parent)[vlayer_gui_control_list.name]).selected_index + local target = assert(element.parent)[vlayer_gui_control_type.name].selected_index + local n = assert(element.parent)[vlayer_gui_control_list.name].selected_index if target and vlayer_control_type_list[target] and n > 0 then local i = vlayer.get_interfaces() local entity = i[vlayer_control_type_list[target]][n] if entity and entity.valid then - local player = Gui.get_player(event) player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface } player.print{ "vlayer.result-interface-location", { "vlayer.control-type-" .. vlayer_control_type_list[target]:gsub("_", "-") }, pos_to_gps_string(entity.position, entity.surface.name) } end @@ -425,8 +424,8 @@ local vlayer_gui_control_remove = Gui.define("vlayer_gui_control_remove") }:style{ width = 200, }:on_click(function(def, player, element) - local target = assert(assert(element.parent)[vlayer_gui_control_type.name]).selected_index - local n = assert(assert(element.parent)[vlayer_gui_control_list.name]).selected_index + local target = assert(element.parent)[vlayer_gui_control_type.name].selected_index + local n = assert(element.parent)[vlayer_gui_control_list.name].selected_index if target and vlayer_control_type_list[target] and n > 0 then local i = vlayer.get_interfaces() diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index ec7777c4..a42ec2a0 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -294,10 +294,10 @@ local confirm_edit_button = Gui.define("confirm_edit_button") local parent = assert(element.parent) local grandparent = assert(parent.parent) local warp_id = parent.caption --[[@as string]] - local name_flow = assert(grandparent["name-" .. warp_id]) - local icon_flow = assert(grandparent["icon-" .. warp_id]) - local warp_name = assert(name_flow[warp_textfield.name]).text - local warp_icon = assert(icon_flow[warp_icon_editing.name]).elem_value --[[@as SignalID]] + local name_flow = grandparent["name-" .. warp_id] + local icon_flow = grandparent["icon-" .. warp_id] + local warp_name = name_flow[warp_textfield.name].text + local warp_icon = icon_flow[warp_icon_editing.name].elem_value --[[@as SignalID]] if warp_icon.type == nil then warp_icon.type = "item" end Warps.set_editing(warp_id, player.name) Warps.update_warp(warp_id, warp_name, warp_icon, player.name) diff --git a/exp_scenario/module/commands/connect.lua b/exp_scenario/module/commands/connect.lua index b935b45d..cf53b181 100644 --- a/exp_scenario/module/commands/connect.lua +++ b/exp_scenario/module/commands/connect.lua @@ -41,7 +41,7 @@ local function get_server_id(server) elseif status == "Offline" then return false, { "exp-commands_connect.offline", server_details.name } end - return true, server_id + return true, server_id --[[@as string]] elseif server_count_before > 0 then return false, { "exp-commands_connect.wrong-version", concat(server_names_before, ", ") } else diff --git a/exp_scenario/module/commands/reports.lua b/exp_scenario/module/commands/reports.lua index 6aebce38..aab4351c 100644 --- a/exp_scenario/module/commands/reports.lua +++ b/exp_scenario/module/commands/reports.lua @@ -43,11 +43,11 @@ Commands.new("create-report", { "exp-commands_reports.description-create" }) if Reports.report_player(other_player, player.name, reason) then local user_message = { "exp-commands_reports.response", other_player_name, reason } local admin_message = { "exp-commands_reports.response-admin", other_player_name, player_name, reason } - for _, player in ipairs(game.connected_players) do - if player.admin then - player.print(admin_message) + for _, connected_player in ipairs(game.connected_players) do + if connected_player.admin then + connected_player.print(admin_message) else - player.print(user_message) + connected_player.print(user_message) end end else diff --git a/exp_scenario/module/commands/search.lua b/exp_scenario/module/commands/search.lua index 1251420a..fe4bba77 100644 --- a/exp_scenario/module/commands/search.lua +++ b/exp_scenario/module/commands/search.lua @@ -91,7 +91,7 @@ local function sort_results(results, func) end end - return sorted + return sorted --[[@as SearchResult[] ]] end local display_players_time_format = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } diff --git a/exp_scenario/module/control/discord_alerts.lua b/exp_scenario/module/control/discord_alerts.lua index 249a3e2e..c4675140 100644 --- a/exp_scenario/module/control/discord_alerts.lua +++ b/exp_scenario/module/control/discord_alerts.lua @@ -32,7 +32,7 @@ end --- Get the player name from an event --- @param event { player_index: uint, by_player_name: string? } ---- @return string, string +--- @return string, string? local function get_player_name(event) local player = game.players[event.player_index] return player.name, event.by_player_name diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index 28912d85..905b5be2 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -102,11 +102,11 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") local action_name = Elements.container.get_selected_action(player) local button_data = action_name and config.buttons[action_name] if button_data and button_data.reason_callback then - local reason = assert(assert(element.parent).entry).text --[[@as string?]] + local reason = assert(element.parent).entry.text --[[@as string?]] if reason == nil or not reason:find("%S") then reason = "no reason given" end button_data.reason_callback(player, reason) end - assert(assert(element.parent).entry).text = "" + assert(element.parent).entry.text = "" Elements.container.set_selected_player(player, nil) Elements.player_table.refresh_player(player) end) --[[@as any]] diff --git a/exp_scenario/module/gui/quick_actions.lua b/exp_scenario/module/gui/quick_actions.lua index dcc0c235..92d358aa 100644 --- a/exp_scenario/module/gui/quick_actions.lua +++ b/exp_scenario/module/gui/quick_actions.lua @@ -43,7 +43,7 @@ local function new_quick_action(command, on_click) :style{ width = 160, } - :on_click(on_click or function(def, player, element, event) + :on_click(on_click or function(_def, player, _element, _event) command(player) end) diff --git a/exp_scenario/module/gui/research_milestones.lua b/exp_scenario/module/gui/research_milestones.lua index e830ee0f..4652edcd 100644 --- a/exp_scenario/module/gui/research_milestones.lua +++ b/exp_scenario/module/gui/research_milestones.lua @@ -270,10 +270,9 @@ Elements.container = Gui.define("research_milestones/container") local milestone_table = Elements.milestone_table(container) Elements.clock_label(header) - local force = Gui.get_player(parent).force + local force = Gui.get_player(parent).force --[[@as LuaForce]] def.data[force] = def.data[force] or {} -- used by start index and row data - local force = Gui.get_player(parent).force --[[@as LuaForce]] local start_index = Elements.container.calculate_starting_research_index(force) for research_index = start_index, start_index + display_size - 1 do local row_data = Elements.milestone_table.calculate_row_data(force, research_index) @@ -297,7 +296,7 @@ end --- @param research_index number --- @return number function Elements.container.get_achieved_time(force, research_index) - return Elements.container.data[force][research_index] + return assert(Elements.container.data[force][research_index]) end --- Calculate the starting research index for a force diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index 97ac2a41..ad7772af 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -109,7 +109,7 @@ function Elements.production_label.calculate_display_data(tooltip, value, cutoff end local suffix, caption = format_number(value) - display_data = display_data or {} + display_data = display_data or {} --[[@as Elements.production_label.display_data]] display_data.caption = caption display_data.suffix = suffix display_data.tooltip = tooltip @@ -221,7 +221,7 @@ function Elements.science_table.calculate_row_data(force, science_pack, row_data end -- Return the pack data - row_data = row_data or {} + row_data = row_data or {} --[[@as ExpGui_ScienceProduction.elements.science_table.row_data]] row_data.visible = production.total.made > 0 row_data.science_pack = science_pack row_data.icon_style = icon_style diff --git a/exp_scenario/module/gui/task_list.lua b/exp_scenario/module/gui/task_list.lua index 339a1bab..75858bad 100644 --- a/exp_scenario/module/gui/task_list.lua +++ b/exp_scenario/module/gui/task_list.lua @@ -612,7 +612,7 @@ function Elements.confirm_task_button.parse(message) -- Trim the spaces of the string local trimmed = string.gsub(message, "^%s*(.-)%s*$", "%1") local title, body = string.match(trimmed, "(.-)\n(.*)") - local parsed = { title = title, body = body } + local parsed = { title = title, body = body } --[[@as { title: string, body: string }]] if not title then -- If it doesn't match the pattern return the str as a title parsed.title = trimmed diff --git a/exp_util/module/module_exports.lua b/exp_util/module/module_exports.lua index 762fa1f5..358c1706 100644 --- a/exp_util/module/module_exports.lua +++ b/exp_util/module/module_exports.lua @@ -284,7 +284,7 @@ function ExpUtil.format_any(value, options) end return inspect(value, { depth = options.depth or 5, indent = "", newline = "", process = ExpUtil.safe_value }) end - return formatted + return formatted --[[@as LocalisedString]] end --- @alias Common.format_time_param_format "short" | "long" | "clock" @@ -522,7 +522,7 @@ end --- Insert a copy of the given items into the found entities. If no entities are found then they will be created if possible. --- @param options Common.copy_items_to_surface_param ---- @return LuaEntity # The last entity inserted into +--- @return LuaEntity? # The last entity inserted into, nil if there were no items function ExpUtil.copy_items_to_surface(options) local entity for item_index = 1, #options.items do @@ -542,7 +542,7 @@ end --- Insert a copy of the given items into the found entities. If no entities are found then they will be created if possible. --- @param options Common.move_items_to_surface_param ---- @return LuaEntity # The last entity inserted into +--- @return LuaEntity? # The last entity inserted into, nil if there were no items function ExpUtil.move_items_to_surface(options) local entity for item_index = 1, #options.items do @@ -564,7 +564,7 @@ end --- Move the given inventory into the found entities. If no entities are found then they will be created if possible. --- @param options Common.transfer_inventory_to_surface_param ---- @return LuaEntity # The last entity inserted into +--- @return LuaEntity? # The last entity inserted into, nil if there were no items function ExpUtil.transfer_inventory_to_surface(options) options.items = options.inventory local entity = ExpUtil.copy_items_to_surface(options) From d495a345d6412515a6bed446a81a9f91202ef608 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:04:16 +0000 Subject: [PATCH 43/46] Resolve clusterio through CLUSTERIO rather than in ci only The library path was injected by the workflow, so an editor opened on this repo could not resolve `modules/clusterio/*`. It now comes from the environment in both places. It cannot be written relative to this repo: emmylua does not normalise `..` in a library path, and an absolute path containing `..` fails the same way, so the location has to come from outside. Co-Authored-By: Claude Opus 5 (1M context) --- .emmyrc.json | 2 ++ .github/workflows/fmtk.yml | 7 ++++--- CONTRIBUTING.md | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index dbaab2ba..e6d9de6d 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -6,6 +6,8 @@ }, "workspace": { "$comment-moduleMap": "Replaces the fmtk --clusterio-modules plugin, rewrites each file's own module path rather than the require string", + "$comment-library": "clusterio is outside this repo, set CLUSTERIO to your checkout. The factorio typedefs come from .luarc.json, which the factoriomod-debug extension manages", + "library": [ "$CLUSTERIO/packages/host" ], "ignoreDir": [ ".github", "web", "dist", "factorio" ], "ignoreGlobs": [ "**/node_modules/**", "**/dist/**" ], "moduleMap": [ diff --git a/.github/workflows/fmtk.yml b/.github/workflows/fmtk.yml index 75d877d3..c74249d7 100644 --- a/.github/workflows/fmtk.yml +++ b/.github/workflows/fmtk.yml @@ -6,6 +6,7 @@ on: env: EMMYLUA_CHECK_VERSION: "0.24.0" + CLUSTERIO: ${{ github.workspace }}/.clusterio jobs: lint: @@ -36,9 +37,9 @@ jobs: - name: Run Lint Report shell: bash run: | - # Library paths are machine specific, so they are merged in rather than committed - jq -n --arg lib "$RUNNER_TEMP/factorio/library" --arg clusterio "$PWD/.clusterio/packages/host" \ - '{ workspace: { library: [ $lib, $clusterio ], ignoreGlobs: [ ".clusterio/**" ] } }' \ + # The typedefs are generated per run, clusterio comes from CLUSTERIO above + jq -n --arg lib "$RUNNER_TEMP/factorio/library" \ + '{ workspace: { library: [ $lib ], ignoreGlobs: [ ".clusterio/**" ] } }' \ > "$RUNNER_TEMP/ci.emmyrc.json" # Exits non zero on error severity, so the gate below is used instead diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d2fcc38..870be0f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ All are welcome to make bug reports, feature requests, and pull requests for our For developers wanting to add features please follow these guidelines: - All lua code is documented using ldoc. +- Set `CLUSTERIO` to your clusterio checkout, the lint needs it to resolve `modules/clusterio/*`. emmylua does not normalise `..` in a library path, so it cannot be written relative to this repo. - Lua is checked with [emmylua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust), configured by `.emmyrc.json`. Install the EmmyLua extension rather than sumneko, and let the factoriomod-debug extension write your machine local library paths to `.luarc.json`, which is git ignored. Note that an inline cast must be written `--[[@as T]]`, the spaced form is not parsed. - Changes should be made on your own fork and merged into `main` through a pull request. - Each pull request should be limited to one feature or a few bug fixes and link to the related issue page. From 1e861c5c0ba1b0787afbbe0cbaaa794f1975cf8a Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sat, 8 Aug 2026 21:58:46 +0100 Subject: [PATCH 44/46] Fix issue with MapPosition.energy --- .emmyrc.json | 2 +- exp_legacy/module/modules/gui/vlayer.lua | 2 +- exp_legacy/module/modules/gui/warp-list.lua | 2 +- exp_scenario/module/commands/artillery.lua | 2 +- exp_scenario/module/commands/protected_entities.lua | 2 +- exp_scenario/module/control/damage_popups.lua | 2 +- exp_scenario/module/control/degrading_tiles.lua | 2 +- exp_scenario/module/control/mine_depletion.lua | 8 ++++---- exp_scenario/module/gui/production_stats.lua | 2 +- exp_util/module/flying_text.lua | 6 +++--- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index e6d9de6d..213157cc 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -9,7 +9,7 @@ "$comment-library": "clusterio is outside this repo, set CLUSTERIO to your checkout. The factorio typedefs come from .luarc.json, which the factoriomod-debug extension manages", "library": [ "$CLUSTERIO/packages/host" ], "ignoreDir": [ ".github", "web", "dist", "factorio" ], - "ignoreGlobs": [ "**/node_modules/**", "**/dist/**" ], + "ignoreGlobs": [ "**/node_modules/**", "**/dist/**", "**/campaigns/**" ], "moduleMap": [ { "pattern": "^([^.]+)\\.module\\.module_exports$", "replace": "modules.$1" }, { "pattern": "^([^.]+)\\.module\\.(.+)$", "replace": "modules.$1.$2" } diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua index dd603bc0..727f9b9d 100644 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ b/exp_legacy/module/modules/gui/vlayer.lua @@ -326,7 +326,7 @@ local function vlayer_gui_list_refresh(player) for i = 1, vlayer.get_interface_counts()[vlayer_control_type_list[target]], 1 do local entity = assert(interface[i]) - local entity_position = entity.position --[[@as MapPosition.struct]] + local entity_position = entity.position table.insert(full_list, i .. " X " .. entity_position.x .. " Y " .. entity_position.y) end diff --git a/exp_legacy/module/modules/gui/warp-list.lua b/exp_legacy/module/modules/gui/warp-list.lua index a42ec2a0..15359dbc 100644 --- a/exp_legacy/module/modules/gui/warp-list.lua +++ b/exp_legacy/module/modules/gui/warp-list.lua @@ -799,7 +799,7 @@ Event.on_nth_tick(math.floor(60 / config.update_smoothing), function() end -- Check if the player is within range - local warp_pos = warp.position --[[@as MapPosition.struct]] + local warp_pos = warp.position if warp.surface == surface then local dx, dy = px - warp_pos.x, py - warp_pos.y local dist = (dx * dx) + (dy * dy) diff --git a/exp_scenario/module/commands/artillery.lua b/exp_scenario/module/commands/artillery.lua index 5a2e16bf..e0a5bdf3 100644 --- a/exp_scenario/module/commands/artillery.lua +++ b/exp_scenario/module/commands/artillery.lua @@ -67,7 +67,7 @@ SelectArea:on_selection(function(event) for _, entity in ipairs(entities) do local skip = false - local entity_position = entity.position --[[@as MapPosition.struct]] + local entity_position = entity.position for _, pos in ipairs(hits) do local x = abs(entity_position.x - pos.x) local y = abs(entity_position.y - pos.y) diff --git a/exp_scenario/module/commands/protected_entities.lua b/exp_scenario/module/commands/protected_entities.lua index b84d14d7..f2c71df2 100644 --- a/exp_scenario/module/commands/protected_entities.lua +++ b/exp_scenario/module/commands/protected_entities.lua @@ -51,7 +51,7 @@ local function show_protected_entity(player, entity) if renders[player.index][key] then return end local selection_box = entity.selection_box --[[@as ExpUtil_AABB.Box]] local rb = selection_box.right_bottom - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position renders[player.index][key] = rendering.draw_sprite{ sprite = "utility/notification", target = entity, diff --git a/exp_scenario/module/control/damage_popups.lua b/exp_scenario/module/control/damage_popups.lua index 146a94fe..803f95b6 100644 --- a/exp_scenario/module/control/damage_popups.lua +++ b/exp_scenario/module/control/damage_popups.lua @@ -26,7 +26,7 @@ local function on_entity_damaged(event) -- Outputs the message as floating text if message then - local entity_position = entity.position --[[@as MapPosition.struct]] + local entity_position = entity.position local entity_radius = max(1, entity.get_radius()) local offset = (random() - 0.5) * entity_radius * config.damage_location_variance local position = { x = entity_position.x + offset, y = entity_position.y - entity_radius } diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index 6d7b951f..7707c0e8 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -85,7 +85,7 @@ local function on_player_changed_position(event) if player.controller_type ~= defines.controllers.character then return end local surface = player.physical_surface - local position = player.physical_position --[[@as MapPosition.struct]] + local position = player.physical_position local strength = get_tile_strength(surface, position) if not strength then return end diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 0f54a676..7b5be7b5 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -68,7 +68,7 @@ local function try_deconstruct_output_chest(entity) end -- Get all adjacent mining drills and inserters - local target_position = target.position --[[@as MapPosition.struct]] + local target_position = target.position local entities = target.surface.find_entities_filtered{ type = { "mining-drill", "inserter" }, to_be_deconstructed = false, @@ -160,7 +160,7 @@ local function try_deconstruct_miner(entity) end -- Build pipes if the miner used fluid - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position local create_entity_position = { x = position.x, y = position.y } local create_entity_param = { name = "entity-ghost", inner_name = "pipe", force = entity.force, position = create_entity_position } local create_entity = surface.create_entity @@ -247,7 +247,7 @@ local function on_resource_depleted(event) end -- Find all mining drills within the area - local position = resource.position --[[@as MapPosition.struct]] + local position = resource.position local drills = resource.surface.find_entities_filtered{ type = "mining-drill", area = { @@ -259,7 +259,7 @@ local function on_resource_depleted(event) -- Check which could have reached this resource for _, drill in pairs(drills) do local radius = drill.prototype.mining_drill_radius - local drill_position = drill.position --[[@as MapPosition.struct]] + local drill_position = drill.position local dx = math.abs(drill_position.x - position.x) local dy = math.abs(drill_position.y - position.y) if dx <= radius and dy <= radius then diff --git a/exp_scenario/module/gui/production_stats.lua b/exp_scenario/module/gui/production_stats.lua index bc6cac8a..94b49562 100644 --- a/exp_scenario/module/gui/production_stats.lua +++ b/exp_scenario/module/gui/production_stats.lua @@ -130,7 +130,7 @@ Elements.production_table = Gui.define("production_stats/production_table") :track_all_elements() :draw(function(def, parent) local scroll_table = Gui.elements.scroll_table(parent, 304, 4) - local display_alignments = scroll_table.style.column_alignments + local display_alignments = assert(scroll_table.style.column_alignments) for i = 2, 4 do display_alignments[i] = "right" end diff --git a/exp_util/module/flying_text.lua b/exp_util/module/flying_text.lua index 84f87564..9ec23028 100644 --- a/exp_util/module/flying_text.lua +++ b/exp_util/module/flying_text.lua @@ -44,7 +44,7 @@ function FlyingText.create_above_entity(options) local entity = assert(options.target_entity, "A target entity is required") local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position local offset = options.offset or { x = 0, y = 0 } options.position = { @@ -66,7 +66,7 @@ function FlyingText.create_above_player(options) local entity = player.character; if not entity then return end local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position local offset = options.offset or { x = 0, y = 0 } options.position = { @@ -88,7 +88,7 @@ function FlyingText.create_as_player(options) local entity = player.character; if not entity then return end local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y - local position = entity.position --[[@as MapPosition.struct]] + local position = entity.position local offset = options.offset or { x = 0, y = 0 } options.color = player.chat_color From 5890155331265812f7e7afd4dc8e6f3b021f3d30 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:09:39 +0100 Subject: [PATCH 45/46] Final manual fixups --- CONTRIBUTING.md | 4 +- exp_commands/module/commands/help.lua | 10 ++--- exp_gui/module/control.lua | 12 +++--- exp_gui/module/elements.lua | 3 +- exp_gui/module/prototype.lua | 3 +- exp_legacy/module/expcore/datastore.lua | 4 +- exp_legacy/module/expcore/external.lua | 9 ++--- exp_legacy/module/expcore/roles.lua | 3 +- exp_scenario/module/commands/artillery.lua | 2 +- .../module/commands/protected_entities.lua | 6 +-- exp_scenario/module/commands/roles.lua | 3 +- .../module/control/deconstruction_log.lua | 5 +-- .../module/control/degrading_tiles.lua | 2 +- .../module/control/mine_depletion.lua | 2 +- .../module/control/station_auto_name.lua | 4 +- exp_scenario/module/gui/module_inserter.lua | 2 +- exp_scenario/module/gui/rocket_info.lua | 15 +++---- exp_util/module/aabb.lua | 39 ++++++++----------- exp_util/module/flying_text.lua | 6 +-- 19 files changed, 59 insertions(+), 75 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 870be0f9..9fa851c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,8 @@ All are welcome to make bug reports, feature requests, and pull requests for our For developers wanting to add features please follow these guidelines: - All lua code is documented using ldoc. -- Set `CLUSTERIO` to your clusterio checkout, the lint needs it to resolve `modules/clusterio/*`. emmylua does not normalise `..` in a library path, so it cannot be written relative to this repo. -- Lua is checked with [emmylua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust), configured by `.emmyrc.json`. Install the EmmyLua extension rather than sumneko, and let the factoriomod-debug extension write your machine local library paths to `.luarc.json`, which is git ignored. Note that an inline cast must be written `--[[@as T]]`, the spaced form is not parsed. +- Set the `CLUSTERIO` environment variable to your clusterio development install path, the lint needs it to resolve `modules/clusterio/*`. +- Lua is checked with [emmylua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust), configured by `.emmyrc.json`. Install the EmmyLua extension rather than sumnekolua / luals, and use factoriomod-debug to generate the factorio api types. Note that an inline cast must be written `--[[@as T]]`, the spaced form is not parsed. - Changes should be made on your own fork and merged into `main` through a pull request. - Each pull request should be limited to one feature or a few bug fixes and link to the related issue page. - Pull requests are automatically linted and documentation checked. diff --git a/exp_commands/module/commands/help.lua b/exp_commands/module/commands/help.lua index 2a6c3b15..bb8c6c8b 100644 --- a/exp_commands/module/commands/help.lua +++ b/exp_commands/module/commands/help.lua @@ -25,18 +25,18 @@ end) --- @param page_size number The number of requests to show per page --- @return LocalisedString[][], number local function format_as_pages(commands, page_size) - local pages = { {} } + local current_page = {} + local pages = { current_page } local page_length = 0 - local current_page = 1 local total = 0 for _, command in pairs(commands) do total = total + 1 page_length = page_length + 1 if page_length > page_size then - current_page = current_page + 1 - pages[current_page] = {} page_length = 1 + current_page = {} + pages[#pages + 1] = current_page end local description @@ -48,7 +48,7 @@ local function format_as_pages(commands, page_size) end local aliases = #command.aliases > 0 and { "exp-commands_help.aliases", table.concat(command.aliases, ", ") } or "" - assert(pages[current_page])[page_length] = { "exp-commands_help.format", command.name, description, aliases } + current_page[page_length] = { "exp-commands_help.format", command.name, description, aliases } end return pages, total diff --git a/exp_gui/module/control.lua b/exp_gui/module/control.lua index 0b5003a3..6c7114ec 100644 --- a/exp_gui/module/control.lua +++ b/exp_gui/module/control.lua @@ -116,8 +116,8 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_top_element(define, player) - local value = assert(assert(player_elements[player.index]).top[define.name], "Element is not on the top flow") - return value + local elements = assert(player_elements[player.index]) + return (assert(elements.top[define.name], "Element is not on the top flow")) end --- Register a element define to be drawn to the left flow on join @@ -125,8 +125,8 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_left_element(define, player) - local value = assert(assert(player_elements[player.index]).left[define.name], "Element is not on the left flow") - return value + local elements = assert(player_elements[player.index]) + return (assert(elements.left[define.name], "Element is not on the left flow")) end --- Register a element define to be drawn to the relative flow on join @@ -134,8 +134,8 @@ end --- @param player LuaPlayer --- @return LuaGuiElement function Gui.get_relative_element(define, player) - local value = assert(assert(player_elements[player.index]).relative[define.name], "Element is not on the relative flow") - return value + local elements = assert(player_elements[player.index]) + return (assert(elements.relative[define.name], "Element is not on the relative flow")) end --- Ensure all the correct elements are visible and exist diff --git a/exp_gui/module/elements.lua b/exp_gui/module/elements.lua index aa343a1d..b4dbd472 100644 --- a/exp_gui/module/elements.lua +++ b/exp_gui/module/elements.lua @@ -319,8 +319,7 @@ Elements.screen_frame = Gui.define("screen_frame") --- @param screen_frame LuaGuiElement --- @return LuaGuiElement function Elements.screen_frame.get_button_flow(screen_frame) - local value = assert(Elements.screen_frame.data[assert(screen_frame.parent)], "Screen frame has no button flow") - return value + return (assert(Elements.screen_frame.data[assert(screen_frame.parent)], "Screen frame has no button flow")) end --- Get the root element of a screen frame diff --git a/exp_gui/module/prototype.lua b/exp_gui/module/prototype.lua index ee4a4fa0..07890a2d 100644 --- a/exp_gui/module/prototype.lua +++ b/exp_gui/module/prototype.lua @@ -168,8 +168,7 @@ end --- @param name string --- @return ExpElement function ExpElement.get(name) - local value = assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name)) - return value + return (assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name))) end --- Create a new instance of this element definition diff --git a/exp_legacy/module/expcore/datastore.lua b/exp_legacy/module/expcore/datastore.lua index 07fe28d5..08fe4de1 100644 --- a/exp_legacy/module/expcore/datastore.lua +++ b/exp_legacy/module/expcore/datastore.lua @@ -150,6 +150,8 @@ local Storage = require("modules/exp_util/storage") local DatastoreManager = {} local Datastores = {} --- @type table +local Data = {} + --- @class Datastore --- @field name string --- @field value_name string @@ -163,7 +165,7 @@ local Datastores = {} --- @type table --- @field events table --- @field data table local Datastore = {} -local Data = {} + local copy = table.deep_copy local trace = debug.traceback local table_to_json = helpers.table_to_json diff --git a/exp_legacy/module/expcore/external.lua b/exp_legacy/module/expcore/external.lua index c139179c..be8fb078 100644 --- a/exp_legacy/module/expcore/external.lua +++ b/exp_legacy/module/expcore/external.lua @@ -50,8 +50,7 @@ local servers = External.get_servers() ]] function External.get_servers() assert(ext, "No external data was found, use External.valid() to ensure external data exists.") - local value = assert(ext.servers, "No server list was found, please ensure that the external service is running") - return value + return (assert(ext.servers, "No server list was found, please ensure that the external service is running")) end --[[-- Gets a table of all the servers filtered by name, key is the server id, value is the server details @@ -86,8 +85,7 @@ function External.get_current_server() assert(ext, "No external data was found, use External.valid() to ensure external data exists.") local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running") local server_id = assert(ext.current, "No current id was found, please ensure that the external service is running") - local value = assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)) - return value + return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))) end --[[-- Gets the details of the given server @@ -101,8 +99,7 @@ local server = External.get_server_details('eu-01') function External.get_server_details(server_id) assert(ext, "No external data was found, use External.valid() to ensure external data exists.") local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running") - local value = assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)) - return value + return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))) end --[[-- Gets the status of the given server diff --git a/exp_legacy/module/expcore/roles.lua b/exp_legacy/module/expcore/roles.lua index b010ad50..641eeaea 100644 --- a/exp_legacy/module/expcore/roles.lua +++ b/exp_legacy/module/expcore/roles.lua @@ -376,8 +376,7 @@ function Roles.get_player_highest_role(player) end end - local value = assert(highest, "Player has no roles") - return value + return (assert(highest, "Player has no roles")) end --- Assignment. diff --git a/exp_scenario/module/commands/artillery.lua b/exp_scenario/module/commands/artillery.lua index e0a5bdf3..9f93732b 100644 --- a/exp_scenario/module/commands/artillery.lua +++ b/exp_scenario/module/commands/artillery.lua @@ -15,7 +15,7 @@ local abs = math.abs local commands = {} --- @param player LuaPlayer ---- @param area ExpUtil_AABB.Box +--- @param area BoundingBox.struct --- @return boolean local function location_break(player, area) local surface = player.surface -- Allow remote view diff --git a/exp_scenario/module/commands/protected_entities.lua b/exp_scenario/module/commands/protected_entities.lua index f2c71df2..7f906413 100644 --- a/exp_scenario/module/commands/protected_entities.lua +++ b/exp_scenario/module/commands/protected_entities.lua @@ -37,7 +37,7 @@ end --- Get the key used in protected_areas --- TODO expose this from EntityProtection ---- @param area ExpUtil_AABB.Box +--- @param area BoundingBox.struct --- @return string local function get_area_key(area) return format_string("%i,%i", floor(area.left_top.x), floor(area.left_top.y)) @@ -49,7 +49,7 @@ end local function show_protected_entity(player, entity) local key = get_entity_key(entity) if renders[player.index][key] then return end - local selection_box = entity.selection_box --[[@as ExpUtil_AABB.Box]] + local selection_box = entity.selection_box local rb = selection_box.right_bottom local position = entity.position renders[player.index][key] = rendering.draw_sprite{ @@ -69,7 +69,7 @@ end --- Show a protected area to a player --- @param player LuaPlayer --- @param surface LuaSurface ---- @param area ExpUtil_AABB.Box +--- @param area BoundingBox.struct local function show_protected_area(player, surface, area) local key = get_area_key(area) if renders[player.index][key] then return end diff --git a/exp_scenario/module/commands/roles.lua b/exp_scenario/module/commands/roles.lua index 7cbfd8d0..b8a510ad 100644 --- a/exp_scenario/module/commands/roles.lua +++ b/exp_scenario/module/commands/roles.lua @@ -55,8 +55,7 @@ Commands.new("get-roles", { "exp-commands_roles.description-get" }) end local last = #roles_formatted - --- @diagnostic disable-next-line: need-check-nil - roles_formatted[last] = roles_formatted[last][2] + roles_formatted[last] = assert(roles_formatted[last])[2] return Commands.status.success(response) end) diff --git a/exp_scenario/module/control/deconstruction_log.lua b/exp_scenario/module/control/deconstruction_log.lua index 5d1743be..b6d2b05d 100644 --- a/exp_scenario/module/control/deconstruction_log.lua +++ b/exp_scenario/module/control/deconstruction_log.lua @@ -42,7 +42,7 @@ local function format_position(pos) end --- Convert an area to a string ---- @param area ExpUtil_AABB.Box +--- @param area BoundingBox.struct --- @return string local function format_area(area) return format_string("%.1f,%.1f,%.1f,%.1f", area.left_top.x, area.left_top.y, area.right_bottom.x, area.right_bottom.y) @@ -137,8 +137,7 @@ local function on_player_ammo_inventory_changed(event) if not player or not player.character then return end local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo)) - local gun_index = player.character.selected_gun_index - --- @cast gun_index uint + local gun_index = player.character.selected_gun_index --[[@as uint]] local item = character_ammo[gun_index] if not item or not item.valid or not item.valid_for_read then return diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index 7707c0e8..dc3a1b11 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -32,7 +32,7 @@ local function degrade_entity(entity) local tiles = {} local surface = entity.surface - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box local left_top = bounding_box.left_top local right_bottom = bounding_box.right_bottom for x = left_top.x, right_bottom.x do diff --git a/exp_scenario/module/control/mine_depletion.lua b/exp_scenario/module/control/mine_depletion.lua index 7b5be7b5..f8a6cb55 100644 --- a/exp_scenario/module/control/mine_depletion.lua +++ b/exp_scenario/module/control/mine_depletion.lua @@ -167,7 +167,7 @@ local function try_deconstruct_miner(entity) create_entity(create_entity_param) -- Find all the entities to connect to - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box local search_area = { { bounding_box.left_top.x - 1, bounding_box.left_top.y - 1 }, { bounding_box.right_bottom.x + 1, bounding_box.right_bottom.y + 1 }, diff --git a/exp_scenario/module/control/station_auto_name.lua b/exp_scenario/module/control/station_auto_name.lua index de8e5595..96b8fc09 100644 --- a/exp_scenario/module/control/station_auto_name.lua +++ b/exp_scenario/module/control/station_auto_name.lua @@ -57,7 +57,7 @@ local function rename_station(event) -- Find the closest resource local icon = "" local item_name = "" - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box local resources = entity.surface.find_entities_filtered{ position = entity.position, radius = 250, type = "resource" } if #resources > 0 then local closest_recourse --- @type LuaEntity? @@ -66,7 +66,7 @@ local function rename_station(event) -- Check which recourse is closest for _, resource in ipairs(resources) do - local resource_box = resource.bounding_box --[[@as ExpUtil_AABB.Box]] + local resource_box = resource.bounding_box local dx = px - resource_box.left_top.x local dy = py - resource_box.left_top.y local distance = (dx * dx) + (dy * dy) diff --git a/exp_scenario/module/gui/module_inserter.lua b/exp_scenario/module/gui/module_inserter.lua index 74e1b287..33011cec 100644 --- a/exp_scenario/module/gui/module_inserter.lua +++ b/exp_scenario/module/gui/module_inserter.lua @@ -427,7 +427,7 @@ local function on_entity_settings_pasted(event) -- Attempt to rotate a machine to match the source machine if (source.name == destination.name or source.prototype.fast_replaceable_group == destination.prototype.fast_replaceable_group) then if source.supports_direction and destination.supports_direction and source.type ~= "transport-belt" then - local destination_box = destination.bounding_box --[[@as ExpUtil_AABB.Box]] + local destination_box = destination.bounding_box local ltx = destination_box.left_top.x local lty = destination_box.left_top.y diff --git a/exp_scenario/module/gui/rocket_info.lua b/exp_scenario/module/gui/rocket_info.lua index 7265238d..d7999db3 100644 --- a/exp_scenario/module/gui/rocket_info.lua +++ b/exp_scenario/module/gui/rocket_info.lua @@ -433,8 +433,7 @@ function Elements.progress_table.add_row(progress_table, row_data) progress.style.padding = { 0, 2 } progress.style.font_color = row_data.color - local unit_number = row_data.entity.unit_number - --- @cast unit_number uint + local unit_number = row_data.entity.unit_number --[[@as uint]] rows[unit_number] = { x = x, y = y, progress = progress } end @@ -457,8 +456,7 @@ end --- @param row_data ExpGui_RocketInfo.elements.progress_table.row_data function Elements.progress_table.refresh_row(progress_table, row_data) local element_data = Elements.progress_table.data[progress_table] - local unit_number = row_data.entity.unit_number - --- @cast unit_number uint + local unit_number = row_data.entity.unit_number --[[@as uint]] local row = element_data.rows[unit_number] row.x.caption = row_data.x row.y.caption = row_data.y @@ -624,8 +622,7 @@ end function Elements.container.add_silo(entity) local force = entity.force --[[@as LuaForce]] local silos = Elements.container._get_force_data(force).silos - local unit_number = entity.unit_number - --- @cast unit_number uint + local unit_number = entity.unit_number --[[@as uint]] silos[unit_number] = { entity = entity, launched = 0, @@ -661,9 +658,8 @@ Gui.toolbar.create_button{ --- Record the launch and update the stats when a cargo pod finishes ascending --- @param event EventData.on_cargo_pod_finished_ascending local function on_cargo_pod_finished_ascending(event) - local force = event.cargo_pod.force - --- @cast force LuaForce - local rockets_launched = force.rockets_launched + local force = event.cargo_pod.force --[[@as LuaForce]] + local rockets_launched = force.rockets_launched --[[@as number]] -- Update the launch stats for the force local stats = Elements.container.get_stats(force) @@ -678,7 +674,6 @@ local function on_cargo_pod_finished_ascending(event) -- Append the launch tick into the times array local times = Elements.container.get_launch_times(force) - --- @cast rockets_launched uint times[rockets_launched] = event.tick -- Discard the launch time that is no longer needed by any rolling average unless it is a milestone diff --git a/exp_util/module/aabb.lua b/exp_util/module/aabb.lua index 9ac567d6..7b985702 100644 --- a/exp_util/module/aabb.lua +++ b/exp_util/module/aabb.lua @@ -7,31 +7,26 @@ local ceil = math.ceil local min = math.min local max = math.max ---- An axis aligned bounding box using named members, the shorthand form is not supported ---- @class ExpUtil_AABB.Box ---- @field left_top MapPosition.struct ---- @field right_bottom MapPosition.struct - --- @class ExpUtil_AABB local AABB = {} --- Check if an area is valid ---- @param aabb ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct --- @return boolean # True if the area is valid function AABB.valid(aabb) return aabb.left_top.x < aabb.right_bottom.x and aabb.left_top.y < aabb.right_bottom.y end --- Returns the size of the area contained within an AABB ---- @param aabb ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct --- @return number function AABB.size(aabb) return (aabb.right_bottom.x - aabb.left_top.x) * (aabb.right_bottom.y - aabb.left_top.y) end --- Clone an area, allows for safe mutation of an input value ---- @param aabb ExpUtil_AABB.Box ---- @return ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct +--- @return BoundingBox.struct function AABB.clone(aabb) return { left_top = { x = aabb.left_top.x, y = aabb.left_top.y }, @@ -40,8 +35,8 @@ function AABB.clone(aabb) end --- Expand an area to be integer aligned, expanding away from 0 ---- @param aabb ExpUtil_AABB.Box ---- @return ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct +--- @return BoundingBox.struct function AABB.expand(aabb) return { left_top = { x = floor(aabb.left_top.x), y = floor(aabb.left_top.y) }, @@ -50,8 +45,8 @@ function AABB.expand(aabb) end --- Contract an area to be integer aligned, contracting towards 0 ---- @param aabb ExpUtil_AABB.Box ---- @return ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct +--- @return BoundingBox.struct function AABB.contract(aabb) return { left_top = { x = ceil(aabb.left_top.x), y = ceil(aabb.left_top.y) }, @@ -60,9 +55,9 @@ function AABB.contract(aabb) end --- Expand an area to include all other areas ---- @param aabb ExpUtil_AABB.Box ---- @param ... ExpUtil_AABB.Box ---- @return ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct +--- @param ... BoundingBox.struct +--- @return BoundingBox.struct function AABB.union(aabb, ...) local rtn = AABB.clone(aabb) for _, next_aabb in ipairs{ ... } do @@ -75,9 +70,9 @@ function AABB.union(aabb, ...) end --- Contract an area to include to the overlap of all areas ---- @param aabb ExpUtil_AABB.Box ---- @param ... ExpUtil_AABB.Box ---- @return ExpUtil_AABB.Box? # Nil if there is no intersection +--- @param aabb BoundingBox.struct +--- @param ... BoundingBox.struct +--- @return BoundingBox.struct? # Nil if there is no intersection function AABB.intersect(aabb, ...) local rtn = AABB.clone(aabb) for _, next_aabb in ipairs{ ... } do @@ -93,7 +88,7 @@ function AABB.intersect(aabb, ...) end --- Check if a point is contained within an area ---- @param aabb ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct --- @param point MapPosition.struct --- @return boolean # True if the point is within or on the edge of the bounding box function AABB.contains_point(aabb, point) @@ -102,8 +97,8 @@ function AABB.contains_point(aabb, point) end --- Check if an area is fulling contained within another area ---- @param aabb ExpUtil_AABB.Box ---- @param other ExpUtil_AABB.Box +--- @param aabb BoundingBox.struct +--- @param other BoundingBox.struct --- @return boolean # True if the point is within or on the edge of the bounding box function AABB.contains_area(aabb, other) return AABB.contains_point(aabb, other.left_top) and AABB.contains_point(aabb, other.right_bottom) diff --git a/exp_util/module/flying_text.lua b/exp_util/module/flying_text.lua index 9ec23028..180cd77e 100644 --- a/exp_util/module/flying_text.lua +++ b/exp_util/module/flying_text.lua @@ -42,7 +42,7 @@ end --- @param options FlyingText.create_above_entity_param function FlyingText.create_above_entity(options) local entity = assert(options.target_entity, "A target entity is required") - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y local position = entity.position local offset = options.offset or { x = 0, y = 0 } @@ -64,7 +64,7 @@ end function FlyingText.create_above_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y local position = entity.position local offset = options.offset or { x = 0, y = 0 } @@ -86,7 +86,7 @@ end function FlyingText.create_as_player(options) local player = assert(options.target_player, "A target player is required") local entity = player.character; if not entity then return end - local bounding_box = entity.bounding_box --[[@as ExpUtil_AABB.Box]] + local bounding_box = entity.bounding_box local size_y = bounding_box.left_top.y - bounding_box.right_bottom.y local position = entity.position local offset = options.offset or { x = 0, y = 0 } From fd38816ff65e6f76caaebaaeaf0bb16d0629e2ef Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:33:54 +0000 Subject: [PATCH 46/46] Write the factorio typedefs where the config looks for them `fmtk luals-addon ` treats its argument as the parent and always appends `factorio`, so the bundle landed in `$RUNNER_TEMP/factorio/factorio` while the config pointed at `$RUNNER_TEMP/factorio/library`. The factorio types were never loaded, which is why ci reported 642 undefined-global and 1204 type-not-found against a tree that is clean locally. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/fmtk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fmtk.yml b/.github/workflows/fmtk.yml index c74249d7..227585f1 100644 --- a/.github/workflows/fmtk.yml +++ b/.github/workflows/fmtk.yml @@ -29,7 +29,7 @@ jobs: - name: Generate Factorio typedefs run: | pnpm install factoriomod-debug - pnpm exec fmtk luals-addon "$RUNNER_TEMP/factorio" + pnpm exec fmtk luals-addon "$RUNNER_TEMP" - name: Install emmylua_check run: | curl -fsSL "https://github.com/EmmyLuaLs/emmylua-analyzer-rust/releases/download/${EMMYLUA_CHECK_VERSION}/emmylua_check-linux-x64.tar.gz" \