diff --git a/exp_legacy/module/config/bonus.lua b/exp_legacy/module/config/bonus.lua deleted file mode 100644 index 5c1a3c22..00000000 --- a/exp_legacy/module/config/bonus.lua +++ /dev/null @@ -1,105 +0,0 @@ ---- Lists all bonuses which can be used, name followed by min max --- @config Bonuses - -return { - --[[ - Base point is equal to the amount of standard value in each parameter. - - CMMS CRS CCS CISB CHB CRDB PBR - STD 20 60 24 10 12 8 40 - = 174 - - MAX 40 120 48 20 24 16 80 - = 348 - ]] - points = { - base = 174, - increase_percentage_per_role_level = 0.03, - role_name = "Member", - }, - periodic_bonus_rate = 300, - player_bonus = { - { - name = "character_mining_speed_modifier", - scale = 1, - cost = 10, - max_value = 4, - value_step = 0.5, - is_percentage = true, - }, - { - name = "character_running_speed_modifier", - scale = 1, - cost = 60, - max_value = 2, - value_step = 0.25, - is_percentage = true, - }, - { - name = "character_crafting_speed_modifier", - scale = 1, - cost = 4, - max_value = 12, - value_step = 1, - is_percentage = true, - }, - { - name = "character_inventory_slots_bonus", - scale = 10, - cost = 2, - max_value = 100, - value_step = 10, - }, - { - name = "character_health_bonus", - scale = 50, - cost = 4, - max_value = 300, - value_step = 50, - }, - { - name = "character_reach_distance_bonus", - scale = 1, - cost = 1, - max_value = 16, - value_step = 2, - combined_bonus = { - "character_resource_reach_distance_bonus", - "character_build_distance_bonus", - }, - }, - { - name = "personal_battery_recharge", - scale = 4, - cost = 40, - max_value = 8, - value_step = 1, - is_special = true, - }, - }, - force_bonus = { - ["worker_robots_battery_modifier"] = { - max_value = 1, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false, - }, - ["worker_robots_storage_bonus"] = { - max_value = 1, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false, - }, - ["following_robots_lifetime_modifier"] = { - max_value = 1, - value_step = 1, - scale = 1, - cost = 1, - is_percentage = false, - }, - }, - surface_bonus = { - }, -} diff --git a/exp_legacy/module/config/compilatron.lua b/exp_legacy/module/config/compilatron.lua deleted file mode 100644 index 53a6cc3e..00000000 --- a/exp_legacy/module/config/compilatron.lua +++ /dev/null @@ -1,26 +0,0 @@ ---- Config file for the compliatrons including where they spawn and what messages they show --- @config Compilatron -local config_server_detail = require("modules.exp_legacy.config.server_detail") --- @dep config.server_detail - -return { - message_cycle = 60 * 15, --- @setting message_cycle 15 seconds default, how often (in ticks) the messages will cycle - locations = { - ["Spawn"] = { - spawn_position = { x = 0, y = 0 }, - spawn_surface = "nauvis", - entity_name = "small-biter", - messages = { - { "info.website", config_server_detail["website"] }, - { "info.read-readme" }, - { "info.discord", config_server_detail["discord"] }, - { "info.softmod" }, - { "info.redmew" }, - { "info.custom-commands" }, - { "info.status", config_server_detail["status"] }, - { "info.lhd" }, - { "info.github", config_server_detail["github"] }, - { "info.patreon", config_server_detail["patreon"] }, - }, - } - }, -} diff --git a/exp_legacy/module/config/deconlog.lua b/exp_legacy/module/config/deconlog.lua deleted file mode 100644 index c55dba24..00000000 --- a/exp_legacy/module/config/deconlog.lua +++ /dev/null @@ -1,11 +0,0 @@ ---- This config controls whether actions such as deconning by players without sufficient permissions is logged or not --- @config Deconlog - -return { - decon_area = true, --- @setting decon_area whether to log when an area is being deconstructed - built_entity = true, --- @setting built_entity whether to log when an entity is built - mined_entity = true, --- @setting mined_entity whether to log when an entity is mined - fired_rocket = true, --- @setting fired_nuke whether to log when a rocket is fired - fired_explosive_rocket = true, --- @setting fired_nuke whether to log when a explosive rocket is fired - fired_nuke = true, --- @setting fired_nuke whether to log when a nuke is fired -} diff --git a/exp_legacy/module/config/graftorio.lua b/exp_legacy/module/config/graftorio.lua deleted file mode 100644 index 9848a1ca..00000000 --- a/exp_legacy/module/config/graftorio.lua +++ /dev/null @@ -1,7 +0,0 @@ -return { - modules = { - ["forcestats"] = true, - ["logistorage"] = false, - ["other"] = true, - }, -} diff --git a/exp_legacy/module/config/gui/rockets.lua b/exp_legacy/module/config/gui/rockets.lua deleted file mode 100644 index c1c7e53f..00000000 --- a/exp_legacy/module/config/gui/rockets.lua +++ /dev/null @@ -1,47 +0,0 @@ ---- This file controls what will show in each section of the rocket info gui --- @config Rockets - -return { - stats = { --- @setting stats The data that will show in the stats section - show_stats = true, --- @setting show_stats false will hide this section all together - show_first_rocket = true, --- @setting show_first_rocket false will not show when the first rocket was launched - show_last_rocket = true, --- @setting show_last_rocket false will not show when the last rocket was launched - show_fastest_rocket = true, --- @setting show_fastest_rocket false will not show the time taken for the fastest rocket - show_total_rockets = true, --- @setting show_total_rockets false will not show the total number of rockets launched - show_game_avg = true, --- @setting show_game_avg false will hide the avg across the entire map time - rolling_avg = { --- @setting rolling_avg each number will be one statistic; 5 means the avg time taken for the last 5 rockets - 5, 10, 25, - }, - }, - milestones = { --- @setting milestones each number will be one statistic; 5 means the time that the 5th rocket was launched - show_milestones = true, --- @setting show_milestones false will hide this section all together - 1, - 2, - 5, - 10, - 20, - 50, - 100, - 200, - 500, - 1000, - 1500, - 2000, - 2500, - 3000, - 3500, - 4000, - 4500, - 5000, - }, - progress = { --- @setting progress The data and buttons in the build progress section - show_progress = true, --- @setting show_progress false will hide this section altogether - allow_zoom_to_map = true, --- @setting allow_zoom_to_map false will disable the zoom to map feature - allow_remote_launch = true, --- @setting allow_remote_launch false removes the remote launch button for all players - remote_launch_admins_only = false, --- @setting remote_launch_admins_only true will remove the remote launch button for all non (game) admins - remote_launch_role_permission = "exp_scenario.gui.rocket_info.remote_launch", --- @setting remote_launch_role_permission value used by custom permission system to allow or disallow the button - allow_toggle_active = true, --- @setting allow_toggle_active false removes the remote toggle auto launch button for all players - toggle_active_admins_only = false, --- @setting toggle_active_admins_only true will remove the toggle auto launch button for all non (game) admins - toggle_active_role_permission = "exp_scenario.gui.rocket_info.toggle_active", --- @setting toggle_active_role_permission value used by custom permission system to allow or disallow the button - }, -} diff --git a/exp_legacy/module/config/gui/science.lua b/exp_legacy/module/config/gui/science.lua deleted file mode 100644 index b8128bbe..00000000 --- a/exp_legacy/module/config/gui/science.lua +++ /dev/null @@ -1,21 +0,0 @@ ---- Config file for the science info gui --- @config Science - -return { - -- list of all science packs to be shown in the gui - show_eta = true, --- @setting show_eta when true the eta for research completion will be shown - color_cutoff = 0.8, --- @setting color_cutoff the amount that production can fall before the text changes color - color_flux = 0.1, --- @setting color_flux the amount of fluctuation allowed in production before the icon changes color - "automation-science-pack", - "logistic-science-pack", - "military-science-pack", - "chemical-science-pack", - "production-science-pack", - "utility-science-pack", - "space-science-pack", - "metallurgic-science-pack", - "agricultural-science-pack", - "electromagnetic-science-pack", - "cryogenic-science-pack", - "promethium-science-pack", -} diff --git a/exp_legacy/module/config/gui/tasks.lua b/exp_legacy/module/config/gui/tasks.lua deleted file mode 100644 index 969b6ab3..00000000 --- a/exp_legacy/module/config/gui/tasks.lua +++ /dev/null @@ -1,13 +0,0 @@ ---- Config file for the tasks gui --- @config Tasks - -return { - -- Adding tasks - allow_add_task = "all", --- @setting allow_add_task dictates who is allowed to add new tasks; values: all, admin, exp_roles, none - exp_roles_allow_add_task = "exp_scenario.gui.task_list.add", --- @setting exp_roles_allow_add_task if exp_roles is used then this is the required permission - - -- Editing tasks - allow_edit_task = "exp_roles", --- @setting allow_edit_task dictates who is allowed to edit existing tasks; values: all, admin, exp_roles, none - exp_roles_allow_edit_task = "exp_scenario.gui.task_list.edit", --- @setting exp_roles_allow_edit_task if exp_roles is used then this is the required permission - user_can_edit_own_tasks = true, --- @settings if true then the user who made the task can edit it regardless of the allow_edit_task setting -} diff --git a/exp_legacy/module/config/gui/warps.lua b/exp_legacy/module/config/gui/warps.lua deleted file mode 100644 index fa6c142b..00000000 --- a/exp_legacy/module/config/gui/warps.lua +++ /dev/null @@ -1,52 +0,0 @@ ---- This file contains all the different settings for the warp system and gui --- @config Warps - -return { - -- General config - update_smoothing = 10, --- @setting update_smoothing the amount of smoothing applied to updates to the cooldown timer, higher is better, max is 60 - minimum_distance = 100, --- @setting minimum_distance the minimum distance that is allowed between warps on the same force - default_icon = { type = "item", name = "discharge-defense-equipment" }, --- @setting default_icon the default icon that will be used for warps - - -- Warp cooldowns - bypass_warp_cooldown = "exp_roles", --- @setting bypass_warp_cooldown dictates who the warp cooldown is applied to; values: all, admin, exp_roles, none - exp_roles_bypass_warp_cooldown = "exp_scenario.gui.warp_list.bypass_cooldown", --- @setting exp_roles_bypass_warp_cooldown if exp_roles is used then this is the required permission - cooldown_duration = 60, --- @setting cooldown_duration the duration of the warp cooldown in seconds - - -- Warp proximity - bypass_warp_proximity = "exp_roles", --- @setting bypass_warp_proximity dictates who the warp proximity is applied to; values: all, admin, exp_roles, none - exp_roles_bypass_warp_proximity = "exp_scenario.gui.warp_list.bypass_proximity", --- @setting exp_roles_bypass_warp_proximity if exp_roles is used then this is the required permission - standard_proximity_radius = 4, --- @setting standard_proximity_radius the minimum distance a player is allowed to be to a warp in order to use it - spawn_proximity_radius = 20, --- @setting spawn_proximity_radius the minimum distance a player is allowed to be from they spawn point to use warps - - -- Adding warps - allow_add_warp = "exp_roles", --- @setting allow_add_warp dictates who is allowed to add warps; values: all, admin, exp_roles, none - exp_roles_allow_add_warp = "exp_scenario.gui.warp_list.add", --- @setting exp_roles_allow_add_warp if exp_roles is used then this is the required permission - - -- Editing warps - allow_edit_warp = "exp_roles", --- @setting allow_edit_warp dictates who is allowed to edit warps; values: all, admin, exp_roles, none - exp_roles_allow_edit_warp = "exp_scenario.gui.warp_list.edit", --- @setting exp_roles_allow_edit_warp if exp_roles is used then this is the required permission - user_can_edit_own_warps = false, --- @settings user_can_edit_own_warps if true then the user who made the warp can edit it regardless of the allow_edit_warp setting - - -- Warp area generation - entities = { --- @setting entities The entities which are created for warp areas - { "small-lamp", -4, -2 }, { "small-lamp", -2, -4 }, { "medium-electric-pole", -3, -3 }, -- Top left corner - { "small-lamp", 3, -2 }, { "small-lamp", 1, -4 }, { "medium-electric-pole", 2, -3 }, -- Top right corner - { "small-lamp", 3, 1 }, { "small-lamp", 1, 3 }, { "medium-electric-pole", 2, 2 }, -- Bottom right corner - { "small-lamp", -4, 1 }, { "small-lamp", -2, 3 }, { "medium-electric-pole", -3, 2 }, -- Bottom left corner - }, - tiles = { --- @setting tiles The tiles which are created for warp areas - { "black-refined-concrete", -4, -2 }, { "black-refined-concrete", -4, -1 }, { "black-refined-concrete", -4, 0 }, { "black-refined-concrete", -4, 1 }, - { "black-refined-concrete", -3, -3 }, { "purple-refined-concrete", -3, -2 }, { "purple-refined-concrete", -3, -1 }, { "purple-refined-concrete", -3, 0 }, - { "purple-refined-concrete", -3, 1 }, { "black-refined-concrete", -3, 2 }, { "black-refined-concrete", -2, -4 }, { "purple-refined-concrete", -2, -3 }, - { "purple-refined-concrete", -2, -2 }, { "purple-refined-concrete", -2, -1 }, { "purple-refined-concrete", -2, 0 }, { "purple-refined-concrete", -2, 1 }, - { "purple-refined-concrete", -2, 2 }, { "black-refined-concrete", -2, 3 }, { "black-refined-concrete", -1, -4 }, { "purple-refined-concrete", -1, -3 }, - { "purple-refined-concrete", -1, -2 }, { "purple-refined-concrete", -1, -1 }, { "purple-refined-concrete", -1, 0 }, { "purple-refined-concrete", -1, 1 }, - { "purple-refined-concrete", -1, 2 }, { "black-refined-concrete", -1, 3 }, { "black-refined-concrete", 0, -4 }, { "purple-refined-concrete", 0, -3 }, - { "purple-refined-concrete", 0, -2 }, { "purple-refined-concrete", 0, -1 }, { "purple-refined-concrete", 0, 0 }, { "purple-refined-concrete", 0, 1 }, - { "purple-refined-concrete", 0, 2 }, { "black-refined-concrete", 0, 3 }, { "black-refined-concrete", 1, -4 }, { "purple-refined-concrete", 1, -3 }, - { "purple-refined-concrete", 1, -2 }, { "purple-refined-concrete", 1, -1 }, { "purple-refined-concrete", 1, 0 }, { "purple-refined-concrete", 1, 1 }, - { "purple-refined-concrete", 1, 2 }, { "black-refined-concrete", 1, 3 }, { "black-refined-concrete", 2, -3 }, { "purple-refined-concrete", 2, -2 }, - { "purple-refined-concrete", 2, -1 }, { "purple-refined-concrete", 2, 0 }, { "purple-refined-concrete", 2, 1 }, { "black-refined-concrete", 2, 2 }, - { "black-refined-concrete", 3, -2 }, { "black-refined-concrete", 3, -1 }, { "black-refined-concrete", 3, 0 }, { "black-refined-concrete", 3, 1 }, - }, -} diff --git a/exp_legacy/module/config/inventory_clear.lua b/exp_legacy/module/config/inventory_clear.lua deleted file mode 100644 index f0f24419..00000000 --- a/exp_legacy/module/config/inventory_clear.lua +++ /dev/null @@ -1,9 +0,0 @@ ---- Config to control when players items are removed, this is a list of event names that will trigger inventory clear --- @config inventory_clear - -local events = defines.events -return { - events.on_player_banned, - events.on_player_kicked, - -- events.on_player_left_game -} diff --git a/exp_legacy/module/config/logging.lua b/exp_legacy/module/config/logging.lua deleted file mode 100644 index eb66cfbf..00000000 --- a/exp_legacy/module/config/logging.lua +++ /dev/null @@ -1,30 +0,0 @@ ---- Settings for logging --- @config logging - -return { - file_name = "log/logging.log", - rocket_launch_display = { - [1] = true, - [2] = true, - [5] = true, - [10] = true, - [20] = true, - [50] = true, - [100] = true, - [200] = true, - }, - rocket_launch_display_rate = 500, - disconnect_reason = { - [defines.disconnect_reason.quit] = "left the game", - [defines.disconnect_reason.dropped] = "was dropped from the game", - [defines.disconnect_reason.reconnect] = "is reconnecting", - [defines.disconnect_reason.wrong_input] = "was having a wrong input", - [defines.disconnect_reason.desync_limit_reached] = "had desync limit reached", - [defines.disconnect_reason.cannot_keep_up] = "cannot keep up", - [defines.disconnect_reason.afk] = "was afk", - [defines.disconnect_reason.kicked] = "was kicked", - [defines.disconnect_reason.kicked_and_deleted] = "was kicked and deleted", - [defines.disconnect_reason.banned] = "was banned", - [defines.disconnect_reason.switching_servers] = "is switching servers", - }, -} diff --git a/exp_legacy/module/config/module.lua b/exp_legacy/module/config/module.lua deleted file mode 100644 index 1e073453..00000000 --- a/exp_legacy/module/config/module.lua +++ /dev/null @@ -1,86 +0,0 @@ -return { - module_slots_per_row = 4, - module_slot_max = 8, - copy_paste_module = true, - copy_paste_rotation = false, - machines = { - }, - machine_sets = { - ["base"] = { - ["electric-mining-drill"] = { - ["module"] = "efficiency-module", - ["prod"] = true, - }, - ["pumpjack"] = { - ["module"] = "efficiency-module", - ["prod"] = true, - }, - ["assembling-machine-2"] = { - ["module"] = "productivity-module", - ["prod"] = true, - }, - ["assembling-machine-3"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["electric-furnace"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["beacon"] = { - ["module"] = "speed-module-3", - ["prod"] = false, - }, - ["oil-refinery"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["chemical-plant"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["centrifuge"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["lab"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["rocket-silo"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - } - }, - ["space-age"] = { - ["big-mining-drill"] = { - ["module"] = "efficiency-module", - ["prod"] = true, - }, - ["biochamber"] = { - ["module"] = "efficiency-module", - ["prod"] = true, - }, - ["electromagnetic-plant"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["cryogenic-plant"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["biolab"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["foundry"] = { - ["module"] = "productivity-module-3", - ["prod"] = true, - }, - ["recycler"] = { - ["module"] = "quality-module-3", - ["prod"] = true, - } - } - } -} diff --git a/exp_legacy/module/config/pollution_grading.lua b/exp_legacy/module/config/pollution_grading.lua deleted file mode 100644 index 07e5f3f2..00000000 --- a/exp_legacy/module/config/pollution_grading.lua +++ /dev/null @@ -1,9 +0,0 @@ ---- This controls how pollution is viewed on the map --- @config Pollution-Grading - -return { - reference_point = { x = 0, y = 0 }, --- @setting reference_point where pollution is read from - max_scalar = 0.5, --- @setting max_scalar the scale between true max and max - min_scalar = 0.17, --- @setting min_scalar the scale between the lowest max and min - update_delay = 15, --- @setting update_delay time in minutes between view updates -} diff --git a/exp_legacy/module/config/repair.lua b/exp_legacy/module/config/repair.lua deleted file mode 100644 index e2fd69ad..00000000 --- a/exp_legacy/module/config/repair.lua +++ /dev/null @@ -1,8 +0,0 @@ ---- Config file for the repair command --- @config Repair - -return { - allow_blueprint_repair = false, --- @setting allow_blueprint_repair when true will allow blueprints (things not destroyed by biters) to be build instantly using the repair command - allow_ghost_revive = true, --- @setting allow_ghost_revive when true will allow ghosts (things destroyed by biters) to be build instantly using the repair command - allow_heal_entities = true, --- @setting allow_heal_entities when true will heal entities to full health that are within range -} diff --git a/exp_legacy/module/config/scorched_earth.lua b/exp_legacy/module/config/scorched_earth.lua deleted file mode 100644 index ee33cd44..00000000 --- a/exp_legacy/module/config/scorched_earth.lua +++ /dev/null @@ -1,116 +0,0 @@ ---- This file controls the placement/degrading of tiles as players build and walk --- @config Scorched-Earth - -return { - weakness_value = 70, --- @setting weakness_value lower value will make tiles more likely to degrade - strengths = { --- @setting strengths this decides how "strong" a tile is, bigger number means less likely to degrade - -- debug: /interface require('modules.addons.worn-paths')(player.name,true) - -- note: tiles are effected by the tiles around them, so player paths will not degrade as fast when made wider - -- note: values are relative to the tile with the highest value, recommended to keep highest tile as a "nice" number - -- note: tiles not in list will never degrade under any conditions (which is why some are omitted such as water) - ["refined-concrete"] = 100, - ["refined-hazard-concrete-left"] = 100, - ["refined-hazard-concrete-right"] = 100, - ["concrete"] = 90, - ["hazard-concrete-left"] = 90, - ["hazard-concrete-right"] = 90, - ["stone-path"] = 80, - ["red-desert-0"] = 80, - ["dry-dirt"] = 50, - -- grass four (main grass tiles) - ["grass-1"] = 50, - ["grass-2"] = 40, - ["grass-3"] = 30, - ["grass-4"] = 25, - -- red three (main red tiles) - ["red-desert-1"] = 40, - ["red-desert-2"] = 30, - ["red-desert-3"] = 25, - -- sand three (main sand tiles) - ["sand-1"] = 40, - ["sand-2"] = 30, - ["sand-3"] = 25, - -- dirt 3 (main dirt tiles) - ["dirt-1"] = 40, - ["dirt-2"] = 30, - ["dirt-3"] = 25, - -- last three/four (all sets of three merge here) - ["dirt-4"] = 25, - ["dirt-5"] = 30, - ["dirt-6"] = 40, - -- ["dirt-7"]=0, -- last tile, nothing to degrade to - -- land fill chain - -- ["landfill"]=50, - -- ["water-shallow"]=90, - -- ["water-mud"]=0, -- last tile, nothing to degrade to - }, - degrade_order = { --- @setting degrade_order when a tile degrades it will turn into the next tile given here - ["refined-concrete"] = "concrete", - ["refined-hazard-concrete-left"] = "hazard-concrete-left", - ["refined-hazard-concrete-right"] = "hazard-concrete-right", - ["concrete"] = "stone-path", - ["hazard-concrete-left"] = "stone-path", - ["hazard-concrete-right"] = "stone-path", - ["stone-path"] = "dry-dirt", - ["red-desert-0"] = "dry-dirt", - ["dry-dirt"] = "dirt-4", - -- grass four (main grass tiles) - ["grass-1"] = "grass-2", - ["grass-2"] = "grass-3", - ["grass-3"] = "grass-4", - ["grass-4"] = "dirt-4", - -- red three (main red tiles) - ["red-desert-1"] = "red-desert-2", - ["red-desert-2"] = "red-desert-3", - ["red-desert-3"] = "dirt-4", - -- sand three (main sand tiles) - ["sand-1"] = "sand-2", - ["sand-2"] = "sand-3", - ["sand-3"] = "dirt-4", - -- dirt 3 (main dirt tiles) - ["dirt-1"] = "dirt-2", - ["dirt-2"] = "dirt-3", - ["dirt-3"] = "dirt-4", - -- last three/four (all sets of three merge here) - ["dirt-4"] = "dirt-5", - ["dirt-5"] = "dirt-6", - ["dirt-6"] = "dirt-7", - -- ["dirt-7"]=0, -- last tile, nothing to degrade to - -- land fill chain - -- ["landfill"]='grass-2', -- 'water-shallow' - -- ["water-shallow"]='water-mud', - -- ["water-mud"]=0, -- last tile, nothing to degrade to - }, - entities = { --- @setting entities entities in this list will degrade the tiles under them when they are placed - ["stone-furnace"] = true, - ["steel-furnace"] = true, - ["electric-furnace"] = true, - ["assembling-machine-1"] = true, - ["assembling-machine-2"] = true, - ["assembling-machine-3"] = true, - ["beacon"] = true, - ["centrifuge"] = true, - ["chemical-plant"] = true, - ["oil-refinery"] = true, - ["storage-tank"] = true, - ["nuclear-reactor"] = true, - ["steam-engine"] = true, - ["steam-turbine"] = true, - ["boiler"] = true, - ["heat-exchanger"] = true, - ["stone-wall"] = true, - ["gate"] = true, - ["gun-turret"] = true, - ["laser-turret"] = true, - ["flamethrower-turret"] = true, - ["radar"] = true, - ["lab"] = true, - ["big-electric-pole"] = true, - ["substation"] = true, - ["rocket-silo"] = true, - ["pumpjack"] = true, - ["electric-mining-drill"] = true, - ["roboport"] = true, - ["accumulator"] = true, - }, -} diff --git a/exp_legacy/module/config/vlayer.lua b/exp_legacy/module/config/vlayer.lua deleted file mode 100644 index c2e0761a..00000000 --- a/exp_legacy/module/config/vlayer.lua +++ /dev/null @@ -1,180 +0,0 @@ ---- Settings for vlayer including the allowed items, the update frequency, and some cheats --- @config Vlayer - -return { - update_tick_storage = 60, --- @setting update_tick_storage The number of ticks between each update of the storage interfaces - update_tick_energy = 10, --- @setting update_tick_energy The number of ticks between each update of the energy and circuit interfaces - update_tick_gui = 60, --- @setting update_tick_gui The number of ticks between each update of the gui - - unlimited_capacity = false, --- @setting unlimited_capacity When true the vlayer has an unlimited energy capacity, accumulators are not required - unlimited_surface_area = false, --- @setting unlimited_surface_area When true the vlayer has an unlimited surface area, landfill is not required - modded_auto_downgrade = false, --- @setting modded_auto_downgrade When true modded items will be converted into their base game equivalent, original items can not be recovered - power_on_space = false, --- @setting power_on_space When true allow on spaceship - power_on_space_research = { --- @setting power_on_space_research the research level needed to use power_on_space - name = "research-productivity", - level = 10 - }, - - mimic_surface = "nauvis", --- @setting mimic_surface Surface name/index the vlayer will copy its settings from, use nil to use the settings below - surface = { --- @setting surface When mimic_surface is nil these settings will be used instead, see LuaSurface for details - always_day = false, - solar_power_multiplier = 1, - min_brightness = 0.15, - ticks_per_day = 25200, - daytime = 0, - dusk = 0.25, - evening = 0.45, - morning = 0.55, - dawn = 0.75, - }, - - interface_limit = { --- @setting interface_limit Sets the limit for the number of vlayer interfaces that can be created - energy = 1, -- >1 allows for disconnected power networks to receive power - circuit = 20, -- No caveats - storage_input = 20, -- No caveats - 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: - starting_value : The amount of the item placed into the vlayer on game start, ignores area requirements - required_area : When greater than 0 the items properties are not applied unless their is sufficient surplus surface area - production : The energy production of the item in MW, used for solar panels - discharge : The energy discharge of the item in MW, used for accumulators - capacity : The energy capacity of the item in MJ, used for accumulators - surface_area : The surface area provided by the item, used for landfill - ]] - ["solar-panel"] = { - starting_value = 0, - required_area = 9, - production = 0.06, -- MW - }, - ["accumulator"] = { - starting_value = 0, - required_area = 4, - discharge = 0.3, -- MW - capacity = 5, -- MJ - }, - ["landfill"] = { - starting_value = 0, - required_area = 0, - surface_area = 20, -- Tiles - }, - ["wood"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 2, -- MJ - power = true, -- turn all wood to power to reduce trash - }, - ["coal"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 4, -- MJ - power = false, -- turn all coal to power to reduce trash - }, - ["solid-fuel"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 12, -- MJ - power = false, -- turn all solid fuel to power to reduce trash - }, - ["rocket-fuel"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 100, -- MJ - power = false, -- turn all rocket fuel to power to reduce trash - } - }, - - modded_items = { --- @setting modded_items List of all modded items allowed in vlayer storage and their base game equivalent - ["solar-panel-2"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 4, - }, - ["solar-panel-3"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 16, - }, - ["solar-panel-4"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 64, - }, - ["solar-panel-5"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 256, - }, - ["solar-panel-6"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 1024, - }, - ["solar-panel-7"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 4096, - }, - ["solar-panel-8"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 16384, - }, - ["accumulator-2"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 4, - }, - ["accumulator-3"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 16, - }, - ["accumulator-4"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 64, - }, - ["accumulator-5"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 256, - }, - ["accumulator-6"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 1024, - }, - ["accumulator-7"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 4096, - }, - ["accumulator-8"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 16384, - }, - } -} diff --git a/exp_scenario/features.ts b/exp_scenario/features.ts index 758ae798..294ee9bf 100644 --- a/exp_scenario/features.ts +++ b/exp_scenario/features.ts @@ -81,6 +81,47 @@ export const features: Feature[] = [ str("command_prefix", "Command prefix", "Prefix for chat commands, and for key words to reply to everyone", "!"), ], }, + { + name: "custom_start", + title: "Custom Start", + description: "Map settings applied when the map is created, and the items new players start with", + fields: [ + bool("skip_intro", "Skip intro", "Skip the freeplay intro", true), + bool("skip_victory", "Skip victory", "Skip the victory screen when a rocket is launched", true), + bool("friendly_fire", "Friendly fire", "Players can damage each other", false), + bool("disable_crashsite", "No crash site", "Do not create the crash site", true), + bool("enemy_expansion", "Enemy expansion", "Biters expand, in case the map settings file fails to load", false), + num("chart_radius", "Chart radius", "Tiles charted around spawn when the map starts", 320, 0, "tiles"), + ], + }, + { + name: "deconstruction_log", + title: "Deconstruction Log", + description: "Write actions of players without the bypass permission to log/deconstruction.log", + fields: [ + bool("decon_area", "Deconstructed areas", "Log when an area is deconstructed", true), + bool("built_entity", "Built entities", "Log when an entity is built", true), + bool("mined_entity", "Mined entities", "Log when an entity is mined", true), + bool("fired_rocket", "Rockets", "Log when a rocket is fired", true), + bool("fired_explosive_rocket", "Explosive rockets", "Log when an explosive rocket is fired", true), + bool("fired_nuke", "Nukes", "Log when a nuke is fired", true), + ], + }, + { + name: "degrading_tiles", + title: "Degrading Tiles", + description: "Tiles wear down as players walk on them and build on them", + fields: [ + num("weakness_value", "Weakness", "Lower values make tiles degrade sooner", 70, 1), + list("entities", "Degrading entities", "Entities which degrade the tiles under them when built", [ + "stone-furnace", "steel-furnace", "electric-furnace", "assembling-machine-1", "assembling-machine-2", "assembling-machine-3", + "beacon", "centrifuge", "chemical-plant", "oil-refinery", "storage-tank", "nuclear-reactor", + "steam-engine", "steam-turbine", "boiler", "heat-exchanger", "stone-wall", "gate", + "gun-turret", "laser-turret", "flamethrower-turret", "radar", "lab", "big-electric-pole", + "substation", "rocket-silo", "pumpjack", "electric-mining-drill", "roboport", "accumulator", + ]), + ], + }, { name: "death_markers", title: "Death Markers", @@ -111,6 +152,27 @@ export const features: Feature[] = [ ]), ], }, + { + name: "extra_logging", + title: "Extra Logging", + description: "Write rockets, deaths, research, joins, and leaves to a log file", + fields: [ + str("file_name", "File name", "Path of the log file within script-output", "log/logging.log"), + num("rocket_launch_display_rate", "Rocket log interval", "After the first few launches, log every this many rockets", 500, 1, "rockets"), + ], + }, + { + name: "help_bubbles", + title: "Help Bubbles", + description: "A compilatron at spawn which cycles through tips and the community links", + fields: [], + }, + { + name: "inventory_clear", + title: "Inventory Clear", + description: "Move the items of banned and kicked players to a chest at spawn", + fields: [], + }, { name: "lawnmower", title: "Lawnmower", @@ -129,6 +191,15 @@ export const features: Feature[] = [ bool("fluid", "Replace with pipes", "Place pipe ghosts where a drill carried fluid", true), ], }, + { + name: "module_inserter", + title: "Module Inserter", + description: "A tool to fill machines with modules by selecting an area", + fields: [ + bool("copy_paste_module", "Copy modules", "Copying a machine also copies its modules", true), + bool("copy_paste_rotation", "Copy rotation", "Copying a machine also copies its rotation", false), + ], + }, { name: "nuke_protection", title: "Nuke Protection", @@ -137,6 +208,27 @@ export const features: Feature[] = [ list("banned_items", "Banned items", "Items which are moved to a chest at spawn", ["atomic-bomb"]), ], }, + { + name: "player_bonus", + title: "Player Bonus", + description: "Points players spend on personal bonuses, the bonuses live in config/player_bonus.lua", + fields: [ + num("points.base", "Base points", "The points of the points role, the standard value of each bonus adds up to this", 174, 0, "points"), + num("points.increase_percentage_per_role_level", "Points per role level", "Extra points as a fraction of the base for each role above the points role", 0.03, 0), + role("points.role_id", "Points role", "The role the base points apply to, without one every player gets the base"), + ], + }, + { + name: "pollution_grading", + title: "Pollution Grading", + description: "Scale the pollution overlay so it is not one red mess", + fields: [ + num("reference_point.x", "Reference X", "Where pollution is read from", 0), + num("reference_point.y", "Reference Y", "Where pollution is read from", 0), + num("max_scalar", "Max scale", "The scale between the true max and the shown max", 0.5, 0), + num("min_scalar", "Min scale", "The scale between the shown max and the shown min", 0.17, 0), + ], + }, { name: "popups", title: "Popups", @@ -166,6 +258,63 @@ export const features: Feature[] = [ ]), ], }, + { + name: "research", + title: "Research", + description: "Research effects, the per level limits live in config/research_data.lua", + fields: [ + bool("pollution_ageing_by_research", "Pollution ageing", "Pollution ages faster with each level of the bonus inventory researches", false), + bool("bonus_inventory.enabled", "Bonus inventory", "Extra inventory slots for each level of the researches below", true), + list("bonus_inventory.res", "Bonus researches", "Researches which grant the extra slots", [ + "mining-productivity", "mining-productivity-2", "mining-productivity-3", "mining-productivity-4", + ]), + num("bonus_inventory.rate", "Slots per level", "Extra slots for each research level", 5, 0, "slots"), + num("bonus_inventory.limit", "Slot limit", "The most extra slots a force can have", 20, 0, "slots"), + ], + }, + { + name: "research_milestones", + title: "Research Milestones", + description: "A GUI tracking research against target times, the targets live in config/research_data.lua", + fields: [ + str("log_file", "Log file", "Where the milestone times are written within script-output", "log/research.log"), + ], + }, + { + name: "rocket_info", + title: "Rocket Info", + description: "A GUI with rocket launch statistics, milestones, and build progress", + fields: [ + bool("show_stats", "Statistics section", "Show the statistics section", true), + bool("show_first_rocket", "First rocket", "Show when the first rocket was launched", true), + bool("show_last_rocket", "Last rocket", "Show when the last rocket was launched", true), + bool("show_fastest_rocket", "Fastest rocket", "Show the time taken for the fastest rocket", true), + bool("show_total_rockets", "Total rockets", "Show the total number of rockets launched", true), + bool("show_game_avg", "Game average", "Show the average across the entire map time", true), + bool("show_milestones", "Milestones section", "Show when each milestone rocket was launched", true), + bool("show_progress", "Progress section", "Show the build progress of each silo", true), + bool("allow_zoom_to_map", "Zoom to map", "Clicking a silo zooms the map to it", true), + ], + }, + { + name: "science_production", + title: "Science Production", + description: "A GUI with the production of each science pack", + fields: [ + bool("show_eta", "Show ETA", "Show the estimated time until the current research completes", true), + num("color_flux", "Colour threshold", "How much production may fluctuate before the icon changes colour", 0.1, 0), + ], + }, + { + name: "repair", + title: "Repair", + description: "The /repair command which revives ghosts and heals entities in an area", + fields: [ + bool("allow_ghost_revive", "Revive ghosts", "Build ghosts in the area instantly", true), + bool("allow_blueprint_repair", "Revive blueprints", "Also revive ghosts which were never built, not only those left by destroyed entities", false), + bool("allow_heal_entities", "Heal entities", "Heal entities in the area to full health", true), + ], + }, { name: "spawn_area", title: "Spawn Area", @@ -202,6 +351,44 @@ export const features: Feature[] = [ str("station_name", "Station name", "Placeholders: __icon__ __item_name__ __backer_name__ __direction__ __x__ __y__", "[L] __icon__"), ], }, + { + name: "task_list", + title: "Task List", + description: "A shared list of tasks, adding and editing need the task list permissions", + fields: [ + bool("user_can_edit_own_tasks", "Own tasks editable", "The player who made a task can edit it without the edit permission", true), + ], + }, + { + name: "warp_list", + title: "Warp List", + description: "Warp points players can travel between, adding, editing, and bypassing limits need the warp list permissions", + fields: [ + num("minimum_distance", "Minimum distance", "Warps of a force can not be closer together than this", 100, 1, "tiles"), + num("cooldown_duration", "Cooldown", "Wait between warps", 60, 0, "seconds"), + num("standard_proximity_radius", "Warp radius", "How close a player must stand to a warp to use it", 4, 1, "tiles"), + num("spawn_proximity_radius", "Spawn radius", "How close a player must stand to spawn to use it as a warp", 20, 1, "tiles"), + bool("user_can_edit_own_warps", "Own warps editable", "The player who made a warp can edit it without the edit permission", false), + ], + }, + { + name: "vlayer", + title: "Virtual Layer", + description: "A shared virtual surface for solar panels, accumulators, and storage, the items live in config/vlayer_items.lua", + fields: [ + bool("unlimited_capacity", "Unlimited capacity", "Energy storage without accumulators", false), + bool("unlimited_surface_area", "Unlimited area", "Place items without landfill", false), + bool("modded_auto_downgrade", "Downgrade modded items", "Convert modded items to their base game equivalent, the originals can not be recovered", false), + bool("power_on_space", "Power on platforms", "Energy interfaces can be built on space platforms", false), + str("power_on_space_research.name", "Platform research", "The research needed to build energy interfaces on platforms", "research-productivity"), + num("power_on_space_research.level", "Platform research level", "The level of that research", 10, 0), + str("mimic_surface", "Mimic surface", "The surface the day cycle is copied from, empty for a fixed cycle", "nauvis"), + num("interface_limit.energy", "Energy interfaces", "More than one lets disconnected power networks receive power", 1, 0), + num("interface_limit.circuit", "Circuit interfaces", "How many circuit interfaces can exist", 20, 0), + num("interface_limit.storage_input", "Input interfaces", "How many storage input interfaces can exist", 20, 0), + num("interface_limit.storage_output", "Output interfaces", "More than zero allows item teleportation of the allowed items", 1, 0), + ], + }, ]; /** Check values against the fields of a feature, returning those which differ from their default */ diff --git a/exp_scenario/module/commands/repair.lua b/exp_scenario/module/commands/repair.lua index 6e886387..10ba9722 100644 --- a/exp_scenario/module/commands/repair.lua +++ b/exp_scenario/module/commands/repair.lua @@ -4,9 +4,15 @@ Adds a command that allows an admin to repair and revive a large area local AABB = require("modules/exp_util/aabb") local Commands = require("modules/exp_commands") -local config = require("modules.exp_legacy.config.repair") --- @dep config.repair +local Feature = require("modules/exp_scenario/features") local Selection = require("modules/exp_util/selection") -local SelectArea = Selection.connect("ExpCommand_Waterfill") +local SelectArea = Selection.connect("ExpCommand_Repair") + +local feature, config = Feature.register("repair", { + allow_blueprint_repair = false, -- revive ghosts which were never built, not only those left by destroyed entities + allow_ghost_revive = true, -- revive ghosts in the area + allow_heal_entities = true, -- heal entities in the area to full health +}) --- @class ExpCommands_Repair.commands local commands = {} @@ -14,6 +20,7 @@ local commands = {} --- Toggle player selection mode --- @class ExpCommands_Repair.commands.repair: ExpCommand commands.repair = Commands.new("repair", { "exp-commands_repair.description" }) + :add_flags{ feature = feature } :register(function(player) if SelectArea:stop(player) then return Commands.status.success{ "exp_util.selection_exit", { "exp-commands_repair.selection-name" } } @@ -22,7 +29,7 @@ commands.repair = Commands.new("repair", { "exp-commands_repair.description" }) return Commands.status.success{ "exp_util.selection_enter", { "exp-commands_repair.selection-name" } } end) ---- When an area is selected to be converted to water +--- When an area is selected to be repaired SelectArea:on_selection(function(event) local player = assert(game.get_player(event.player_index)) local area = AABB.expand(event.area) diff --git a/exp_legacy/module/config/gui/autofill.lua b/exp_scenario/module/config/autofill.lua similarity index 98% rename from exp_legacy/module/config/gui/autofill.lua rename to exp_scenario/module/config/autofill.lua index bcade17c..d44a53ef 100644 --- a/exp_legacy/module/config/gui/autofill.lua +++ b/exp_scenario/module/config/autofill.lua @@ -3,7 +3,7 @@ local config = { -- General config - icon = "item/piercing-rounds-magazine", -- @setting icon that will be used for the toolbar + icon = "item/piercing-rounds-magazine", -- that will be used for the toolbar categories = { ammo = "ammo", fuel = "fuel", diff --git a/exp_scenario/module/config/module_inserter_machines.lua b/exp_scenario/module/config/module_inserter_machines.lua new file mode 100644 index 00000000..c328725a --- /dev/null +++ b/exp_scenario/module/config/module_inserter_machines.lua @@ -0,0 +1,82 @@ +--- The machines the module inserter can fill, and the modules it puts in them, by mod set +-- @config Module-Inserter-Machines + +return { + ["base"] = { + ["electric-mining-drill"] = { + ["module"] = "efficiency-module", + ["prod"] = true, + }, + ["pumpjack"] = { + ["module"] = "efficiency-module", + ["prod"] = true, + }, + ["assembling-machine-2"] = { + ["module"] = "productivity-module", + ["prod"] = true, + }, + ["assembling-machine-3"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["electric-furnace"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["beacon"] = { + ["module"] = "speed-module-3", + ["prod"] = false, + }, + ["oil-refinery"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["chemical-plant"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["centrifuge"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["lab"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["rocket-silo"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + } + }, + ["space-age"] = { + ["big-mining-drill"] = { + ["module"] = "efficiency-module", + ["prod"] = true, + }, + ["biochamber"] = { + ["module"] = "efficiency-module", + ["prod"] = true, + }, + ["electromagnetic-plant"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["cryogenic-plant"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["biolab"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["foundry"] = { + ["module"] = "productivity-module-3", + ["prod"] = true, + }, + ["recycler"] = { + ["module"] = "quality-module-3", + ["prod"] = true, + } + } + +} diff --git a/exp_scenario/module/config/player_bonus.lua b/exp_scenario/module/config/player_bonus.lua new file mode 100644 index 00000000..ce688d05 --- /dev/null +++ b/exp_scenario/module/config/player_bonus.lua @@ -0,0 +1,63 @@ +--- The bonuses players can spend their points on, see gui/player_bonus.lua +-- @config Player-Bonus + +return { + { + name = "character_mining_speed_modifier", + scale = 1, + cost = 10, + max_value = 4, + value_step = 0.5, + is_percentage = true, + }, + { + name = "character_running_speed_modifier", + scale = 1, + cost = 60, + max_value = 2, + value_step = 0.25, + is_percentage = true, + }, + { + name = "character_crafting_speed_modifier", + scale = 1, + cost = 4, + max_value = 12, + value_step = 1, + is_percentage = true, + }, + { + name = "character_inventory_slots_bonus", + scale = 10, + cost = 2, + max_value = 100, + value_step = 10, + }, + { + name = "character_health_bonus", + scale = 50, + cost = 4, + max_value = 300, + value_step = 50, + }, + { + name = "character_reach_distance_bonus", + scale = 1, + cost = 1, + max_value = 16, + value_step = 2, + combined_bonus = { + "character_resource_reach_distance_bonus", + "character_build_distance_bonus", + }, + }, + { + name = "personal_battery_recharge", + scale = 4, + cost = 40, + max_value = 8, + value_step = 1, + is_special = true, + }, + +} diff --git a/exp_legacy/module/config/research.lua b/exp_scenario/module/config/research_data.lua similarity index 85% rename from exp_legacy/module/config/research.lua rename to exp_scenario/module/config/research_data.lua index 73e710d8..ab130149 100644 --- a/exp_legacy/module/config/research.lua +++ b/exp_scenario/module/config/research_data.lua @@ -1,40 +1,20 @@ ---- Res Settings --- @config Research +--- Research data by mod set, the first set in mod_set_lookup whose mod is active is used +-- @config Research-Data -return { - enabled = true, - pollution_ageing_by_research = false, - queue_amount = 3, - mod_set = "base", +local data = { mod_set_lookup = { "space-age", }, - -- this enable 20 more inventory for each mining productivity level up to 4 - bonus_inventory = { - enabled = true, - res = { - -- Mining Productivity - ["mining-productivity"] = true, - ["mining-productivity-2"] = true, - ["mining-productivity-3"] = true, - ["mining-productivity-4"] = true, + final_res = { + ["base"] = { + ["name"] = "mining-productivity-4", + ["level"] = 4 }, - name = "character_inventory_slots_bonus", - rate = 5, - limit = 20, - }, - file = { - name = "log/research.log", - final_res = { - ["base"] = { - ["name"] = "mining-productivity-4", - ["level"] = 4 - }, - ["space-age"] = { - ["name"] = "mining-productivity-3", - ["level"] = 3 - } + ["space-age"] = { + ["name"] = "mining-productivity-3", + ["level"] = 3 } + }, milestone = { ["base"] = { @@ -177,3 +157,13 @@ return { -- ["atomic-bomb"] = 1 }, } + +data.mod_set = "base" +for _, mod_name in ipairs(data.mod_set_lookup) do + if script.active_mods[mod_name] then + data.mod_set = mod_name + break + end +end + +return data diff --git a/exp_legacy/module/config/advanced_start.lua b/exp_scenario/module/config/starting_items.lua similarity index 52% rename from exp_legacy/module/config/advanced_start.lua rename to exp_scenario/module/config/starting_items.lua index 31fb6154..5511c7d5 100644 --- a/exp_legacy/module/config/advanced_start.lua +++ b/exp_scenario/module/config/starting_items.lua @@ -1,14 +1,12 @@ ---- This file is used to setup the map starting settings and the items players will start with --- @config Advanced-Start +--- The items players start with, and how many depending on what the factory has made +-- @config Starting-Items --- These are called factories because they return another function -- use these as a simple methods of adding new items -- they will do most of the work for you -- ['item-name'] = factory(params) --- luacheck:ignore 212/amount_made 212/items_made 212/player -- Use these to adjust for ticks ie game.tick < 5*minutes --- luacheck:ignore 211/seconds 211/minutes 211/hours local seconds, minutes, hours = 60, 3600, 216000 --- Use to make a split point for the number of items given based on time @@ -72,41 +70,34 @@ end hours, minutes, seconds are the number of ticks in each unit of time ]] +--- @type table return { - skip_intro = true, --- @setting skip_intro skips the intro given in the default factorio free play scenario - skip_victory = true, --- @setting skip_victory will skip the victory screen when a rocket is launched - friendly_fire = false, --- @setting friendly_fire weather players will be able to attack each other on the same force - disable_crashsite = true, --- @setting disable_crashsite weather to disable creation of the crashsite - enemy_expansion = false, --- @setting enemy_expansion a catch all for in case the map settings file fails to load - chart_radius = 10 * 32, --- @setting chart_radius the number of tiles that will be charted when the map starts - items = { --- @setting items items and there condition for being given - -- ['item-name'] = function(amount_made, production_stats, player) return end -- 0 means no items given - -- Plates - ["iron-plate"] = scale_amount_made(100, 10, 10), - ["copper-plate"] = scale_amount_made(100, 0, 8), - ["steel-plate"] = scale_amount_made(100, 0, 4), - -- Secondary Items - ["electronic-circuit"] = scale_amount_made(1000, 0, 6), - ["iron-gear-wheel"] = scale_amount_made(1000, 0, 6), - -- Starting Items - ["burner-mining-drill"] = cutoff_time(10 * minutes, 4, 0), - ["stone-furnace"] = cutoff_time(10 * minutes, 4, 0), - -- Armor - ["light-armor"] = cutoff_amount_made_unless(5, 0, 1, "heavy-armor", 5), - ["heavy-armor"] = cutoff_amount_made(5, 0, 1), - -- Weapon - ["pistol"] = cutoff_amount_made_unless(0, 1, 1, "submachine-gun", 5), - ["submachine-gun"] = cutoff_amount_made(5, 0, 1), - -- Ammo - ["firearm-magazine"] = cutoff_amount_made_unless(100, 10, 0, "piercing-rounds-magazine", 100), - ["piercing-rounds-magazine"] = cutoff_amount_made(100, 0, 10), - --[[ - ['construction-robot'] = 10, - ['modular-armor'] = 1, - ['solar-panel-equipment'] = 16, - ['belt-immunity-equipment'] = 1, - ['battery-equipment'] = 2, - ['personal-roboport-equipment'] = 1 - ]] - } + -- ['item-name'] = function(amount_made, production_stats, player) return end -- 0 means no items given + -- Plates + ["iron-plate"] = scale_amount_made(100, 10, 10), + ["copper-plate"] = scale_amount_made(100, 0, 8), + ["steel-plate"] = scale_amount_made(100, 0, 4), + -- Secondary Items + ["electronic-circuit"] = scale_amount_made(1000, 0, 6), + ["iron-gear-wheel"] = scale_amount_made(1000, 0, 6), + -- Starting Items + ["burner-mining-drill"] = cutoff_time(10 * minutes, 4, 0), + ["stone-furnace"] = cutoff_time(10 * minutes, 4, 0), + -- Armor + ["light-armor"] = cutoff_amount_made_unless(5, 0, 1, "heavy-armor", 5), + ["heavy-armor"] = cutoff_amount_made(5, 0, 1), + -- Weapon + ["pistol"] = cutoff_amount_made_unless(0, 1, 1, "submachine-gun", 5), + ["submachine-gun"] = cutoff_amount_made(5, 0, 1), + -- Ammo + ["firearm-magazine"] = cutoff_amount_made_unless(100, 10, 0, "piercing-rounds-magazine", 100), + ["piercing-rounds-magazine"] = cutoff_amount_made(100, 0, 10), + --[[ + ['construction-robot'] = 10, + ['modular-armor'] = 1, + ['solar-panel-equipment'] = 16, + ['belt-immunity-equipment'] = 1, + ['battery-equipment'] = 2, + ['personal-roboport-equipment'] = 1 + ]] } diff --git a/exp_scenario/module/config/vlayer_items.lua b/exp_scenario/module/config/vlayer_items.lua new file mode 100644 index 00000000..75e9449f --- /dev/null +++ b/exp_scenario/module/config/vlayer_items.lua @@ -0,0 +1,147 @@ +--- The items the vlayer can hold and what they do, see control/vlayer.lua +-- @config Vlayer-Items + +return { +--- @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 = { -- List of all items allowed in vlayer storage and their properties + --[[ + Allowed properties: + starting_value : The amount of the item placed into the vlayer on game start, ignores area requirements + required_area : When greater than 0 the items properties are not applied unless their is sufficient surplus surface area + production : The energy production of the item in MW, used for solar panels + discharge : The energy discharge of the item in MW, used for accumulators + capacity : The energy capacity of the item in MJ, used for accumulators + surface_area : The surface area provided by the item, used for landfill + ]] + ["solar-panel"] = { + starting_value = 0, + required_area = 9, + production = 0.06, -- MW + }, + ["accumulator"] = { + starting_value = 0, + required_area = 4, + discharge = 0.3, -- MW + capacity = 5, -- MJ + }, + ["landfill"] = { + starting_value = 0, + required_area = 0, + surface_area = 20, -- Tiles + }, + ["wood"] = { + starting_value = 0, + required_area = 0, + surface_area = 0, + fuel_value = 2, -- MJ + power = true, -- turn all wood to power to reduce trash + }, + ["coal"] = { + starting_value = 0, + required_area = 0, + surface_area = 0, + fuel_value = 4, -- MJ + power = false, -- turn all coal to power to reduce trash + }, + ["solid-fuel"] = { + starting_value = 0, + required_area = 0, + surface_area = 0, + fuel_value = 12, -- MJ + power = false, -- turn all solid fuel to power to reduce trash + }, + ["rocket-fuel"] = { + starting_value = 0, + required_area = 0, + surface_area = 0, + fuel_value = 100, -- MJ + power = false, -- turn all rocket fuel to power to reduce trash + } +}, + +modded_items = { -- List of all modded items allowed in vlayer storage and their base game equivalent + ["solar-panel-2"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 4, + }, + ["solar-panel-3"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 16, + }, + ["solar-panel-4"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 64, + }, + ["solar-panel-5"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 256, + }, + ["solar-panel-6"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 1024, + }, + ["solar-panel-7"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 4096, + }, + ["solar-panel-8"] = { + starting_value = 0, + base_game_equivalent = "solar-panel", + multiplier = 16384, + }, + ["accumulator-2"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 4, + }, + ["accumulator-3"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 16, + }, + ["accumulator-4"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 64, + }, + ["accumulator-5"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 256, + }, + ["accumulator-6"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 1024, + }, + ["accumulator-7"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 4096, + }, + ["accumulator-8"] = { + starting_value = 0, + base_game_equivalent = "accumulator", + multiplier = 16384, + }, +} +} diff --git a/exp_scenario/module/control/bonus.lua b/exp_scenario/module/control/bonus.lua index 8d0a192d..262d192b 100644 --- a/exp_scenario/module/control/bonus.lua +++ b/exp_scenario/module/control/bonus.lua @@ -1,42 +1,70 @@ ---[[ Control - Bonus -Various bonus related event handlers - -TODO Refactor this fully, this is temp to get it out of the player bonus gui file -]] - -local Roles = require("modules/exp_roles") -local config = require("modules/exp_legacy/config/bonus") - ---- @param event EventData.on_force_created -local function apply_force_bonus(event) - local force = event.force - for k, v in pairs(config.force_bonus) do - force[k] = math.floor(v.max_value / 2) - end -end - ---- @param event EventData.on_surface_created -local function apply_surface_bonus(event) - local surface = assert(game.get_surface(event.surface_index)) - for k, v in pairs(config.surface_bonus) do - surface[k] = math.floor(v.max_value / 2) - end -end - ---- @param event EventData.on_player_died -local function fast_respawn(event) - local player = assert(game.get_player(event.player_index)) - if Roles.player_has_permission(player, "exp_scenario.player.instant_respawn") then - player.ticks_to_respawn = 120 - end -end - -local e = defines.events - -return { - events = { - [e.on_force_created] = apply_force_bonus, - [e.on_surface_created] = apply_surface_bonus, - [e.on_player_died] = fast_respawn, - } -} +--[[ Control - Bonus +Various bonus related event handlers + +TODO Refactor this fully, this is temp to get it out of the player bonus gui file +]] + +local Roles = require("modules/exp_roles") + +--- Force bonuses are started at half their max value +local force_bonus = { + ["worker_robots_battery_modifier"] = { + max_value = 1, + value_step = 1, + scale = 1, + cost = 1, + is_percentage = false, + }, + ["worker_robots_storage_bonus"] = { + max_value = 1, + value_step = 1, + scale = 1, + cost = 1, + is_percentage = false, + }, + ["following_robots_lifetime_modifier"] = { + max_value = 1, + value_step = 1, + scale = 1, + cost = 1, + is_percentage = false, + }, + +} + +--- Surface bonuses are started at half their max value +local surface_bonus = {} + +--- @param event EventData.on_force_created +local function apply_force_bonus(event) + local force = event.force + for k, v in pairs(force_bonus) do + force[k] = math.floor(v.max_value / 2) + end +end + +--- @param event EventData.on_surface_created +local function apply_surface_bonus(event) + local surface = assert(game.get_surface(event.surface_index)) + for k, v in pairs(surface_bonus) do + surface[k] = math.floor(v.max_value / 2) + end +end + +--- @param event EventData.on_player_died +local function fast_respawn(event) + local player = assert(game.get_player(event.player_index)) + if Roles.player_has_permission(player, "exp_scenario.player.instant_respawn") then + player.ticks_to_respawn = 120 + end +end + +local e = defines.events + +return { + events = { + [e.on_force_created] = apply_force_bonus, + [e.on_surface_created] = apply_surface_bonus, + [e.on_player_died] = fast_respawn, + } +} diff --git a/exp_scenario/module/control/custom_start.lua b/exp_scenario/module/control/custom_start.lua index 62d59a1a..5537fd22 100644 --- a/exp_scenario/module/control/custom_start.lua +++ b/exp_scenario/module/control/custom_start.lua @@ -2,9 +2,20 @@ Changes the starting script and the items given on first join depending on factory production levels ]] -local config = require("modules.exp_legacy.config.advanced_start") +local Feature = require("modules/exp_scenario/features") +local starting_items = require("modules/exp_scenario/config/starting_items") local floor = math.floor +--- Applied when the map is created +local feature, config = Feature.register("custom_start", { + skip_intro = true, -- skips the intro given in the default factorio free play scenario + skip_victory = true, -- will skip the victory screen when a rocket is launched + friendly_fire = false, -- weather players will be able to attack each other on the same force + disable_crashsite = true, -- weather to disable creation of the crashsite + enemy_expansion = false, -- a catch all for in case the map settings file fails to load + chart_radius = 320, -- the number of tiles that will be charted when the map starts +}) + --- Give a player their starting items --- @param player LuaPlayer local function give_starting_items(player) @@ -12,7 +23,7 @@ local function give_starting_items(player) local get_input_count = get_prod_stats.get_input_count local insert_param = { name = "", count = 0 } local insert = player.insert - for item_name, insert_amount in pairs(config.items) do + for item_name, insert_amount in pairs(starting_items) do insert_param.name = item_name if type(insert_amount) == "function" then local count = insert_amount(get_input_count(item_name), get_input_count, player) @@ -55,7 +66,7 @@ end local e = defines.events -return { +return feature:guard{ on_init = on_init, events = { [e.on_player_created] = on_player_created, diff --git a/exp_scenario/module/control/deconstruction_log.lua b/exp_scenario/module/control/deconstruction_log.lua index 325bb3f8..62990ce6 100644 --- a/exp_scenario/module/control/deconstruction_log.lua +++ b/exp_scenario/module/control/deconstruction_log.lua @@ -5,7 +5,16 @@ Log certain actions into a file when events are triggered local ExpUtil = require("modules/exp_util") local Storage = require("modules/exp_util/storage") local Roles = require("modules/exp_roles") -local config = require("modules.exp_legacy.config.deconlog") +local Feature = require("modules/exp_scenario/features") + +local feature, config = Feature.register("deconstruction_log", { + decon_area = true, -- log when an area is deconstructed + built_entity = true, -- log when an entity is built + mined_entity = true, -- log when an entity is mined + fired_rocket = true, -- log when a rocket is fired + fired_explosive_rocket = true, -- log when an explosive rocket is fired + fired_nuke = true, -- log when a nuke is fired +}) local seconds_time_format = ExpUtil.format_time_factory{ format = "short", hours = true, minutes = true, seconds = true } local format_number = require("util").format_number @@ -90,6 +99,7 @@ end --- Log when an area is deconstructed --- @param event EventData.on_player_deconstructed_area local function on_player_deconstructed_area(event) + if not config.decon_area then return end local player = get_log_player(event) if not player then return end @@ -118,6 +128,7 @@ end --- Log when an entity is built --- @param event EventData.on_built_entity local function on_built_entity(event) + if not config.built_entity then return end local player = get_log_player(event) if not player then return end add_log_line(player, "built_entity", format_entity(event.entity)) @@ -126,16 +137,17 @@ end --- Log when an entity is mined --- @param event EventData.on_player_mined_entity local function on_player_mined_entity(event) + if not config.mined_entity then return end local player = get_log_player(event) if not player then return end add_log_line(player, "mined_entity", format_entity(event.entity)) end ---- Ammo which is logged when fired -local logged_ammo = { - ["rocket"] = config.fired_rocket, - ["explosive-rocket"] = config.fired_explosive_rocket, - ["atomic-bomb"] = config.fired_nuke, +--- The setting which decides if a shot of an ammo is logged +local ammo_settings = { + ["rocket"] = "fired_rocket", + ["explosive-rocket"] = "fired_explosive_rocket", + ["atomic-bomb"] = "fired_nuke", } --- @class ExpScenario_DeconstructionLog.AmmoSlot @@ -186,7 +198,7 @@ local function on_player_ammo_inventory_changed(event) fired = previous.name end - if fired and logged_ammo[fired] then + if fired and config[ammo_settings[fired]] then add_log_line(player, "shot-" .. fired, format_position(player.physical_position), format_position(player.shooting_state.position)) end end @@ -206,29 +218,16 @@ local function on_player_left_game(event) end local e = defines.events -local events = { - [e.on_multiplayer_init] = clear_log, -} -if config.decon_area then - events[e.on_player_deconstructed_area] = on_player_deconstructed_area -end - -if config.built_entity then - events[e.on_built_entity] = on_built_entity -end - -if config.mined_entity then - events[e.on_player_mined_entity] = on_player_mined_entity -end - -if config.fired_rocket or config.fired_explosive_rocket or config.fired_nuke then - events[e.on_player_ammo_inventory_changed] = on_player_ammo_inventory_changed - events[e.on_player_joined_game] = on_player_character_changed - events[e.on_player_respawned] = on_player_character_changed - events[e.on_player_left_game] = on_player_left_game -end - -return { - events = events, +return feature:guard{ + events = { + [e.on_multiplayer_init] = clear_log, + [e.on_player_deconstructed_area] = on_player_deconstructed_area, + [e.on_built_entity] = on_built_entity, + [e.on_player_mined_entity] = on_player_mined_entity, + [e.on_player_ammo_inventory_changed] = on_player_ammo_inventory_changed, + [e.on_player_joined_game] = on_player_character_changed, + [e.on_player_respawned] = on_player_character_changed, + [e.on_player_left_game] = on_player_left_game, + }, } diff --git a/exp_scenario/module/control/degrading_tiles.lua b/exp_scenario/module/control/degrading_tiles.lua index dc3a1b11..ab9c4e47 100644 --- a/exp_scenario/module/control/degrading_tiles.lua +++ b/exp_scenario/module/control/degrading_tiles.lua @@ -2,13 +2,108 @@ When a player walks around the tiles under them will degrade over time, the same is true when entites are built ]] -local config = require("modules.exp_legacy.config.scorched_earth") +local Feature = require("modules/exp_scenario/features") + +local feature, config = Feature.register("degrading_tiles", { + weakness_value = 70, -- lower value will make tiles more likely to degrade + entities = Feature.set{ -- entities which degrade the tiles under them when they are placed + "stone-furnace", "steel-furnace", "electric-furnace", "assembling-machine-1", "assembling-machine-2", "assembling-machine-3", + "beacon", "centrifuge", "chemical-plant", "oil-refinery", "storage-tank", "nuclear-reactor", + "steam-engine", "steam-turbine", "boiler", "heat-exchanger", "stone-wall", "gate", + "gun-turret", "laser-turret", "flamethrower-turret", "radar", "lab", "big-electric-pole", + "substation", "rocket-silo", "pumpjack", "electric-mining-drill", "roboport", "accumulator", + }, +}) local random = math.random +--- How "strong" a tile is, bigger number means less likely to degrade +local strengths = { + -- debug: /interface require('modules.addons.worn-paths')(player.name,true) + -- note: tiles are effected by the tiles around them, so player paths will not degrade as fast when made wider + -- note: values are relative to the tile with the highest value, recommended to keep highest tile as a "nice" number + -- note: tiles not in list will never degrade under any conditions (which is why some are omitted such as water) + ["refined-concrete"] = 100, + ["refined-hazard-concrete-left"] = 100, + ["refined-hazard-concrete-right"] = 100, + ["concrete"] = 90, + ["hazard-concrete-left"] = 90, + ["hazard-concrete-right"] = 90, + ["stone-path"] = 80, + ["red-desert-0"] = 80, + ["dry-dirt"] = 50, + -- grass four (main grass tiles) + ["grass-1"] = 50, + ["grass-2"] = 40, + ["grass-3"] = 30, + ["grass-4"] = 25, + -- red three (main red tiles) + ["red-desert-1"] = 40, + ["red-desert-2"] = 30, + ["red-desert-3"] = 25, + -- sand three (main sand tiles) + ["sand-1"] = 40, + ["sand-2"] = 30, + ["sand-3"] = 25, + -- dirt 3 (main dirt tiles) + ["dirt-1"] = 40, + ["dirt-2"] = 30, + ["dirt-3"] = 25, + -- last three/four (all sets of three merge here) + ["dirt-4"] = 25, + ["dirt-5"] = 30, + ["dirt-6"] = 40, + -- ["dirt-7"]=0, -- last tile, nothing to degrade to + -- land fill chain + -- ["landfill"]=50, + -- ["water-shallow"]=90, + -- ["water-mud"]=0, -- last tile, nothing to degrade to + +} + +--- When a tile degrades it will turn into the next tile given here +local degrade_order = { + ["refined-concrete"] = "concrete", + ["refined-hazard-concrete-left"] = "hazard-concrete-left", + ["refined-hazard-concrete-right"] = "hazard-concrete-right", + ["concrete"] = "stone-path", + ["hazard-concrete-left"] = "stone-path", + ["hazard-concrete-right"] = "stone-path", + ["stone-path"] = "dry-dirt", + ["red-desert-0"] = "dry-dirt", + ["dry-dirt"] = "dirt-4", + -- grass four (main grass tiles) + ["grass-1"] = "grass-2", + ["grass-2"] = "grass-3", + ["grass-3"] = "grass-4", + ["grass-4"] = "dirt-4", + -- red three (main red tiles) + ["red-desert-1"] = "red-desert-2", + ["red-desert-2"] = "red-desert-3", + ["red-desert-3"] = "dirt-4", + -- sand three (main sand tiles) + ["sand-1"] = "sand-2", + ["sand-2"] = "sand-3", + ["sand-3"] = "dirt-4", + -- dirt 3 (main dirt tiles) + ["dirt-1"] = "dirt-2", + ["dirt-2"] = "dirt-3", + ["dirt-3"] = "dirt-4", + -- last three/four (all sets of three merge here) + ["dirt-4"] = "dirt-5", + ["dirt-5"] = "dirt-6", + ["dirt-6"] = "dirt-7", + -- ["dirt-7"]=0, -- last tile, nothing to degrade to + -- land fill chain + -- ["landfill"]='grass-2', -- 'water-shallow' + -- ["water-shallow"]='water-mud', + -- ["water-mud"]=0, -- last tile, nothing to degrade to + +} + --- Get the max tile strength local max_strength = 0 -for _, strength in pairs(config.strengths) do +for _, strength in pairs(strengths) do if strength > max_strength then max_strength = strength end @@ -20,7 +115,7 @@ end local function degrade_tile(surface, position) local tile = surface.get_tile(position.x, position.y) local tile_name = tile.name - local degrade_tile_name = config.degrade_order[tile_name] + local degrade_tile_name = degrade_order[tile_name] if not degrade_tile_name then return end surface.set_tiles{ { name = degrade_tile_name, position = position } } end @@ -39,7 +134,7 @@ local function degrade_entity(entity) for y = left_top.y, right_bottom.y do local tile = surface.get_tile(x, y) local tile_name = tile.name - local degrade_tile_name = config.degrade_order[tile_name] + local degrade_tile_name = degrade_order[tile_name] if degrade_tile_name then tiles[#tiles + 1] = { name = degrade_tile_name, position = { x, y } } end @@ -63,14 +158,14 @@ end local function get_tile_strength(surface, position) local tile = surface.get_tile(position.x, position.y) local tile_name = tile.name - local strength = config.strengths[tile_name] + local strength = strengths[tile_name] if not strength then return end for x = position.x - 1, position.x + 1 do for y = position.y - 1, position.y + 1 do local check_tile = surface.get_tile(x, y) local check_tile_name = check_tile.name - local check_strength = config.strengths[check_tile_name] or 0 + local check_strength = strengths[check_tile_name] or 0 strength = strength + check_strength end end @@ -108,7 +203,7 @@ end local e = defines.events -return { +return feature:guard{ events = { [e.on_player_changed_position] = on_player_changed_position, [e.on_robot_built_entity] = on_built_entity, diff --git a/exp_scenario/module/control/extra_logging.lua b/exp_scenario/module/control/extra_logging.lua index 07055245..b1a91bc2 100644 --- a/exp_scenario/module/control/extra_logging.lua +++ b/exp_scenario/module/control/extra_logging.lua @@ -2,8 +2,39 @@ Log some extra events to a separate file ]] -local config = require("modules.exp_legacy.config.logging") -local config_res = require("modules.exp_legacy.config.research") +local Feature = require("modules/exp_scenario/features") +local research_data = require("modules/exp_scenario/config/research_data") + +local feature, config = Feature.register("extra_logging", { + file_name = "log/logging.log", -- the file the lines are written to + rocket_launch_display_rate = 500, -- after the counts below, log every this many rockets +}) + +--- Rocket counts which are always logged +local rocket_launch_display = { + [1] = true, + [2] = true, + [5] = true, + [10] = true, + [20] = true, + [50] = true, + [100] = true, + [200] = true, +} + +local disconnect_reason = { + [defines.disconnect_reason.quit] = "left the game", + [defines.disconnect_reason.dropped] = "was dropped from the game", + [defines.disconnect_reason.reconnect] = "is reconnecting", + [defines.disconnect_reason.wrong_input] = "was having a wrong input", + [defines.disconnect_reason.desync_limit_reached] = "had desync limit reached", + [defines.disconnect_reason.cannot_keep_up] = "cannot keep up", + [defines.disconnect_reason.afk] = "was afk", + [defines.disconnect_reason.kicked] = "was kicked", + [defines.disconnect_reason.kicked_and_deleted] = "was kicked and deleted", + [defines.disconnect_reason.banned] = "was banned", + [defines.disconnect_reason.switching_servers] = "is switching servers", +} local concat = table.concat local write_file = helpers.write_file @@ -26,7 +57,7 @@ local function on_cargo_pod_finished_ascending(event) 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 + elseif rocket_launch_display[force.rockets_launched] then add_log_line("[ROCKET]", force.rockets_launched, "rockets launched") end end @@ -53,7 +84,7 @@ local function on_research_finished(event) return end - local inf_research_level = config_res.inf_res[config_res.mod_set][event.research.name] + local inf_research_level = research_data.inf_res[research_data.mod_set][event.research.name] if inf_research_level and event.research.level >= inf_research_level then add_log_line_locale{ "", "[RES]", event.research.prototype.localised_name, " at level ", event.research.level - 1, " has been researched\n" } else @@ -70,17 +101,17 @@ end --- @param event EventData.on_player_left_game local function on_player_left_game(event) local player = assert(game.get_player(event.player_index)) - add_log_line("[LEAVE]", player.name, config.disconnect_reason[event.reason]) + add_log_line("[LEAVE]", player.name, disconnect_reason[event.reason]) end local e = defines.events -return { +return feature:guard{ events = { [e.on_cargo_pod_finished_ascending] = on_cargo_pod_finished_ascending, [e.on_pre_player_died] = on_pre_player_died, [e.on_research_finished] = on_research_finished, [e.on_player_joined_game] = on_player_joined_game, [e.on_player_left_game] = on_player_left_game, - } + }, } diff --git a/exp_scenario/module/control/help_bubbles.lua b/exp_scenario/module/control/help_bubbles.lua index b37c143f..51f8a12f 100644 --- a/exp_scenario/module/control/help_bubbles.lua +++ b/exp_scenario/module/control/help_bubbles.lua @@ -4,7 +4,34 @@ Adds friendly biters that walk around and give helpful messages local Async = require("modules/exp_util/async") local Storage = require("modules/exp_util/storage") -local config = require("modules.exp_legacy.config.compilatron") +local Feature = require("modules/exp_scenario/features") +local config_server_detail = require("modules.exp_legacy.config.server_detail") + +local feature = Feature.register("help_bubbles") + +--- Ticks between message changes +local message_cycle = 60 * 15 + +local locations = { + ["Spawn"] = { + spawn_position = { x = 0, y = 0 }, + spawn_surface = "nauvis", + entity_name = "small-biter", + messages = { + { "info.website", config_server_detail["website"] }, + { "info.read-readme" }, + { "info.discord", config_server_detail["discord"] }, + { "info.softmod" }, + { "info.redmew" }, + { "info.custom-commands" }, + { "info.status", config_server_detail["status"] }, + { "info.lhd" }, + { "info.github", config_server_detail["github"] }, + { "info.patreon", config_server_detail["patreon"] }, + }, + } + +} --- @type table> local persistent_locations = {} @@ -48,7 +75,7 @@ local speech_bubble_task = task.previous_message = entity.surface.create_entity(speech_bubble_param) task.current_message_index = index + 1 - return Async.status.delay(config.message_cycle, task) + return Async.status.delay(message_cycle, task) end) --- Register an entity to start spawning speech bubbles @@ -66,7 +93,7 @@ end --- Check all persistent locations from the config are active local function check_persistent_locations() - for name, location in pairs(config.locations) do + for name, location in pairs(locations) do local task = persistent_locations[name] if task and not task.completed then goto continue @@ -92,9 +119,9 @@ local function check_persistent_locations() end end -return { +return feature:guard{ on_nth_tick = { - [config.message_cycle] = check_persistent_locations, + [message_cycle] = check_persistent_locations, }, register_entity = register_entity, } diff --git a/exp_scenario/module/control/inventory_clear.lua b/exp_scenario/module/control/inventory_clear.lua index cdbfd650..6a04a105 100644 --- a/exp_scenario/module/control/inventory_clear.lua +++ b/exp_scenario/module/control/inventory_clear.lua @@ -6,7 +6,15 @@ on_player_kicked - player_index ]] local ExpUtil = require("modules/exp_util") -local events = require("modules.exp_legacy.config.inventory_clear") +local Feature = require("modules/exp_scenario/features") + +local feature = Feature.register("inventory_clear") + +--- The events which clear the inventory of the player +local events = { + defines.events.on_player_banned, + defines.events.on_player_kicked, +} --- @param event { player_index: number } local function clear_items(event) @@ -26,6 +34,6 @@ for _, event_name in ipairs(events) do event_handlers[event_name] = clear_items end -return { +return feature:guard{ events = event_handlers, } diff --git a/exp_scenario/module/control/pollution_grading.lua b/exp_scenario/module/control/pollution_grading.lua index 8bd8f876..da480c0d 100644 --- a/exp_scenario/module/control/pollution_grading.lua +++ b/exp_scenario/module/control/pollution_grading.lua @@ -2,7 +2,16 @@ Makes pollution look much nice of the map, ie not one big red mess ]] -local config = require("modules.exp_legacy.config.pollution_grading") +local Feature = require("modules/exp_scenario/features") + +local feature, config = Feature.register("pollution_grading", { + reference_point = { -- where pollution is read from + x = 0, + y = 0, + }, + max_scalar = 0.5, -- the scale between true max and max + min_scalar = 0.17, -- the scale between the lowest max and min +}) local function check_surfaces() local max_reference = 0 @@ -20,8 +29,8 @@ local function check_surfaces() settings.min_to_show_per_chunk = min end -return { +return feature:guard{ on_nth_tick = { - [config.update_delay * 3600] = check_surfaces, - } + [15 * 3600] = check_surfaces, + }, } diff --git a/exp_scenario/module/control/research.lua b/exp_scenario/module/control/research.lua index 4d885740..3852b553 100644 --- a/exp_scenario/module/control/research.lua +++ b/exp_scenario/module/control/research.lua @@ -1,48 +1,62 @@ ---[[ Control - Research -Various research related event handlers - -TODO Refactor this fully, this is temp to get it out of the research times gui file -]] - -local config = require("modules/exp_legacy/config/research") - ---- @param event EventData.on_research_finished -local function on_research_finished(event) - local research_name = event.research.name - if config.bonus_inventory.enabled and config.bonus_inventory.res[research_name] then - event.research.force[config.bonus_inventory.name] = math.min((event.research.level - 1) * config.bonus_inventory.rate, config.bonus_inventory.limit) - end - - if config.pollution_ageing_by_research and config.bonus_inventory.res[research_name] then - game.map_settings.pollution.ageing = math.min(10, event.research.level / 5) - end -end - ---- @param event EventData.on_research_started -local function on_research_started(event) - if config.limit_res[event.research.name] and event.research.level > config.limit_res[event.research.name] then - event.research.enabled = false - event.research.visible_when_disabled = true - local rq = event.research.force.research_queue - - for i = #rq, 1, -1 do - if rq[i] == event.research.name then - table.remove(rq, i) - end - end - - event.research.force.cancel_current_research() - event.research.force.research_queue = rq - end -end - -local e = defines.events - -return { - events = { - [e.on_research_finished] = on_research_finished, - [e.on_research_reversed] = on_research_finished, - [e.on_research_started] = on_research_started, - [e.on_research_queued] = on_research_started, - } -} +--[[ Control - Research +Various research related event handlers + +TODO Refactor this fully, this is temp to get it out of the research times gui file +]] + +local Feature = require("modules/exp_scenario/features") +local research_data = require("modules/exp_scenario/config/research_data") + +local feature, config = Feature.register("research", { + pollution_ageing_by_research = false, -- pollution ages faster with each level of the bonus inventory researches + bonus_inventory = { -- extra inventory slots for each level of these researches + enabled = true, + res = Feature.set{ "mining-productivity", "mining-productivity-2", "mining-productivity-3", "mining-productivity-4" }, + rate = 5, + limit = 20, + }, +}) + +local bonus_inventory_name = "character_inventory_slots_bonus" + +--- @param event EventData.on_research_finished +local function on_research_finished(event) + local research_name = event.research.name + if config.bonus_inventory.enabled and config.bonus_inventory.res[research_name] then + event.research.force[bonus_inventory_name] = math.min((event.research.level - 1) * config.bonus_inventory.rate, config.bonus_inventory.limit) + end + + if config.pollution_ageing_by_research and config.bonus_inventory.res[research_name] then + game.map_settings.pollution.ageing = math.min(10, event.research.level / 5) + end +end + +--- @param event EventData.on_research_started +local function on_research_started(event) + local limit = research_data.limit_res[event.research.name] + if limit and event.research.level > limit then + event.research.enabled = false + event.research.visible_when_disabled = true + local rq = event.research.force.research_queue + + for i = #rq, 1, -1 do + if rq[i] == event.research.name then + table.remove(rq, i) + end + end + + event.research.force.cancel_current_research() + event.research.force.research_queue = rq + end +end + +local e = defines.events + +return feature:guard{ + events = { + [e.on_research_finished] = on_research_finished, + [e.on_research_reversed] = on_research_finished, + [e.on_research_started] = on_research_started, + [e.on_research_queued] = on_research_started, + } +} diff --git a/exp_scenario/module/control/vlayer.lua b/exp_scenario/module/control/vlayer.lua index 7d063557..c59d5a03 100644 --- a/exp_scenario/module/control/vlayer.lua +++ b/exp_scenario/module/control/vlayer.lua @@ -4,7 +4,43 @@ Stores solar panels, accumulators and landfill in a virtual layer which produces local ExpUtil = require("modules/exp_util") local Storage = require("modules/exp_util/storage") -local config = require("modules.exp_legacy.config.vlayer") +local Feature = require("modules/exp_scenario/features") +local items = require("modules/exp_scenario/config/vlayer_items") + +local feature, config = Feature.register("vlayer", { + unlimited_capacity = false, -- an unlimited energy capacity, accumulators are not required + unlimited_surface_area = false, -- an unlimited surface area, landfill is not required + modded_auto_downgrade = false, -- modded items are converted into their base game equivalent, the originals can not be recovered + power_on_space = false, -- energy interfaces can be built on platforms + power_on_space_research = { -- the research level needed to build them there + name = "research-productivity", + level = 10, + }, + mimic_surface = "nauvis", -- the surface the day cycle is copied from, empty to use the fixed cycle below + interface_limit = { -- how many of each interface can exist + energy = 1, -- >1 allows for disconnected power networks to receive power + circuit = 20, + storage_input = 20, + storage_output = 1, -- >0 allows for item teleportation of the allowed items + }, +}) + +local update_tick_storage = 60 +local update_tick_energy = 10 + +--- The day cycle used when there is no mimic surface, see LuaSurface for details +local surface_settings = { + always_day = false, + solar_power_multiplier = 1, + min_brightness = 0.15, + ticks_per_day = 25200, + daytime = 0, + dusk = 0.25, + evening = 0.45, + morning = 0.55, + dawn = 0.75, + +} local floor = math.floor local min = math.min @@ -58,6 +94,9 @@ local mega = 1000000 --- @class ExpScenario_Vlayer local Vlayer = { + --- The config of the feature, read only + config = config, + feature = feature, --- @package events = {}, --- @package @@ -66,11 +105,11 @@ local Vlayer = { --- The properties of every allowed item, modded items are derived from their base game equivalent local allowed_items = {} --- @type table -for name, properties in pairs(config.allowed_items) do +for name, properties in pairs(items.allowed_items) do allowed_items[name] = properties end -for name, properties in pairs(config.modded_items) do - local base = config.allowed_items[properties.base_game_equivalent] +for name, properties in pairs(items.modded_items) do + local base = items.allowed_items[properties.base_game_equivalent] local multiplier = properties.multiplier allowed_items[name] = { starting_value = properties.starting_value or 0, @@ -103,7 +142,7 @@ local vlayer_data = { power_items = {}, energy = 0, }, - surface = table.deep_copy(config.surface), + surface = table.deep_copy(surface_settings), } Storage.register(vlayer_data, function(tbl) @@ -490,7 +529,7 @@ local function handle_input_interfaces() end if removed > 0 then - local modded = config.modded_items[name] + local modded = items.modded_items[name] if modded and config.modded_auto_downgrade then Vlayer.insert_item(modded.base_game_equivalent, stored * modded.multiplier) elseif vlayer_data.storage.power_items[name] then @@ -595,7 +634,7 @@ end local function handle_energy_interfaces() local storage = vlayer_data.storage local properties = vlayer_data.properties - local production = properties.production * mega * (config.update_tick_energy / 60) + local production = properties.production * mega * (update_tick_energy / 60) storage.energy = storage.energy + floor(production * get_production_multiplier()) local interfaces = vlayer_data.interfaces.energy @@ -634,7 +673,7 @@ end --- Take the day cycle from the mimic surface when it exists, otherwise from the config local function update_surface() - if config.mimic_surface then + if config.mimic_surface ~= "" then local surface = game.get_surface(config.mimic_surface) if surface then vlayer_data.surface = surface @@ -644,7 +683,7 @@ local function update_surface() -- The config copy never has an index, so this avoids replacing it every time a surface changes if not vlayer_data.surface.index then - vlayer_data.surface = table.deep_copy(config.surface) + vlayer_data.surface = table.deep_copy(surface_settings) end end @@ -667,7 +706,12 @@ Vlayer.on_init = update_surface -- The default surface exists before on_surface_ Vlayer.events[e.on_surface_created] = update_surface Vlayer.events[e.on_surface_renamed] = update_surface Vlayer.events[e.on_surface_imported] = update_surface -Vlayer.on_nth_tick[config.update_tick_storage] = update_storage -Vlayer.on_nth_tick[config.update_tick_energy] = update_energy +Vlayer.events[Feature.events.on_config_updated] = function(event) + if event.feature_name == feature.name and event.path == "mimic_surface" then + update_surface() + end +end +Vlayer.on_nth_tick[update_tick_storage] = update_storage +Vlayer.on_nth_tick[update_tick_energy] = update_energy -return Vlayer +return feature:guard(Vlayer) diff --git a/exp_scenario/module/gui/autofill.lua b/exp_scenario/module/gui/autofill.lua index 9a6b7093..f5fec96e 100644 --- a/exp_scenario/module/gui/autofill.lua +++ b/exp_scenario/module/gui/autofill.lua @@ -5,7 +5,7 @@ Adds a config menu for setting autofill of placed entities local Feature = require("modules/exp_scenario/features") local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") -local config = require("modules.exp_legacy.config.gui.autofill") +local config = require("modules/exp_scenario/config/autofill") local feature = Feature.register("autofill") local FlyingText = require("modules/exp_util/flying_text") diff --git a/exp_scenario/module/gui/module_inserter.lua b/exp_scenario/module/gui/module_inserter.lua index f930bd34..dd6650c4 100644 --- a/exp_scenario/module/gui/module_inserter.lua +++ b/exp_scenario/module/gui/module_inserter.lua @@ -9,17 +9,29 @@ local Roles = require("modules/exp_roles") local Selection = require("modules/exp_util/selection") local SelectArea = Selection.connect("ModuleArea") -local config = require("modules/exp_legacy/config/module") +local Feature = require("modules/exp_scenario/features") +local machine_sets = require("modules/exp_scenario/config/module_inserter_machines") + +local feature, config = Feature.register("module_inserter", { + copy_paste_module = true, -- copying a machine also copies its modules + copy_paste_rotation = false, -- copying a machine also copies its rotation +}) + +local module_slots_per_row = 4 +local module_slot_max = 8 + +--- The machines of the active mod sets +local machines = {} --- @class ExpGui_ModuleInserter.elements local Elements = {} --- Load all the valid machines from the config file local machine_names = {} -for mod_name, machine_set in pairs(config.machine_sets) do +for mod_name, machine_set in pairs(machine_sets) do if script.active_mods[mod_name] then for machine_name, v in pairs(machine_set) do - config.machines[machine_name] = v + machines[machine_name] = v table.insert(machine_names, machine_name) end end @@ -137,7 +149,7 @@ Elements.module_selector = Gui.define("module_inserter/module_selector") Elements.module_table = Gui.define("module_inserter/module_table") :draw(function(def, parent) --- @cast def ExpGui_ModuleInserter.elements.module_table - local slots_per_row = config.module_slots_per_row + 1 + local slots_per_row = module_slots_per_row + 1 return Gui.elements.scroll_table(parent, 280, slots_per_row) end) :element_data{} --[[@as any]] @@ -163,12 +175,12 @@ function Elements.module_table.add_row(module_table) } -- Add the module selectors and row separators - local slots_per_row = config.module_slots_per_row + 1 - for i = 1, config.module_slot_max do + local slots_per_row = module_slots_per_row + 1 + for i = 1, module_slot_max do if i % slots_per_row == 0 then row_separators[#row_separators + 1] = module_table.add{ type = "flow", visible = false } end - module_selectors[i] = Elements.module_selector(module_table, i <= config.module_slots_per_row) + module_selectors[i] = Elements.module_selector(module_table, i <= module_slots_per_row) end end @@ -199,7 +211,7 @@ function Elements.module_table.reset_row(module_table, machine_selector) separator.visible = false end for i, selector in pairs(row.module_selectors) do - selector.visible = i <= config.module_slots_per_row + selector.visible = i <= module_slots_per_row selector.enabled = false selector.elem_value = nil end @@ -214,16 +226,16 @@ function Elements.module_table.refresh_row(module_table, machine_selector, machi local row = assert(rows[machine_selector.index]) 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 } + local visible_row_count = math.ceil(active_module_count / module_slots_per_row) + local visible_module_count = visible_row_count * module_slots_per_row + local module_elem_value = { name = machines[machine_name].module } for i, separator in pairs(row.row_separators) do separator.visible = i < visible_row_count end for i, selector in pairs(row.module_selectors) do if i <= active_module_count then - if config.machines[machine_name].prod then + if machines[machine_name].prod then selector.elem_filters = elem_filter.with_prod else selector.elem_filters = elem_filter.no_prod @@ -259,7 +271,7 @@ Gui.toolbar.create_button{ sprite = "item/productivity-module-3", tooltip = { "exp-gui_module-inserter.tooltip-main" }, visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.module") + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.module") end } @@ -499,7 +511,7 @@ end local e = defines.events -return { +return feature:guard{ elements = Elements, events = { [e.on_entity_settings_pasted] = on_entity_settings_pasted, diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index 1fadeccc..ac940a3c 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -4,7 +4,18 @@ Adds a gui that allows players to apply various bonuses local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") -local config = require("modules/exp_legacy/config/bonus") +local Feature = require("modules/exp_scenario/features") + +local feature, config = Feature.register("player_bonus", { + points = { -- how many points a player can spend on bonuses + base = 174, -- the points of the points role, the standard value of each bonus adds up to this + increase_percentage_per_role_level = 0.03, -- extra points as a fraction of the base for each role above the points role + role_id = Feature.optional("number"), -- the role the base points apply to, without one every player gets the base + }, +}) + +--- Ticks between applications of the periodic bonuses +local periodic_bonus_rate = 300 local Vlayer = require("modules/exp_scenario/control/vlayer") local format_number = require("util").format_number @@ -19,11 +30,12 @@ local Elements = {} --- @field is_percentage boolean --- @field is_special boolean --- @field value_step number +--- @field combined_bonus string[]? Bonuses applied together with this one --- @field _cost_scale number --- For perf calculate the division of scale against cost ahead of time ---- @type table -local player_bonus = config.player_bonus +--- @type ExpGui_PlayerBonus.bonus_data[] +local player_bonus = require("modules/exp_scenario/config/player_bonus") for _, bonus_data in pairs(player_bonus) do bonus_data._cost_scale = bonus_data.cost / bonus_data.scale @@ -71,8 +83,8 @@ do local _points_limit = {} --- @type table local roles = Roles.get_roles() local positions = {} for index, role in ipairs(roles) do positions[role.id] = index end - local role = assert(Roles.get_role_by_name(config.points.role_name), "Bonus points role does not exist") - local role_diff = positions[role.id] - positions[Roles.get_player_highest_role(player).id] + local role = config.points.role_id and Roles.get_role(config.points.role_id) + local role_diff = role and positions[role.id] - positions[Roles.get_player_highest_role(player).id] or 0 local points_limit = math.floor(config.points.base * (1 + config.points.increase_percentage_per_role_level * role_diff)) _points_limit[player.index] = points_limit return points_limit @@ -375,7 +387,7 @@ Elements.container = Gui.define("player_bonus/container") Elements.reset_button.link_apply_button(elements.reset_button, elements.apply_button) Elements.apply_button.link_reset_button(elements.apply_button, elements.reset_button) - for _, bonus_data in pairs(config.player_bonus) do + for _, bonus_data in pairs(player_bonus) do --- @cast bonus_data ExpGui_PlayerBonus.bonus_data Elements.bonus_table.add_row(bonus_table, bonus_data, elements) end @@ -409,7 +421,7 @@ function Elements.container.clear_player_bonus(player) if not player.character then return end - for _, bonus_data in pairs(config.player_bonus) do + for _, bonus_data in pairs(player_bonus) do if not bonus_data.is_special then player[bonus_data.name] = 0 if bonus_data.combined_bonus then @@ -429,7 +441,7 @@ function Elements.container.apply_player_bonus(player) end local player_data = Elements.container.data[player] - for _, bonus_data in pairs(config.player_bonus) do + for _, bonus_data in pairs(player_bonus) do if not bonus_data.is_special then local value = player_data[bonus_data.name] or 0 player[bonus_data.name] = value @@ -462,27 +474,45 @@ Gui.toolbar.create_button{ sprite = "item/exoskeleton-equipment", tooltip = { "exp-gui_player-bonus.tooltip-main" }, visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.bonus") + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.bonus") end } --- Recalculate and apply the bonus for a player +--- Recalculate the limit of a player, their bonus is removed when it is exceeded +--- @param player LuaPlayer +--- @return boolean # False when the bonus was removed +local function refresh_points_limit(player) + Elements.bonus_used._clear_points_limit_cache(player) + local bonus_cost = Elements.container.calculate_cost(player) + local within_limit = Elements.bonus_used.refresh_player(player, bonus_cost) + if not within_limit or not Roles.player_has_permission(player, "exp_scenario.gui.bonus") then + Elements.container.clear_player_bonus(player) + return false + end + return true +end + local function recalculate_bonus(event) local player = Gui.get_player(event) - if event.name == Roles.events.on_player_roles_changed then - -- If the player's roles changed then we will need to recalculate their limit - Elements.bonus_used._clear_points_limit_cache(player) - local bonus_cost = Elements.container.calculate_cost(player) - local within_limit = Elements.bonus_used.refresh_player(player, bonus_cost) - if not within_limit or not Roles.player_has_permission(player, "exp_scenario.gui.bonus") then - Elements.container.clear_player_bonus(player) - return - end + if event.name == Roles.events.on_player_roles_changed and not refresh_points_limit(player) then + return end Elements.container.apply_player_bonus(player) end +--- The points of every player change with the points settings +--- @param event EventData.ExpScenario.on_config_updated +local function on_config_updated(event) + if event.feature_name ~= feature.name then return end + for _, player in pairs(game.connected_players) do + if refresh_points_limit(player) then + Elements.container.apply_player_bonus(player) + end + end +end + --- Apply periodic bonus to a player --- @param player LuaPlayer local function apply_personal_battery_recharge(player) @@ -501,7 +531,7 @@ local function apply_personal_battery_recharge(player) return -- No grid or already full end - local recharge_amount = Elements.container.get_player_bonus(player, "personal_battery_recharge") * 100000 * config.periodic_bonus_rate / 6 + local recharge_amount = Elements.container.get_player_bonus(player, "personal_battery_recharge") * 100000 * periodic_bonus_rate / 6 for _, equipment in pairs(grid.equipment) do if equipment.energy < equipment.max_energy then @@ -524,13 +554,14 @@ end local e = defines.events -return { +return feature:guard{ elements = Elements, events = { [e.on_player_respawned] = recalculate_bonus, [Roles.events.on_player_roles_changed] = recalculate_bonus, + [Feature.events.on_config_updated] = on_config_updated, }, on_nth_tick = { - [config.periodic_bonus_rate] = apply_periodic_bonus_online, + [periodic_bonus_rate] = apply_periodic_bonus_online, } } diff --git a/exp_scenario/module/gui/player_list.lua b/exp_scenario/module/gui/player_list.lua index e6fc9058..009f7bb4 100644 --- a/exp_scenario/module/gui/player_list.lua +++ b/exp_scenario/module/gui/player_list.lua @@ -5,7 +5,7 @@ Adds a player list to show names and play time; also includes action buttons whi local ExpUtil = require("modules/exp_util") local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") -local config = require("modules/exp_legacy/config/gui/player_list_actions") +local actions = require("modules/exp_scenario/gui/player_list_actions") --- @class ExpGui_PlayerList.elements local Elements = {} @@ -100,7 +100,7 @@ Elements.reason_confirm = Gui.define("player_list/reason_confirm") } :on_click(function(_, player, element) local action_name = Elements.container.get_selected_action(player) - local button_data = action_name and config.buttons[action_name] + local button_data = action_name and actions.buttons[action_name] if button_data and button_data.reason_callback then local reason = assert(element.parent).entry.text --[[@as string?]] if reason == nil or not reason:find("%S") then reason = "no reason given" end @@ -330,7 +330,7 @@ Elements.action_bar = Gui.define("player_list/action_bar") action_bar.visible = false Elements.close_action_bar(action_bar) - for action_name, button_data in pairs(config.buttons) do + for action_name, button_data in pairs(actions.buttons) do local permission_flow = action_bar.add{ type = "flow", name = action_name } permission_flow.visible = false for _, button in ipairs(button_data) do @@ -356,7 +356,7 @@ function Elements.action_bar.refresh(action_bar, player, selected_player) end action_bar.visible = true - for action_name, buttons in pairs(config.buttons) do + for action_name, buttons in pairs(actions.buttons) do local flow = action_bar[action_name] if buttons.auth and not buttons.auth(player, selected_player) then flow.visible = false @@ -483,7 +483,7 @@ local function select_action(player, selected_action) Elements.container.set_selected_action(player, selected_action) Elements.player_table.refresh_player(player) end -config.set_accessors(Elements.container.get_selected_player, select_action) +actions.set_accessors(Elements.container.get_selected_player, select_action) --- Add the element to the left flow with a toolbar button Gui.add_left_element(Elements.container, true) diff --git a/exp_legacy/module/config/gui/player_list_actions.lua b/exp_scenario/module/gui/player_list_actions.lua similarity index 92% rename from exp_legacy/module/config/gui/player_list_actions.lua rename to exp_scenario/module/gui/player_list_actions.lua index 7f0a1270..a84f3956 100644 --- a/exp_legacy/module/config/gui/player_list_actions.lua +++ b/exp_scenario/module/gui/player_list_actions.lua @@ -1,9 +1,7 @@ ---- Config for the different action buttons that show on the player list; --- each button has the button define(s) given along side an auth function, and optional reason callback; --- if a reason callback is used then Store.set(action_name_store,player.name,'BUTTON_NAME') should be called during on_click; --- buttons can be removed from the gui by commenting them out of the config at the bottom of this file; --- the key used for the name of the button is the permission name used by the role system; --- @config Player-List +--[[-- Gui - Player List Actions +The action buttons shown on the player list, each with its auth function and optional reason callback. +The key of each button is the permission name used by the role system. +]] local ExpUtil = require("modules/exp_util") local Gui = require("modules/exp_gui") diff --git a/exp_scenario/module/gui/research_milestones.lua b/exp_scenario/module/gui/research_milestones.lua index a7e06846..2a84364f 100644 --- a/exp_scenario/module/gui/research_milestones.lua +++ b/exp_scenario/module/gui/research_milestones.lua @@ -5,7 +5,12 @@ Adds a gui for tracking research milestones local ExpUtil = require("modules/exp_util") local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") -local config = require("modules/exp_legacy/config/research") +local Feature = require("modules/exp_scenario/features") +local research_data = require("modules/exp_scenario/config/research_data") + +local feature, config = Feature.register("research_milestones", { + log_file = "log/research.log", -- where the milestone times are written +}) local table_to_json = helpers.table_to_json local write_file = helpers.write_file @@ -34,18 +39,10 @@ local research_targets = { length = 0, } ---- Select the mod set to be used for milestones -for _, mod_name in ipairs(config.mod_set_lookup) do - if script.active_mods[mod_name] then - config.mod_set = mod_name - break - end -end - do --- Calculate the research targets local research_index = 1 local total_time = 0 - for name, time in pairs(config.milestone[config.mod_set]) do + for name, time in pairs(research_data.milestone[research_data.mod_set]) do if prototypes.technology[name] and (not prototypes.technology[name].hidden) then research_targets.index_lookup[name] = research_index total_time = total_time + time * 60 @@ -328,7 +325,7 @@ function Elements.container.append_log_line(force) result_data[name] = force_data[research_index] end - write_file(config.file.name, table_to_json(result_data) .. "\n", true, 0) + write_file(config.log_file, table_to_json(result_data) .. "\n", true, 0) end --- Add the element to the left flow with a toolbar button @@ -339,7 +336,7 @@ Gui.toolbar.create_button{ sprite = "item/space-science-pack", tooltip = { "exp-gui_research-milestones.tooltip-main" }, visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.research") + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.research") end } @@ -350,8 +347,8 @@ local function on_research_finished(event) local force = event.research.force -- Check if the log should be updated and print a message to chat - if config.inf_res[config.mod_set][research_name] then - local log_requirement = config.file.final_res[config.mod_set] + if research_data.inf_res[research_data.mod_set][research_name] then + local log_requirement = research_data.final_res[research_data.mod_set] if research_name == log_requirement.name and research_level == log_requirement.level + 1 then Elements.container.append_log_line(force) end @@ -394,7 +391,7 @@ end local e = defines.events -return { +return feature:guard{ elements = Elements, events = { [e.on_research_finished] = on_research_finished, diff --git a/exp_scenario/module/gui/rocket_info.lua b/exp_scenario/module/gui/rocket_info.lua index e0b9074a..8958dc2a 100644 --- a/exp_scenario/module/gui/rocket_info.lua +++ b/exp_scenario/module/gui/rocket_info.lua @@ -5,7 +5,25 @@ Adds a rocket information gui which shows general stats, milestones and build pr local ExpUtil = require("modules/exp_util") local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") -local config = require("modules/exp_legacy/config/gui/rockets") +local Feature = require("modules/exp_scenario/features") + +local feature, config = Feature.register("rocket_info", { + show_stats = true, -- the stats section + show_first_rocket = true, -- when the first rocket was launched + show_last_rocket = true, -- when the last rocket was launched + show_fastest_rocket = true, -- the time taken for the fastest rocket + show_total_rockets = true, -- the total number of rockets launched + show_game_avg = true, -- the average across the entire map time + show_milestones = true, -- the milestones section + show_progress = true, -- the build progress section + allow_zoom_to_map = true, -- the zoom to map button +}) + +--- Each number is one statistic, 5 means the average time taken for the last 5 rockets +local rolling_avg = { 5, 10, 25 } + +--- Each number is one statistic, 5 means the time that the 5th rocket was launched +local milestones = { 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000 } --- @class ExpGui_RocketInfo.elements local Elements = {} @@ -26,7 +44,7 @@ local font_color = { -- The largest rolling average is used to know when an old launch time can be discarded local largest_rolling_avg = 0 -for _, avg_over in pairs(config.stats.rolling_avg) do +for _, avg_over in pairs(rolling_avg) do if avg_over > largest_rolling_avg then largest_rolling_avg = avg_over end @@ -110,7 +128,7 @@ Elements.position_label = Gui.define("rocket_info/position_label") ) :on_click(function(def, player, element) --- @cast def ExpGui_RocketInfo.elements.position_label - if not config.progress.allow_zoom_to_map then return end + if not config.allow_zoom_to_map then return end 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 } @@ -142,34 +160,34 @@ function Elements.stats_table.calculate_row_data(force) local force_rockets = force.rockets_launched local rows = {} - if config.stats.show_first_rocket then + if config.show_first_rocket then local value = stats.first_launch or 0 rows[#rows + 1] = { key = "first-launch", name = "first-launch", value = time_formats.caption_hours(value), tooltip = time_formats.tooltip_hours(value) } end - if config.stats.show_last_rocket then + if config.show_last_rocket then local value = stats.last_launch or 0 rows[#rows + 1] = { key = "last-launch", name = "last-launch", value = time_formats.caption_hours(value), tooltip = time_formats.tooltip_hours(value) } end - if config.stats.show_fastest_rocket then + if config.show_fastest_rocket then local value = stats.fastest_launch or 0 rows[#rows + 1] = { key = "fastest-launch", name = "fastest-launch", value = time_formats.caption_hours(value), tooltip = time_formats.tooltip_hours(value) } end - if config.stats.show_total_rockets then + if config.show_total_rockets then local total_rockets = get_game_rocket_count() if total_rockets == 0 then total_rockets = 1 end local percentage = math.floor(force_rockets / total_rockets * 1000) / 10 rows[#rows + 1] = { key = "total-rockets", name = "total-rockets", value = tostring(force_rockets), tooltip = { "exp-gui_rocket-info.value-tooltip-total-rockets", percentage } } end - if config.stats.show_game_avg then + if config.show_game_avg then local avg = force_rockets > 0 and math.floor(game.tick / force_rockets) or 0 rows[#rows + 1] = { key = "avg-launch", name = "avg-launch", value = time_formats.caption(avg), tooltip = time_formats.tooltip(avg) } end - for _, avg_over in pairs(config.stats.rolling_avg) do + for _, avg_over in pairs(rolling_avg) do local avg = get_rolling_average(force, avg_over) rows[#rows + 1] = { key = "avg-launch-n-" .. avg_over, name = "avg-launch-n", subname = avg_over, value = time_formats.caption(avg), tooltip = time_formats.tooltip(avg) } end @@ -258,7 +276,7 @@ function Elements.milestones_table.calculate_row_data(force) local force_rockets = force.rockets_launched local rows = {} - for _, milestone in ipairs(config.milestones) do + for _, milestone in ipairs(milestones) do -- The milestones config mixes the show_milestones flag with the milestone numbers if type(milestone) == "number" then if milestone <= force_rockets then @@ -421,7 +439,7 @@ end --- @param row_data ExpGui_RocketInfo.elements.progress_table.row_data function Elements.progress_table.add_row(progress_table, row_data) local rows = Elements.progress_table.data[progress_table].rows - local zoom_tooltip = config.progress.allow_zoom_to_map and { "exp-gui_rocket-info.progress-label-tooltip" } or nil + local zoom_tooltip = config.allow_zoom_to_map and { "exp-gui_rocket-info.progress-label-tooltip" } or nil local x = Elements.position_label(progress_table, { caption = row_data.x, tooltip = zoom_tooltip, entity = row_data.entity }) local y = Elements.position_label(progress_table, { caption = row_data.y, tooltip = zoom_tooltip, entity = row_data.entity }) @@ -544,17 +562,17 @@ Elements.container = Gui.define("rocket_info/container") local force = player.force --[[@as LuaForce]] local force_data = def._get_force_data(force) - if config.stats.show_stats then + if config.show_stats then local row_data = Elements.stats_table.calculate_row_data(force) Elements.stats_table.refresh(def.add_section(container, "stats", Elements.stats_table), row_data) end - if config.milestones.show_milestones then + if config.show_milestones then local row_data = Elements.milestones_table.calculate_row_data(force) Elements.milestones_table.refresh(def.add_section(container, "milestones", Elements.milestones_table), row_data) end - if config.progress.show_progress then + if config.show_progress then local row_data = Elements.progress_table.calculate_row_data_all(force_data.silos) Elements.progress_table.refresh(def.add_section(container, "progress", Elements.progress_table), row_data) end @@ -651,7 +669,7 @@ Gui.toolbar.create_button{ sprite = "item/rocket-silo", tooltip = { "exp-gui_rocket-info.tooltip-main" }, visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.rocket_info") + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.rocket_info") end } @@ -678,7 +696,7 @@ local function on_cargo_pod_finished_ascending(event) -- Discard the launch time that is no longer needed by any rolling average unless it is a milestone local remove_rocket = rockets_launched - largest_rolling_avg - if remove_rocket > 0 and not table.array_contains(config.milestones, remove_rocket) then + if remove_rocket > 0 and not table.array_contains(milestones, remove_rocket) then times[remove_rocket] = nil end @@ -721,7 +739,7 @@ end local e = defines.events -return { +return feature:guard{ elements = Elements, events = { [e.on_cargo_pod_finished_ascending] = on_cargo_pod_finished_ascending, diff --git a/exp_scenario/module/gui/science_production.lua b/exp_scenario/module/gui/science_production.lua index c2ef7273..72854bf2 100644 --- a/exp_scenario/module/gui/science_production.lua +++ b/exp_scenario/module/gui/science_production.lua @@ -6,7 +6,28 @@ local ExpUtil = require("modules/exp_util") local Gui = require("modules/exp_gui") local Colors = require("modules/exp_util/include/color") local Roles = require("modules/exp_roles") -local config = require("modules/exp_legacy/config/gui/science") +local Feature = require("modules/exp_scenario/features") + +local feature, config = Feature.register("science_production", { + show_eta = true, -- the eta for research completion + color_flux = 0.1, -- the fluctuation allowed in production before the icon changes color +}) + +--- The science packs shown, those not in the game are removed below +local science_packs = { + "automation-science-pack", + "logistic-science-pack", + "military-science-pack", + "chemical-science-pack", + "production-science-pack", + "utility-science-pack", + "space-science-pack", + "metallurgic-science-pack", + "agricultural-science-pack", + "electromagnetic-science-pack", + "cryogenic-science-pack", + "promethium-science-pack", +} local _format_number = require("util").format_number local clock_time_format = ExpUtil.format_time_factory_locale{ format = "clock", hours = true, minutes = true, seconds = true } @@ -16,9 +37,9 @@ local clock_time_format_nil = { "exp-gui_science-production.caption-eta-time", c local long_time_format_nil = long_time_format(nil) --- Remove invalid science packs, this can result from a certain mod not being loaded -for i = #config, 1, -1 do - if not prototypes.item[config[i]] then - table.remove(config, i) +for i = #science_packs, 1, -1 do + if not prototypes.item[science_packs[i]] then + table.remove(science_packs, i) end end @@ -337,7 +358,7 @@ do local _row_data = {} --- @type table ---- @field title string ---- @field body string ---- @field new boolean? ---- @field deleted boolean? - ---- Check if a player can create a new task ---- @param player LuaPlayer ---- @return boolean -local function has_permission_create_task(player) - local allow_add_task = config.allow_add_task - - if allow_add_task == "all" then - return true - elseif allow_add_task == "admin" then - return player.admin - elseif allow_add_task == "exp_roles" then - return Roles.player_has_permission(player, config.exp_roles_allow_add_task) - end - - return false -end - ---- Check if a player can edit an existing task ---- @param player LuaPlayer ---- @param task ExpGui_TaskList.Task ---- @return boolean -local function has_permission_edit_task(player, task) - local allow_edit_task = config.allow_edit_task - - -- Check if editing your own task allows bypassing other permissions - if config.user_can_edit_own_tasks and task.last_user.index == player.index then - return true - end - - -- Check player has permission based on value in the config - if allow_edit_task == "all" then - return true - elseif allow_edit_task == "admin" then - return player.admin - elseif allow_edit_task == "exp_roles" then - return Roles.player_has_permission(player, config.exp_roles_allow_edit_task) - end - - return false -end - ---- @class ExpGui_TaskList.element.new_task_button.elements ---- @field container LuaGuiElement - ---- Button displayed in the header bar, used to add a new task ---- @class ExpGui_TaskList.element.new_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.new_task_button = Gui.define("task_list/new_task_button") - :track_all_elements() - :draw{ - type = "sprite-button", - sprite = "utility/add", - tooltip = { "exp-gui_task-list.tooltip-new" }, - style = "tool_button", - } - :style(Styles.sprite22) - :element_data{ - container = Gui.from_argument(1) - } - :on_click(function(def, player, new_task_button) - --- @cast def ExpGui_TaskList.element.new_task_button - local container = def.data[new_task_button].container - Elements.container.open_edit_task(container, { - id = Elements.container.next_task_id(), - last_edit_tick = game.tick, - last_user = player, - editing = {}, - title = "", - body = "", - new = true, - }) - end) --[[@as any]] - ---- Refresh the visibility based on player permissions ---- @param new_task_button LuaGuiElement -function Elements.new_task_button.refresh(new_task_button) - local player = Gui.get_player(new_task_button) - new_task_button.visible = has_permission_create_task(player) -end - ---- Refresh the visibility based on player permissions ---- @param player LuaPlayer -function Elements.new_task_button.refresh_player(player) - local visible = has_permission_create_task(player) - for _, new_task_button in Elements.new_task_button:tracked_elements(player) do - new_task_button.visible = visible - end -end - ---- @class ExpGui_TaskList.elements.view_task_button.elements ---- @field task ExpGui_TaskList.Task ---- @field container LuaGuiElement - ---- Button used to view a task details, the caption is the task title ---- @class ExpGui_TaskList.element.view_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, task: ExpGui_TaskList.Task, container: LuaGuiElement): LuaGuiElement -Elements.view_task_button = Gui.define("task_list/view_task_button") - :draw(function(def, parent, task) - --- @cast def ExpGui_TaskList.element.view_task_button - --- @cast task ExpGui_TaskList.Task - return parent.add{ - type = "button", - style = "list_box_item", - caption = task.title, - tooltip = { "exp-gui_task-list.tooltip-last-edit", task.last_user.name, format_time(task.last_edit_tick) }, - } - end) - :style{ - width = 268, - horizontally_stretchable = true, - } - :element_data{ - task = Gui.from_argument(1), - container = Gui.from_argument(2), - } - :on_click(function(def, player, 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]] - ---- Refresh the title and tooltip of the view task button ---- @param view_task_button LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.view_task_button.refresh(view_task_button, task) - view_task_button.caption = task.title - view_task_button.tooltip = { "exp-gui_task-list.tooltip-last-edit", task.last_user.name, format_time(task.last_edit_tick) } -end - ---- Label used to signal that no tasks are open ---- @class ExpGui_TaskList.elements.no_tasks_header: ExpElement ---- @overload fun(parent: LuaGuiElement): LuaGuiElement -Elements.no_tasks_header = Gui.define("task_list/no_tasks_header") - :track_all_elements() - :draw(function(def, parent, ...) - local subheader = Gui.elements.subframe_base(parent, "negative_subheader_frame") - - local label = subheader.add{ - type = "label", - style = "bold_label", - caption = { "exp-gui_task-list.caption-no-tasks" }, - tooltip = { "exp-gui_task-list.tooltip-no-tasks" }, - } - - label.style.width = 268 - label.style.horizontal_align = "center" - - return subheader - end) - :style{ - padding = { 2, 0 }, - bottom_margin = 0, - } --[[@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]] - 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]]) - for _, no_tasks_header in Elements.no_tasks_header:tracked_elements(player) do - no_tasks_header.visible = visible - end -end - ---- Refresh the visibility of the no tasks label ---- @param force LuaForce -function Elements.no_tasks_header.refresh_force_online(force) - local visible = not Elements.container.has_tasks(force) - for _, no_tasks_header in Elements.no_tasks_header:online_elements(force) do - no_tasks_header.visible = visible - end -end - ---- A table containing all of the current tasks ---- @class ExpGui_TaskList.elements.task_table: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement): LuaGuiElement -Elements.task_list = Gui.define("task_list/task_list") - :draw(function(_, parent) - local task_list = parent.add{ - type = "scroll-pane", - vertical_scroll_policy = "auto", - horizontal_scroll_policy = "never", - style = "scroll_pane_under_subheader", - } - - local task_list_style = task_list.style - task_list_style.horizontally_stretchable = true - task_list_style.maximal_height = 224 - task_list_style.padding = 0 - - -- Cant modify vertical spacing on scroll pane style so need a sub flow - task_list = task_list.add{ type = "flow", direction = "vertical" } - - task_list_style = task_list.style - task_list_style.horizontally_stretchable = true - task_list_style.vertical_spacing = 0 - task_list_style.padding = 0 - - -- Add the no tasks header - local no_tasks_header = Elements.no_tasks_header(task_list) - Elements.no_tasks_header.refresh(no_tasks_header) - - return task_list - end) - :element_data{} --[[@as any]] - ---- Adds a task to the task list ---- @param task_list LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.task_list.add_task(task_list, task) - 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 - ---- Remove a task from the task list ---- @param task_list LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.task_list.remove_task(task_list, task) - local view_task_buttons = Elements.task_list.data[task_list] - Gui.destroy_if_valid(view_task_buttons[task.id]) - view_task_buttons[task.id] = nil -end - ---- Refresh a task on the list ---- @param task_list LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.task_list.refresh_task(task_list, task) - local view_task_buttons = Elements.task_list.data[task_list] - Elements.view_task_button.refresh(view_task_buttons[task.id], task) -end - ---- Refresh all tasks on the list ---- @param task_list LuaGuiElement ---- @param tasks ExpGui_TaskList.Task[] -function Elements.task_list.refresh_tasks(task_list, tasks) - local view_task_buttons = Elements.task_list.data[task_list] - local done = {} - - -- Refresh all valid tasks - for _, task in ipairs(tasks) do - done[task.id] = true - local view_task_button = view_task_buttons[task.id] - if view_task_button then - Elements.view_task_button.refresh(view_task_button, task) - else - Elements.task_list.add_task(task_list, task) - end - end - - -- Remove tasks buttons that are no longer required - for id, view_task_button in pairs(view_task_buttons) do - if not done[id] then - view_task_button.destroy() - end - end -end - ---- Select a task button, if nil then all buttons are reset ---- @param task_list LuaGuiElement ---- @param task ExpGui_TaskList.Task? -function Elements.task_list.select_task_button(task_list, task) - local view_task_buttons = Elements.task_list.data[task_list] - local task_button = task and view_task_buttons[task.id] - if task_button and not task_button.enabled then - return - end - for _, view_task_button in pairs(view_task_buttons) do - view_task_button.enabled = true - end - if task_button then - task_button.enabled = false - end -end - ---- The base footer used for view and edit ---- @class ExpGui_TaskList.elements.task_footer: ExpElement ---- @overload fun(parent: LuaGuiElement): LuaGuiElement -Elements.task_footer = Gui.define("task_list/task_footer") - :draw{ - type = "frame", - direction = "vertical", - style = "subfooter_frame", - } - :style{ - height = 0, - padding = 5, - use_header_filler = false, - } --[[@as any]] - ---- @class ExpGui_TaskList.elements.close_task_button.elements ---- @field container LuaGuiElement - ---- Button used to close an open task details ---- @class ExpGui_TaskList.element.close_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.close_task_button = Gui.define("task_list/close_task_button") - :draw{ - type = "sprite-button", - sprite = "utility/collapse", - style = "frame_action_button", - tooltip = { "exp-gui_task-list.tooltip-close" }, - } - :style(Styles.sprite22) - :element_data{ - container = Gui.from_argument(1), - } - :on_click(function(def, player, 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]] - ---- @class ExpGui_TaskList.elements.task_button.elements ---- @field task ExpGui_TaskList.Task? ---- @field container LuaGuiElement - ---- Button used to edit a task ---- @class ExpGui_TaskList.element.edit_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.edit_task_button = Gui.define("task_list/edit_task_button") - :draw{ - type = "button", - caption = { "exp-gui_task-list.caption-edit" }, - tooltip = { "exp-gui_task-list.tooltip-edit" }, - style = "shortcut_bar_button", - } - :style(Styles.footer_button) - :element_data{ - container = Gui.from_argument(1), - } - :on_click(function(def, player, 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]] - ---- Refresh the edit button ---- @param edit_task_button LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.edit_task_button.refresh(edit_task_button, task) - local player = Gui.get_player(edit_task_button) - Elements.edit_task_button.data[edit_task_button].task = task - edit_task_button.visible = has_permission_edit_task(player, task) - - if next(task.editing) then - local player_names = table.get_values(task.editing) - edit_task_button.tooltip = { "exp-gui_task-list.tooltip-edit", table.concat(player_names, ", ") } - else - edit_task_button.tooltip = { "exp-gui_task-list.tooltip-edit-none" } - end -end - ---- Button used to delete a task ---- @class ExpGui_TaskList.element.delete_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.delete_task_button = Gui.define("task_list/delete_task_button") - :draw{ - type = "button", - caption = { "exp-gui_task-list.caption-delete" }, - tooltip = { "exp-gui_task-list.tooltip-delete" }, - style = "shortcut_bar_button_red", - } - :style(Styles.footer_button) - :element_data{ - container = Gui.from_argument(1), - } - :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.close_footers(elements.container) - end) --[[@as any]] - ---- Refresh the delete button ---- @param delete_task_button LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.delete_task_button.refresh(delete_task_button, task) - local player = Gui.get_player(delete_task_button) - Elements.delete_task_button.data[delete_task_button].task = task - delete_task_button.visible = has_permission_edit_task(player, task) -end - ---- @class ExpGui_TaskList.elements.view_task_footer.elements ---- @field task ExpGui_TaskList.Task? ---- @field body_label LuaGuiElement ---- @field title_label LuaGuiElement ---- @field delete_task_button LuaGuiElement ---- @field edit_task_button LuaGuiElement - ---- The view footer used for view task details ---- @class ExpGui_TaskList.elements.view_task_footer: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.view_task_footer = Gui.define("task_list/view_task_footer") - :draw(function(def, parent, container) - --- @cast def ExpGui_TaskList.elements.view_task_footer - local view_task_footer = Elements.task_footer(parent) - - local header_flow = view_task_footer.add{ type = "flow" } - header_flow.add{ - type = "label", - style = "frame_title", - caption = { "exp-gui_task-list.caption-view-footer" }, - } - header_flow.style.right_padding = 1 - header_flow.add{ type = "empty-widget" }.style.horizontally_stretchable = true - Elements.close_task_button(header_flow, container) - - local title_label = view_task_footer.add{ type = "label" } - local title_label_style = title_label.style - title_label_style.font = "default-bold" - title_label_style.single_line = false - title_label_style.padding = 4 - - local body_label = view_task_footer.add{ type = "label" } - body_label.style.single_line = false - body_label.style.padding = 4 - - local action_flow = view_task_footer.add{ type = "flow" } - local delete_task_button = Elements.delete_task_button(action_flow, container) - local edit_task_button = Elements.edit_task_button(action_flow, container) - - def.data[view_task_footer] = { - task = nil, - body_label = body_label, - title_label = title_label, - delete_task_button = delete_task_button, - edit_task_button = edit_task_button, - } - - return view_task_footer - end) --[[@as any]] - ---- Refresh the view task with new task details ---- @param view_task_footer LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.view_task_footer.refresh(view_task_footer, task) - local elements = Elements.view_task_footer.data[view_task_footer] - Elements.delete_task_button.refresh(elements.delete_task_button, task) - Elements.edit_task_button.refresh(elements.edit_task_button, task) - elements.title_label.caption = task.title - elements.body_label.caption = task.body - elements.task = task -end - ---- Refresh the view task with previously selected task ---- If task is provided, then will only update if task is the selected task ---- @param view_task_footer LuaGuiElement ---- @param task ExpGui_TaskList.Task? -function Elements.view_task_footer.update(view_task_footer, task) - local elements = Elements.view_task_footer.data[view_task_footer] - if elements.task and (task == nil or task == elements.task) then - Elements.view_task_footer.refresh(view_task_footer, elements.task) - end -end - ---- Refresh the view task with the previously selected task ---- @param player LuaPlayer -function Elements.view_task_footer.update_player(player) - for _, view_task_footer in Elements.view_task_footer:tracked_elements(player) do - Elements.view_task_footer.update(view_task_footer) - end -end - ---- @class ExpGui_TaskList.elements.task_message_textfield.elements ---- @field confirm_task_button LuaGuiElement? - ---- Textfield element used in both the task create and edit footers ---- @class ExpGui_TaskList.elements.task_message_textfield: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, confirm_task_button: LuaGuiElement?): LuaGuiElement -Elements.task_message_textfield = Gui.define("task_list/task_message_textfield") - :draw{ - type = "text-box", - text = "", - } - :style{ - maximal_width = 268, - minimal_height = 100, - horizontally_stretchable = true, - } - :element_data{ - confirm_task_button = Gui.from_argument(1), - } - :on_text_changed(function(def, player, task_message_textfield) - --- @cast def ExpGui_TaskList.elements.task_message_textfield - 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]] - ---- Set the confirm task button to update on text changed ---- @param task_message_textfield LuaGuiElement ---- @param confirm_task_button LuaGuiElement -function Elements.task_message_textfield.set_confirm_task_button(task_message_textfield, confirm_task_button) - Elements.task_message_textfield.data[task_message_textfield].confirm_task_button = confirm_task_button -end - ---- Refresh the task message field ---- @param task_message_textfield LuaGuiElement ---- @param task ExpGui_TaskList.Task -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 - assert(elements.confirm_task_button).enabled = string.len(message) > 5 - task_message_textfield.text = message - task_message_textfield.focus() -end - ---- @class ExpGui_TaskList.elements.confirm_task_button.elements ---- @field task ExpGui_TaskList.Task? ---- @field task_message_textfield LuaGuiElement ---- @field container LuaGuiElement - ---- Textfield element used in both the task create and edit footers ---- @class ExpGui_TaskList.elements.confirm_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, task_message_textfield: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.confirm_task_button = Gui.define("task_list/confirm_task_button") - :draw{ - type = "button", - name = Gui.from_name, - caption = { "exp-gui_task-list.caption-confirm" }, - tooltip = { "exp-gui_task-list.tooltip-confirm" }, - style = "shortcut_bar_button_green", - } - :style(Styles.footer_button) - :element_data{ - task_message_textfield = Gui.from_argument(1), - container = Gui.from_argument(2), - } - :on_click(function(def, player, confirm_task_button) - --- @cast def ExpGui_TaskList.elements.confirm_task_button - local elements = def.data[confirm_task_button] - local task_message_textfield = elements.task_message_textfield - local task = assert(elements.task) - - local parsed = Elements.confirm_task_button.parse(task_message_textfield.text) - task.last_edit_tick = game.tick - task.last_user = player - task.title = parsed.title - task.body = parsed.body - - Elements.container.close_footers(elements.container) - - 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]] - ---- Refresh the confirm task button ---- @param confirm_task_button LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.confirm_task_button.refresh(confirm_task_button, task) - Elements.confirm_task_button.data[confirm_task_button].task = task -end - ---- Parse a task message into its two parts ---- @param message string ---- @return { title: string, body: string } -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 } --[[@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 - parsed.body = "" - end - return parsed -end - ---- @class ExpGui_TaskList.elements.discard_task_button.elements ---- @field task ExpGui_TaskList.Task? ---- @field container LuaGuiElement - ---- Button used to close an open task details ---- @class ExpGui_TaskList.element.discard_task_button: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.discard_task_button = Gui.define("task_list/discard_task_button") - :draw{ - type = "button", - caption = { "exp-gui_task-list.caption-discard" }, - tooltip = { "exp-gui_task-list.tooltip-discard" }, - style = "shortcut_bar_button_red", - } - :style(Styles.footer_button) - :element_data{ - container = Gui.from_argument(1), - } - :on_click(function(def, player, discard_task_button) - --- @cast def ExpGui_TaskList.element.discard_task_button - local elements = def.data[discard_task_button] - local task = assert(elements.task) - if task.new then - Elements.container.close_footers(elements.container) - else - Elements.container.open_view_task(elements.container, task) - end - end) --[[@as any]] - ---- Refresh the discard task button ---- @param discard_task_button LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.discard_task_button.refresh(discard_task_button, task) - Elements.discard_task_button.data[discard_task_button].task = task -end - ---- @class ExpGui_TaskList.elements.edit_task_footer.elements ---- @field task ExpGui_TaskList.Task? ---- @field header LuaGuiElement ---- @field task_message_textfield LuaGuiElement ---- @field discard_task_button LuaGuiElement ---- @field confirm_task_button LuaGuiElement - ---- The view footer used for view task details ---- @class ExpGui_TaskList.elements.edit_task_footer: ExpElement ---- @field data table ---- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement -Elements.edit_task_footer = Gui.define("task_list/edit_task_footer") - :draw(function(def, parent, container) - --- @cast def ExpGui_TaskList.elements.edit_task_footer - local edit_task_footer = Elements.task_footer(parent) - - local header = edit_task_footer.add{ - type = "label", - style = "frame_title", - caption = { "exp-gui_task-list.caption-edit-footer" }, - } - - local task_message_textfield = Elements.task_message_textfield(edit_task_footer) - - local action_flow = edit_task_footer.add{ type = "flow" } - local discard_task_button = Elements.discard_task_button(action_flow, container) - local confirm_task_button = Elements.confirm_task_button(action_flow, task_message_textfield, container) - Elements.task_message_textfield.set_confirm_task_button(task_message_textfield, confirm_task_button) - - def.data[edit_task_footer] = { - task = nil, - header = header, - task_message_textfield = task_message_textfield, - discard_task_button = discard_task_button, - confirm_task_button = confirm_task_button, - } - - return edit_task_footer - end) --[[@as any]] - ---- Refresh the view task with new task details ---- @param edit_task_footer LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.edit_task_footer.refresh(edit_task_footer, task) - local player = Gui.get_player(edit_task_footer) - local elements = Elements.edit_task_footer.data[edit_task_footer] - task.editing[player.index] = player.name - - if elements.task and elements.task ~= task then - elements.task.editing[player.index] = nil - end - - elements.header.caption = { - task.new and "exp-gui_task-list.caption-create-footer" or "exp-gui_task-list.caption-edit-footer" - } - - Elements.task_message_textfield.refresh(elements.task_message_textfield, task) - Elements.confirm_task_button.refresh(elements.confirm_task_button, task) - Elements.discard_task_button.refresh(elements.discard_task_button, task) - elements.task = task -end - ---- Refresh the view task with previously selected task ---- If task is provided, then will only update if task is the selected task ---- @param edit_task_footer LuaGuiElement ---- @param task ExpGui_TaskList.Task? -function Elements.edit_task_footer.update(edit_task_footer, task) - local elements = Elements.edit_task_footer.data[edit_task_footer] - if elements.task and (task == nil or task == elements.task) then - Elements.edit_task_footer.refresh(edit_task_footer, elements.task) - end -end - ---- Refresh the view task with the previously selected task ---- @param player LuaPlayer -function Elements.edit_task_footer.update_player(player) - for _, edit_task_footer in Elements.edit_task_footer:tracked_elements(player) do - Elements.edit_task_footer.update(edit_task_footer) - end -end - ---- Clear the previously edited task ---- @param edit_task_footer LuaGuiElement -function Elements.edit_task_footer.clear(edit_task_footer) - local elements = Elements.edit_task_footer.data[edit_task_footer] - if elements.task then - local player = Gui.get_player(edit_task_footer) - elements.task.editing[player.index] = nil - elements.task = nil - end -end - ---- @class ExpGui_TaskList.elements.container.elements ---- @field task_list LuaGuiElement ---- @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 ExpGui_TaskList.elements.container.data -Elements.container = Gui.define("task_list/container") - :track_all_elements() - :draw(function(def, parent) - --- @cast def ExpGui_TaskList.elements.container - local container = Gui.elements.container(parent) -- width 268 - local root = Gui.elements.container.get_root_element(container) - local elements = {} - - -- Add the header - local header = Gui.elements.header(container, { - caption = { "exp-gui_task-list.caption-main" }, - tooltip = { "exp-gui_task-list.tooltip-sub" }, - }) - - -- Add buttons to the header - local new_task_button = Elements.new_task_button(header, root) - Elements.new_task_button.refresh(new_task_button) - - -- Add the task table and footers - elements.task_list = Elements.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 tasks = def.data[force] - if tasks then - for _, task in ipairs(tasks) do - Elements.task_list.add_task(elements.task_list, task) - end - end - - -- Add the footers - elements.view_task_footer = Elements.view_task_footer(container, root) - elements.edit_task_footer = Elements.edit_task_footer(container, root) - elements.view_task_footer.visible = false - elements.edit_task_footer.visible = false - - -- Set the data and return - def.data[root] = elements --[[@as any]] - return root - end) - :global_data{ next_task_id = 1 } - :force_data{} --[[@as any]] - ---- Check if a force has tasks ---- @param force LuaForce ---- @return boolean -function Elements.container.has_tasks(force) - local tasks = Elements.container.data[force] - return tasks and #tasks > 0 -end - ---- Get the next task id ---- @return number -function Elements.container.next_task_id() - local next_task_id = Elements.container.data.global_data.next_task_id - Elements.container.data.global_data.next_task_id = next_task_id + 1 - return next_task_id -end - ---- Add a new task for a force ---- @param force LuaForce ---- @param task ExpGui_TaskList.Task -function Elements.container.add_task(force, task) - local tasks = Elements.container.data[force] - tasks[#tasks + 1] = task - task.deleted = nil - task.new = nil - - Elements.no_tasks_header.refresh_force_online(force) - for _, container in Elements.container:online_elements(force) do - local task_list = Elements.container.data[container].task_list - Elements.task_list.add_task(task_list, task) - end -end - ---- Remove a task from a force ---- @param force LuaForce ---- @param task ExpGui_TaskList.Task -function Elements.container.remove_task(force, task) - local tasks = Elements.container.data[force] - table.remove_element(tasks, task) - task.deleted = true - - Elements.no_tasks_header.refresh_force_online(force) - for _, container in Elements.container:online_elements(force) do - local task_list = Elements.container.data[container].task_list - Elements.task_list.remove_task(task_list, task) - end -end - ---- Open the view footer ---- @param container LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.container.open_view_task(container, task) - local elements = Elements.container.data[container] - Elements.view_task_footer.refresh(elements.view_task_footer, task) - Elements.edit_task_footer.clear(elements.edit_task_footer) - Elements.task_list.select_task_button(elements.task_list, task) - elements.view_task_footer.visible = true - elements.edit_task_footer.visible = false -end - ---- Open the edit footer ---- @param container LuaGuiElement ---- @param task ExpGui_TaskList.Task -function Elements.container.open_edit_task(container, task) - local elements = Elements.container.data[container] - Elements.edit_task_footer.refresh(elements.edit_task_footer, task) - Elements.task_list.select_task_button(elements.task_list, task) - elements.edit_task_footer.visible = true - elements.view_task_footer.visible = false -end - ---- Close the footers ---- @param container LuaGuiElement -function Elements.container.close_footers(container) - local elements = Elements.container.data[container] - Elements.edit_task_footer.clear(elements.edit_task_footer) - Elements.task_list.select_task_button(elements.task_list) - elements.view_task_footer.visible = false - elements.edit_task_footer.visible = false -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]] ] - for _, container in Elements.container:tracked_elements(player) do - local elements = Elements.container.data[container] - Elements.task_list.refresh_tasks(elements.task_list, tasks) - Elements.view_task_footer.update(elements.view_task_footer) - Elements.edit_task_footer.update(elements.edit_task_footer) - end -end - ---- Refresh a tasks for a force ---- @param force LuaForce ---- @param task ExpGui_TaskList.Task -function Elements.container.refresh_force_online(force, task) - for _, container in Elements.container:online_elements(force) do - local elements = Elements.container.data[container] - Elements.task_list.refresh_task(elements.task_list, task) - Elements.view_task_footer.update(elements.view_task_footer, task) - Elements.edit_task_footer.update(elements.edit_task_footer, task) - end -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]]) -end) - -Gui.toolbar.create_button{ - name = "toggle_task_list", - left_element = Elements.container, - sprite = "utility/not_enough_repair_packs_icon", - tooltip = { "exp-gui_task-list.tooltip-main" }, - visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.task_list") - end -} - ---- Update the gui when the player joins because it is likely to be outdated ---- @param event EventData.on_player_joined_game -local function refresh_player_tasks(event) - local player = Gui.get_player(event) - Elements.container.refresh_player(player) - Elements.no_tasks_header.refresh_player(player) -end - ---- Update the gui when the player joins because it is likely to be outdated ---- @param event EventData.on_player_joined_game -local function refresh_player_permissions(event) - local player = Gui.get_player(event) - Elements.new_task_button.refresh_player(player) - Elements.view_task_footer.update_player(player) - Elements.edit_task_footer.update_player(player) -end - -local e = defines.events - -return { - elements = Elements, - events = { - [e.on_player_joined_game] = refresh_player_tasks, - [e.on_player_changed_force] = refresh_player_tasks, - [Roles.events.on_player_roles_changed] = refresh_player_permissions, - } -} +--[[-- Gui - Task List +Adds a task list to the game which players can add, remove and edit items on +]] + +local Gui = require("modules/exp_gui") +local Roles = require("modules/exp_roles") +local Feature = require("modules/exp_scenario/features") + +local ExpUtil = require("modules/exp_util") +local format_time = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } + +--- @class ExpGui_TaskList.elements +local Elements = {} + +local Styles = { + sprite22 = Gui.styles.sprite{ + size = 22 + }, + footer_button = Gui.styles.sprite{ + height = 29, + maximal_width = 268, + horizontally_stretchable = true, + }, +} + +--- @class ExpGui_TaskList.Task +--- @field id number +--- @field last_user LuaPlayer +--- @field last_edit_tick number +--- @field editing table +--- @field title string +--- @field body string +--- @field new boolean? +--- @field deleted boolean? + +local feature, config = Feature.register("task_list", { + user_can_edit_own_tasks = true, -- the player who made a task can edit it without the edit permission +}) + +--- Check if a player can create a new task +--- @param player LuaPlayer +--- @return boolean +local function has_permission_create_task(player) + return Roles.player_has_permission(player, "exp_scenario.gui.task_list.add") +end + +--- Check if a player can edit an existing task +--- @param player LuaPlayer +--- @param task ExpGui_TaskList.Task +--- @return boolean +local function has_permission_edit_task(player, task) + if config.user_can_edit_own_tasks and task.last_user.index == player.index then + return true + end + return Roles.player_has_permission(player, "exp_scenario.gui.task_list.edit") +end + +--- @class ExpGui_TaskList.element.new_task_button.elements +--- @field container LuaGuiElement + +--- Button displayed in the header bar, used to add a new task +--- @class ExpGui_TaskList.element.new_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.new_task_button = Gui.define("task_list/new_task_button") + :track_all_elements() + :draw{ + type = "sprite-button", + sprite = "utility/add", + tooltip = { "exp-gui_task-list.tooltip-new" }, + style = "tool_button", + } + :style(Styles.sprite22) + :element_data{ + container = Gui.from_argument(1) + } + :on_click(function(def, player, new_task_button) + --- @cast def ExpGui_TaskList.element.new_task_button + local container = def.data[new_task_button].container + Elements.container.open_edit_task(container, { + id = Elements.container.next_task_id(), + last_edit_tick = game.tick, + last_user = player, + editing = {}, + title = "", + body = "", + new = true, + }) + end) --[[@as any]] + +--- Refresh the visibility based on player permissions +--- @param new_task_button LuaGuiElement +function Elements.new_task_button.refresh(new_task_button) + local player = Gui.get_player(new_task_button) + new_task_button.visible = has_permission_create_task(player) +end + +--- Refresh the visibility based on player permissions +--- @param player LuaPlayer +function Elements.new_task_button.refresh_player(player) + local visible = has_permission_create_task(player) + for _, new_task_button in Elements.new_task_button:tracked_elements(player) do + new_task_button.visible = visible + end +end + +--- @class ExpGui_TaskList.elements.view_task_button.elements +--- @field task ExpGui_TaskList.Task +--- @field container LuaGuiElement + +--- Button used to view a task details, the caption is the task title +--- @class ExpGui_TaskList.element.view_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, task: ExpGui_TaskList.Task, container: LuaGuiElement): LuaGuiElement +Elements.view_task_button = Gui.define("task_list/view_task_button") + :draw(function(def, parent, task) + --- @cast def ExpGui_TaskList.element.view_task_button + --- @cast task ExpGui_TaskList.Task + return parent.add{ + type = "button", + style = "list_box_item", + caption = task.title, + tooltip = { "exp-gui_task-list.tooltip-last-edit", task.last_user.name, format_time(task.last_edit_tick) }, + } + end) + :style{ + width = 268, + horizontally_stretchable = true, + } + :element_data{ + task = Gui.from_argument(1), + container = Gui.from_argument(2), + } + :on_click(function(def, player, 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]] + +--- Refresh the title and tooltip of the view task button +--- @param view_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.view_task_button.refresh(view_task_button, task) + view_task_button.caption = task.title + view_task_button.tooltip = { "exp-gui_task-list.tooltip-last-edit", task.last_user.name, format_time(task.last_edit_tick) } +end + +--- Label used to signal that no tasks are open +--- @class ExpGui_TaskList.elements.no_tasks_header: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.no_tasks_header = Gui.define("task_list/no_tasks_header") + :track_all_elements() + :draw(function(def, parent, ...) + local subheader = Gui.elements.subframe_base(parent, "negative_subheader_frame") + + local label = subheader.add{ + type = "label", + style = "bold_label", + caption = { "exp-gui_task-list.caption-no-tasks" }, + tooltip = { "exp-gui_task-list.tooltip-no-tasks" }, + } + + label.style.width = 268 + label.style.horizontal_align = "center" + + return subheader + end) + :style{ + padding = { 2, 0 }, + bottom_margin = 0, + } --[[@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]] + 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]]) + for _, no_tasks_header in Elements.no_tasks_header:tracked_elements(player) do + no_tasks_header.visible = visible + end +end + +--- Refresh the visibility of the no tasks label +--- @param force LuaForce +function Elements.no_tasks_header.refresh_force_online(force) + local visible = not Elements.container.has_tasks(force) + for _, no_tasks_header in Elements.no_tasks_header:online_elements(force) do + no_tasks_header.visible = visible + end +end + +--- A table containing all of the current tasks +--- @class ExpGui_TaskList.elements.task_table: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.task_list = Gui.define("task_list/task_list") + :draw(function(_, parent) + local task_list = parent.add{ + type = "scroll-pane", + vertical_scroll_policy = "auto", + horizontal_scroll_policy = "never", + style = "scroll_pane_under_subheader", + } + + local task_list_style = task_list.style + task_list_style.horizontally_stretchable = true + task_list_style.maximal_height = 224 + task_list_style.padding = 0 + + -- Cant modify vertical spacing on scroll pane style so need a sub flow + task_list = task_list.add{ type = "flow", direction = "vertical" } + + task_list_style = task_list.style + task_list_style.horizontally_stretchable = true + task_list_style.vertical_spacing = 0 + task_list_style.padding = 0 + + -- Add the no tasks header + local no_tasks_header = Elements.no_tasks_header(task_list) + Elements.no_tasks_header.refresh(no_tasks_header) + + return task_list + end) + :element_data{} --[[@as any]] + +--- Adds a task to the task list +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_list.add_task(task_list, task) + 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 + +--- Remove a task from the task list +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_list.remove_task(task_list, task) + local view_task_buttons = Elements.task_list.data[task_list] + Gui.destroy_if_valid(view_task_buttons[task.id]) + view_task_buttons[task.id] = nil +end + +--- Refresh a task on the list +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_list.refresh_task(task_list, task) + local view_task_buttons = Elements.task_list.data[task_list] + Elements.view_task_button.refresh(view_task_buttons[task.id], task) +end + +--- Refresh all tasks on the list +--- @param task_list LuaGuiElement +--- @param tasks ExpGui_TaskList.Task[] +function Elements.task_list.refresh_tasks(task_list, tasks) + local view_task_buttons = Elements.task_list.data[task_list] + local done = {} + + -- Refresh all valid tasks + for _, task in ipairs(tasks) do + done[task.id] = true + local view_task_button = view_task_buttons[task.id] + if view_task_button then + Elements.view_task_button.refresh(view_task_button, task) + else + Elements.task_list.add_task(task_list, task) + end + end + + -- Remove tasks buttons that are no longer required + for id, view_task_button in pairs(view_task_buttons) do + if not done[id] then + view_task_button.destroy() + end + end +end + +--- Select a task button, if nil then all buttons are reset +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task? +function Elements.task_list.select_task_button(task_list, task) + local view_task_buttons = Elements.task_list.data[task_list] + local task_button = task and view_task_buttons[task.id] + if task_button and not task_button.enabled then + return + end + for _, view_task_button in pairs(view_task_buttons) do + view_task_button.enabled = true + end + if task_button then + task_button.enabled = false + end +end + +--- The base footer used for view and edit +--- @class ExpGui_TaskList.elements.task_footer: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.task_footer = Gui.define("task_list/task_footer") + :draw{ + type = "frame", + direction = "vertical", + style = "subfooter_frame", + } + :style{ + height = 0, + padding = 5, + use_header_filler = false, + } --[[@as any]] + +--- @class ExpGui_TaskList.elements.close_task_button.elements +--- @field container LuaGuiElement + +--- Button used to close an open task details +--- @class ExpGui_TaskList.element.close_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.close_task_button = Gui.define("task_list/close_task_button") + :draw{ + type = "sprite-button", + sprite = "utility/collapse", + style = "frame_action_button", + tooltip = { "exp-gui_task-list.tooltip-close" }, + } + :style(Styles.sprite22) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, 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]] + +--- @class ExpGui_TaskList.elements.task_button.elements +--- @field task ExpGui_TaskList.Task? +--- @field container LuaGuiElement + +--- Button used to edit a task +--- @class ExpGui_TaskList.element.edit_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.edit_task_button = Gui.define("task_list/edit_task_button") + :draw{ + type = "button", + caption = { "exp-gui_task-list.caption-edit" }, + tooltip = { "exp-gui_task-list.tooltip-edit" }, + style = "shortcut_bar_button", + } + :style(Styles.footer_button) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, 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]] + +--- Refresh the edit button +--- @param edit_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.edit_task_button.refresh(edit_task_button, task) + local player = Gui.get_player(edit_task_button) + Elements.edit_task_button.data[edit_task_button].task = task + edit_task_button.visible = has_permission_edit_task(player, task) + + if next(task.editing) then + local player_names = table.get_values(task.editing) + edit_task_button.tooltip = { "exp-gui_task-list.tooltip-edit", table.concat(player_names, ", ") } + else + edit_task_button.tooltip = { "exp-gui_task-list.tooltip-edit-none" } + end +end + +--- Button used to delete a task +--- @class ExpGui_TaskList.element.delete_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.delete_task_button = Gui.define("task_list/delete_task_button") + :draw{ + type = "button", + caption = { "exp-gui_task-list.caption-delete" }, + tooltip = { "exp-gui_task-list.tooltip-delete" }, + style = "shortcut_bar_button_red", + } + :style(Styles.footer_button) + :element_data{ + container = Gui.from_argument(1), + } + :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.close_footers(elements.container) + end) --[[@as any]] + +--- Refresh the delete button +--- @param delete_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.delete_task_button.refresh(delete_task_button, task) + local player = Gui.get_player(delete_task_button) + Elements.delete_task_button.data[delete_task_button].task = task + delete_task_button.visible = has_permission_edit_task(player, task) +end + +--- @class ExpGui_TaskList.elements.view_task_footer.elements +--- @field task ExpGui_TaskList.Task? +--- @field body_label LuaGuiElement +--- @field title_label LuaGuiElement +--- @field delete_task_button LuaGuiElement +--- @field edit_task_button LuaGuiElement + +--- The view footer used for view task details +--- @class ExpGui_TaskList.elements.view_task_footer: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.view_task_footer = Gui.define("task_list/view_task_footer") + :draw(function(def, parent, container) + --- @cast def ExpGui_TaskList.elements.view_task_footer + local view_task_footer = Elements.task_footer(parent) + + local header_flow = view_task_footer.add{ type = "flow" } + header_flow.add{ + type = "label", + style = "frame_title", + caption = { "exp-gui_task-list.caption-view-footer" }, + } + header_flow.style.right_padding = 1 + header_flow.add{ type = "empty-widget" }.style.horizontally_stretchable = true + Elements.close_task_button(header_flow, container) + + local title_label = view_task_footer.add{ type = "label" } + local title_label_style = title_label.style + title_label_style.font = "default-bold" + title_label_style.single_line = false + title_label_style.padding = 4 + + local body_label = view_task_footer.add{ type = "label" } + body_label.style.single_line = false + body_label.style.padding = 4 + + local action_flow = view_task_footer.add{ type = "flow" } + local delete_task_button = Elements.delete_task_button(action_flow, container) + local edit_task_button = Elements.edit_task_button(action_flow, container) + + def.data[view_task_footer] = { + task = nil, + body_label = body_label, + title_label = title_label, + delete_task_button = delete_task_button, + edit_task_button = edit_task_button, + } + + return view_task_footer + end) --[[@as any]] + +--- Refresh the view task with new task details +--- @param view_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.view_task_footer.refresh(view_task_footer, task) + local elements = Elements.view_task_footer.data[view_task_footer] + Elements.delete_task_button.refresh(elements.delete_task_button, task) + Elements.edit_task_button.refresh(elements.edit_task_button, task) + elements.title_label.caption = task.title + elements.body_label.caption = task.body + elements.task = task +end + +--- Refresh the view task with previously selected task +--- If task is provided, then will only update if task is the selected task +--- @param view_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task? +function Elements.view_task_footer.update(view_task_footer, task) + local elements = Elements.view_task_footer.data[view_task_footer] + if elements.task and (task == nil or task == elements.task) then + Elements.view_task_footer.refresh(view_task_footer, elements.task) + end +end + +--- Refresh the view task with the previously selected task +--- @param player LuaPlayer +function Elements.view_task_footer.update_player(player) + for _, view_task_footer in Elements.view_task_footer:tracked_elements(player) do + Elements.view_task_footer.update(view_task_footer) + end +end + +--- @class ExpGui_TaskList.elements.task_message_textfield.elements +--- @field confirm_task_button LuaGuiElement? + +--- Textfield element used in both the task create and edit footers +--- @class ExpGui_TaskList.elements.task_message_textfield: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, confirm_task_button: LuaGuiElement?): LuaGuiElement +Elements.task_message_textfield = Gui.define("task_list/task_message_textfield") + :draw{ + type = "text-box", + text = "", + } + :style{ + maximal_width = 268, + minimal_height = 100, + horizontally_stretchable = true, + } + :element_data{ + confirm_task_button = Gui.from_argument(1), + } + :on_text_changed(function(def, player, task_message_textfield) + --- @cast def ExpGui_TaskList.elements.task_message_textfield + 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]] + +--- Set the confirm task button to update on text changed +--- @param task_message_textfield LuaGuiElement +--- @param confirm_task_button LuaGuiElement +function Elements.task_message_textfield.set_confirm_task_button(task_message_textfield, confirm_task_button) + Elements.task_message_textfield.data[task_message_textfield].confirm_task_button = confirm_task_button +end + +--- Refresh the task message field +--- @param task_message_textfield LuaGuiElement +--- @param task ExpGui_TaskList.Task +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 + assert(elements.confirm_task_button).enabled = string.len(message) > 5 + task_message_textfield.text = message + task_message_textfield.focus() +end + +--- @class ExpGui_TaskList.elements.confirm_task_button.elements +--- @field task ExpGui_TaskList.Task? +--- @field task_message_textfield LuaGuiElement +--- @field container LuaGuiElement + +--- Textfield element used in both the task create and edit footers +--- @class ExpGui_TaskList.elements.confirm_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, task_message_textfield: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.confirm_task_button = Gui.define("task_list/confirm_task_button") + :draw{ + type = "button", + name = Gui.from_name, + caption = { "exp-gui_task-list.caption-confirm" }, + tooltip = { "exp-gui_task-list.tooltip-confirm" }, + style = "shortcut_bar_button_green", + } + :style(Styles.footer_button) + :element_data{ + task_message_textfield = Gui.from_argument(1), + container = Gui.from_argument(2), + } + :on_click(function(def, player, confirm_task_button) + --- @cast def ExpGui_TaskList.elements.confirm_task_button + local elements = def.data[confirm_task_button] + local task_message_textfield = elements.task_message_textfield + local task = assert(elements.task) + + local parsed = Elements.confirm_task_button.parse(task_message_textfield.text) + task.last_edit_tick = game.tick + task.last_user = player + task.title = parsed.title + task.body = parsed.body + + Elements.container.close_footers(elements.container) + + 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]] + +--- Refresh the confirm task button +--- @param confirm_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.confirm_task_button.refresh(confirm_task_button, task) + Elements.confirm_task_button.data[confirm_task_button].task = task +end + +--- Parse a task message into its two parts +--- @param message string +--- @return { title: string, body: string } +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 } --[[@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 + parsed.body = "" + end + return parsed +end + +--- @class ExpGui_TaskList.elements.discard_task_button.elements +--- @field task ExpGui_TaskList.Task? +--- @field container LuaGuiElement + +--- Button used to close an open task details +--- @class ExpGui_TaskList.element.discard_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.discard_task_button = Gui.define("task_list/discard_task_button") + :draw{ + type = "button", + caption = { "exp-gui_task-list.caption-discard" }, + tooltip = { "exp-gui_task-list.tooltip-discard" }, + style = "shortcut_bar_button_red", + } + :style(Styles.footer_button) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, discard_task_button) + --- @cast def ExpGui_TaskList.element.discard_task_button + local elements = def.data[discard_task_button] + local task = assert(elements.task) + if task.new then + Elements.container.close_footers(elements.container) + else + Elements.container.open_view_task(elements.container, task) + end + end) --[[@as any]] + +--- Refresh the discard task button +--- @param discard_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.discard_task_button.refresh(discard_task_button, task) + Elements.discard_task_button.data[discard_task_button].task = task +end + +--- @class ExpGui_TaskList.elements.edit_task_footer.elements +--- @field task ExpGui_TaskList.Task? +--- @field header LuaGuiElement +--- @field task_message_textfield LuaGuiElement +--- @field discard_task_button LuaGuiElement +--- @field confirm_task_button LuaGuiElement + +--- The view footer used for view task details +--- @class ExpGui_TaskList.elements.edit_task_footer: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.edit_task_footer = Gui.define("task_list/edit_task_footer") + :draw(function(def, parent, container) + --- @cast def ExpGui_TaskList.elements.edit_task_footer + local edit_task_footer = Elements.task_footer(parent) + + local header = edit_task_footer.add{ + type = "label", + style = "frame_title", + caption = { "exp-gui_task-list.caption-edit-footer" }, + } + + local task_message_textfield = Elements.task_message_textfield(edit_task_footer) + + local action_flow = edit_task_footer.add{ type = "flow" } + local discard_task_button = Elements.discard_task_button(action_flow, container) + local confirm_task_button = Elements.confirm_task_button(action_flow, task_message_textfield, container) + Elements.task_message_textfield.set_confirm_task_button(task_message_textfield, confirm_task_button) + + def.data[edit_task_footer] = { + task = nil, + header = header, + task_message_textfield = task_message_textfield, + discard_task_button = discard_task_button, + confirm_task_button = confirm_task_button, + } + + return edit_task_footer + end) --[[@as any]] + +--- Refresh the view task with new task details +--- @param edit_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.edit_task_footer.refresh(edit_task_footer, task) + local player = Gui.get_player(edit_task_footer) + local elements = Elements.edit_task_footer.data[edit_task_footer] + task.editing[player.index] = player.name + + if elements.task and elements.task ~= task then + elements.task.editing[player.index] = nil + end + + elements.header.caption = { + task.new and "exp-gui_task-list.caption-create-footer" or "exp-gui_task-list.caption-edit-footer" + } + + Elements.task_message_textfield.refresh(elements.task_message_textfield, task) + Elements.confirm_task_button.refresh(elements.confirm_task_button, task) + Elements.discard_task_button.refresh(elements.discard_task_button, task) + elements.task = task +end + +--- Refresh the view task with previously selected task +--- If task is provided, then will only update if task is the selected task +--- @param edit_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task? +function Elements.edit_task_footer.update(edit_task_footer, task) + local elements = Elements.edit_task_footer.data[edit_task_footer] + if elements.task and (task == nil or task == elements.task) then + Elements.edit_task_footer.refresh(edit_task_footer, elements.task) + end +end + +--- Refresh the view task with the previously selected task +--- @param player LuaPlayer +function Elements.edit_task_footer.update_player(player) + for _, edit_task_footer in Elements.edit_task_footer:tracked_elements(player) do + Elements.edit_task_footer.update(edit_task_footer) + end +end + +--- Clear the previously edited task +--- @param edit_task_footer LuaGuiElement +function Elements.edit_task_footer.clear(edit_task_footer) + local elements = Elements.edit_task_footer.data[edit_task_footer] + if elements.task then + local player = Gui.get_player(edit_task_footer) + elements.task.editing[player.index] = nil + elements.task = nil + end +end + +--- @class ExpGui_TaskList.elements.container.elements +--- @field task_list LuaGuiElement +--- @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 ExpGui_TaskList.elements.container.data +Elements.container = Gui.define("task_list/container") + :track_all_elements() + :draw(function(def, parent) + --- @cast def ExpGui_TaskList.elements.container + local container = Gui.elements.container(parent) -- width 268 + local root = Gui.elements.container.get_root_element(container) + local elements = {} + + -- Add the header + local header = Gui.elements.header(container, { + caption = { "exp-gui_task-list.caption-main" }, + tooltip = { "exp-gui_task-list.tooltip-sub" }, + }) + + -- Add buttons to the header + local new_task_button = Elements.new_task_button(header, root) + Elements.new_task_button.refresh(new_task_button) + + -- Add the task table and footers + elements.task_list = Elements.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 tasks = def.data[force] + if tasks then + for _, task in ipairs(tasks) do + Elements.task_list.add_task(elements.task_list, task) + end + end + + -- Add the footers + elements.view_task_footer = Elements.view_task_footer(container, root) + elements.edit_task_footer = Elements.edit_task_footer(container, root) + elements.view_task_footer.visible = false + elements.edit_task_footer.visible = false + + -- Set the data and return + def.data[root] = elements --[[@as any]] + return root + end) + :global_data{ next_task_id = 1 } + :force_data{} --[[@as any]] + +--- Check if a force has tasks +--- @param force LuaForce +--- @return boolean +function Elements.container.has_tasks(force) + local tasks = Elements.container.data[force] + return tasks and #tasks > 0 +end + +--- Get the next task id +--- @return number +function Elements.container.next_task_id() + local next_task_id = Elements.container.data.global_data.next_task_id + Elements.container.data.global_data.next_task_id = next_task_id + 1 + return next_task_id +end + +--- Add a new task for a force +--- @param force LuaForce +--- @param task ExpGui_TaskList.Task +function Elements.container.add_task(force, task) + local tasks = Elements.container.data[force] + tasks[#tasks + 1] = task + task.deleted = nil + task.new = nil + + Elements.no_tasks_header.refresh_force_online(force) + for _, container in Elements.container:online_elements(force) do + local task_list = Elements.container.data[container].task_list + Elements.task_list.add_task(task_list, task) + end +end + +--- Remove a task from a force +--- @param force LuaForce +--- @param task ExpGui_TaskList.Task +function Elements.container.remove_task(force, task) + local tasks = Elements.container.data[force] + table.remove_element(tasks, task) + task.deleted = true + + Elements.no_tasks_header.refresh_force_online(force) + for _, container in Elements.container:online_elements(force) do + local task_list = Elements.container.data[container].task_list + Elements.task_list.remove_task(task_list, task) + end +end + +--- Open the view footer +--- @param container LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.container.open_view_task(container, task) + local elements = Elements.container.data[container] + Elements.view_task_footer.refresh(elements.view_task_footer, task) + Elements.edit_task_footer.clear(elements.edit_task_footer) + Elements.task_list.select_task_button(elements.task_list, task) + elements.view_task_footer.visible = true + elements.edit_task_footer.visible = false +end + +--- Open the edit footer +--- @param container LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.container.open_edit_task(container, task) + local elements = Elements.container.data[container] + Elements.edit_task_footer.refresh(elements.edit_task_footer, task) + Elements.task_list.select_task_button(elements.task_list, task) + elements.edit_task_footer.visible = true + elements.view_task_footer.visible = false +end + +--- Close the footers +--- @param container LuaGuiElement +function Elements.container.close_footers(container) + local elements = Elements.container.data[container] + Elements.edit_task_footer.clear(elements.edit_task_footer) + Elements.task_list.select_task_button(elements.task_list) + elements.view_task_footer.visible = false + elements.edit_task_footer.visible = false +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]] ] + for _, container in Elements.container:tracked_elements(player) do + local elements = Elements.container.data[container] + Elements.task_list.refresh_tasks(elements.task_list, tasks) + Elements.view_task_footer.update(elements.view_task_footer) + Elements.edit_task_footer.update(elements.edit_task_footer) + end +end + +--- Refresh a tasks for a force +--- @param force LuaForce +--- @param task ExpGui_TaskList.Task +function Elements.container.refresh_force_online(force, task) + for _, container in Elements.container:online_elements(force) do + local elements = Elements.container.data[container] + Elements.task_list.refresh_task(elements.task_list, task) + Elements.view_task_footer.update(elements.view_task_footer, task) + Elements.edit_task_footer.update(elements.edit_task_footer, task) + end +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]]) +end) + +Gui.toolbar.create_button{ + name = "toggle_task_list", + left_element = Elements.container, + sprite = "utility/not_enough_repair_packs_icon", + tooltip = { "exp-gui_task-list.tooltip-main" }, + visible = function(player, element) + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.task_list") + end +} + +--- Update the gui when the player joins because it is likely to be outdated +--- @param event EventData.on_player_joined_game +local function refresh_player_tasks(event) + local player = Gui.get_player(event) + Elements.container.refresh_player(player) + Elements.no_tasks_header.refresh_player(player) +end + +--- Update the gui when the player joins because it is likely to be outdated +--- @param event EventData.on_player_joined_game +local function refresh_player_permissions(event) + local player = Gui.get_player(event) + Elements.new_task_button.refresh_player(player) + Elements.view_task_footer.update_player(player) + Elements.edit_task_footer.update_player(player) +end + +local e = defines.events + +return feature:guard{ + elements = Elements, + events = { + [e.on_player_joined_game] = refresh_player_tasks, + [e.on_player_changed_force] = refresh_player_tasks, + [Roles.events.on_player_roles_changed] = refresh_player_permissions, + } +} diff --git a/exp_scenario/module/gui/vlayer.lua b/exp_scenario/module/gui/vlayer.lua index b5bea951..1295ea92 100644 --- a/exp_scenario/module/gui/vlayer.lua +++ b/exp_scenario/module/gui/vlayer.lua @@ -5,8 +5,8 @@ Shows what the virtual layer holds and lets players build and remove its interfa local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") local Colors = require("modules/exp_util/include/color") -local config = require("modules.exp_legacy.config.vlayer") local Vlayer = require("modules/exp_scenario/control/vlayer") +local feature, config = Vlayer.feature, Vlayer.config local format_number = require("util").format_number local Selection = require("modules/exp_util/selection") @@ -444,7 +444,7 @@ Gui.toolbar.create_button{ sprite = "entity/solar-panel", tooltip = { "exp-gui_vlayer.tooltip-main" }, visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.vlayer") + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.vlayer") end } @@ -520,12 +520,12 @@ local function refresh_player_permissions(event) Elements.controls.refresh_permissions(Gui.get_player(event)) end -return { +return feature:guard{ elements = Elements, events = { [Roles.events.on_player_roles_changed] = refresh_player_permissions, }, on_nth_tick = { - [config.update_tick_gui] = Elements.stats_table.refresh_online, + [60] = Elements.stats_table.refresh_online, } } diff --git a/exp_scenario/module/gui/warp_list.lua b/exp_scenario/module/gui/warp_list.lua index f76faed4..d7ac0574 100644 --- a/exp_scenario/module/gui/warp_list.lua +++ b/exp_scenario/module/gui/warp_list.lua @@ -5,7 +5,7 @@ Adds a list of warp points which players can travel between, add, and edit local Gui = require("modules/exp_gui") local Roles = require("modules/exp_roles") local Colors = require("modules/exp_util/include/color") -local config = require("modules.exp_legacy.config.gui.warps") +local Feature = require("modules/exp_scenario/features") local ExpUtil = require("modules/exp_util") local format_time = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } @@ -31,18 +31,61 @@ local status_icons = { different = "[img=utility/warning_white]", } -local update_interval = floor(60 / config.update_smoothing) -local cooldown_ticks = config.cooldown_duration * 60 -local proximity_radius_sq = config.standard_proximity_radius ^ 2 -local spawn_radius_sq = config.spawn_proximity_radius ^ 2 -local minimum_distance_sq = config.minimum_distance ^ 2 +local feature, config = Feature.register("warp_list", { + minimum_distance = 100, -- the minimum distance that is allowed between warps on the same force + cooldown_duration = 60, -- the duration of the warp cooldown in seconds + standard_proximity_radius = 4, -- the minimum distance a player is allowed to be to a warp in order to use it + spawn_proximity_radius = 20, -- the minimum distance a player is allowed to be from their spawn point to use warps + user_can_edit_own_warps = false, -- the player who made a warp can edit it without the edit permission +}) + +--- Ticks between updates of the cooldown timers +local update_interval = 6 + +--- The default icon of new warps +local default_icon = { type = "item", name = "discharge-defense-equipment" } + +--- The entities which are created for warp areas { name, x, y } +local area_entities = { + { "small-lamp", -4, -2 }, { "small-lamp", -2, -4 }, { "medium-electric-pole", -3, -3 }, -- Top left corner + { "small-lamp", 3, -2 }, { "small-lamp", 1, -4 }, { "medium-electric-pole", 2, -3 }, -- Top right corner + { "small-lamp", 3, 1 }, { "small-lamp", 1, 3 }, { "medium-electric-pole", 2, 2 }, -- Bottom right corner + { "small-lamp", -4, 1 }, { "small-lamp", -2, 3 }, { "medium-electric-pole", -3, 2 }, -- Bottom left corner + +} + +--- The tiles which are created for warp areas { name, x, y } +local area_tiles = { + { "black-refined-concrete", -4, -2 }, { "black-refined-concrete", -4, -1 }, { "black-refined-concrete", -4, 0 }, { "black-refined-concrete", -4, 1 }, + { "black-refined-concrete", -3, -3 }, { "purple-refined-concrete", -3, -2 }, { "purple-refined-concrete", -3, -1 }, { "purple-refined-concrete", -3, 0 }, + { "purple-refined-concrete", -3, 1 }, { "black-refined-concrete", -3, 2 }, { "black-refined-concrete", -2, -4 }, { "purple-refined-concrete", -2, -3 }, + { "purple-refined-concrete", -2, -2 }, { "purple-refined-concrete", -2, -1 }, { "purple-refined-concrete", -2, 0 }, { "purple-refined-concrete", -2, 1 }, + { "purple-refined-concrete", -2, 2 }, { "black-refined-concrete", -2, 3 }, { "black-refined-concrete", -1, -4 }, { "purple-refined-concrete", -1, -3 }, + { "purple-refined-concrete", -1, -2 }, { "purple-refined-concrete", -1, -1 }, { "purple-refined-concrete", -1, 0 }, { "purple-refined-concrete", -1, 1 }, + { "purple-refined-concrete", -1, 2 }, { "black-refined-concrete", -1, 3 }, { "black-refined-concrete", 0, -4 }, { "purple-refined-concrete", 0, -3 }, + { "purple-refined-concrete", 0, -2 }, { "purple-refined-concrete", 0, -1 }, { "purple-refined-concrete", 0, 0 }, { "purple-refined-concrete", 0, 1 }, + { "purple-refined-concrete", 0, 2 }, { "black-refined-concrete", 0, 3 }, { "black-refined-concrete", 1, -4 }, { "purple-refined-concrete", 1, -3 }, + { "purple-refined-concrete", 1, -2 }, { "purple-refined-concrete", 1, -1 }, { "purple-refined-concrete", 1, 0 }, { "purple-refined-concrete", 1, 1 }, + { "purple-refined-concrete", 1, 2 }, { "black-refined-concrete", 1, 3 }, { "black-refined-concrete", 2, -3 }, { "purple-refined-concrete", 2, -2 }, + { "purple-refined-concrete", 2, -1 }, { "purple-refined-concrete", 2, 0 }, { "purple-refined-concrete", 2, 1 }, { "black-refined-concrete", 2, 2 }, + { "black-refined-concrete", 3, -2 }, { "black-refined-concrete", 3, -1 }, { "black-refined-concrete", 3, 0 }, { "black-refined-concrete", 3, 1 }, + +} --- Names of the entities which make up a warp area, used to find them when the area is removed local area_entity_names = {} --- @type string[] -for _, entity in ipairs(config.entities) do +for _, entity in ipairs(area_entities) do area_entity_names[#area_entity_names + 1] = entity[1] end +--- The role permission each action needs +local permissions = { + bypass_warp_cooldown = "exp_scenario.gui.warp_list.bypass_cooldown", + bypass_warp_proximity = "exp_scenario.gui.warp_list.bypass_proximity", + allow_add_warp = "exp_scenario.gui.warp_list.add", + allow_edit_warp = "exp_scenario.gui.warp_list.edit", +} + --- @class ExpGui_WarpList.Warp --- @field id number --- @field force LuaForce @@ -63,21 +106,11 @@ end --- @field on_cooldown boolean --- @field bypass_proximity boolean ---- Check a permission from the config, which says who an action applies to --- @param player LuaPlayer --- @param action "bypass_warp_cooldown" | "bypass_warp_proximity" | "allow_add_warp" | "allow_edit_warp" --- @return boolean local function has_config_permission(player, action) - local setting = config[action] - if setting == "all" then - return true - elseif setting == "admin" then - return player.admin - elseif setting == "exp_roles" then - return Roles.player_has_permission(player, config["exp_roles_" .. action]) - end - - return false + return Roles.player_has_permission(player, permissions[action]) end --- Check if a player can edit a warp, the spawn warp can never be edited @@ -694,7 +727,7 @@ Elements.cooldown_bar = Gui.define("warp_list/cooldown_bar") --- @param cooldown number function Elements.cooldown_bar.refresh(cooldown_bar, cooldown) if cooldown > 0 then - cooldown_bar.value = 1 - cooldown / cooldown_ticks + cooldown_bar.value = 1 - cooldown / (config.cooldown_duration * 60) cooldown_bar.tooltip = { "exp-gui_warp-list.tooltip-cooldown", ceil(cooldown / 60) } else cooldown_bar.value = 1 @@ -877,12 +910,12 @@ function Elements.container._make_area(warp) warp.old_tile = surface.get_tile(position.x, position.y).name local tiles = {} - for index, tile in ipairs(config.tiles) do + for index, tile in ipairs(area_tiles) do tiles[index] = { name = tile[1], position = { x = tile[2] + position.x, y = tile[3] + position.y } } end surface.set_tiles(tiles) - for _, entity_config in ipairs(config.entities) do + for _, entity_config in ipairs(area_entities) do local entity = assert(surface.create_entity{ name = entity_config[1], position = { x = entity_config[2] + position.x, y = entity_config[3] + position.y }, @@ -908,7 +941,7 @@ function Elements.container._remove_area(warp) local surface = warp.surface local position = warp.position local tiles = {} - for index, tile in ipairs(config.tiles) do + for index, tile in ipairs(area_tiles) do tiles[index] = { name = old_tile, position = { x = tile[2] + position.x, y = tile[3] + position.y } } end surface.set_tiles(tiles) @@ -947,7 +980,7 @@ function Elements.container.add_warp(force, surface, position, player, name) id = id, force = force, name = name or "New warp", - icon = { type = config.default_icon.type, name = config.default_icon.name }, + icon = { type = default_icon.type, name = default_icon.name }, surface = surface, position = { x = floor(position.x), y = floor(position.y) }, last_user = player, @@ -1054,8 +1087,8 @@ function Elements.container.teleport_player(player, warp) local player_data = Elements.container._get_player_data(player) if not has_config_permission(player, "bypass_warp_cooldown") then - player_data.cooldown = cooldown_ticks - Elements.cooldown_bar.refresh_player(player, cooldown_ticks) + player_data.cooldown = config.cooldown_duration * 60 + Elements.cooldown_bar.refresh_player(player, player_data.cooldown) end player_data.in_range = warp Elements.warp_table.refresh_status_player(player) @@ -1109,7 +1142,7 @@ function Elements.container._update_proximity(player, player_data) end local in_range = nil --- @type ExpGui_WarpList.Warp? - if closest and closest_distance < (closest == force_data.spawn and spawn_radius_sq or proximity_radius_sq) then + if closest and closest_distance < (closest == force_data.spawn and config.spawn_proximity_radius or config.standard_proximity_radius) ^ 2 then in_range = closest end if in_range ~= player_data.in_range then @@ -1121,7 +1154,7 @@ function Elements.container._update_proximity(player, player_data) end local blocking = nil --- @type ExpGui_WarpList.Warp? - if closest and closest_distance <= minimum_distance_sq then + if closest and closest_distance <= config.minimum_distance ^ 2 then blocking = closest end if blocking ~= player_data.blocking then @@ -1135,10 +1168,10 @@ Gui.add_left_element(Elements.container, false) Gui.toolbar.create_button{ name = "toggle_warp_list", left_element = Elements.container, - sprite = config.default_icon.type .. "/" .. config.default_icon.name, + sprite = default_icon.type .. "/" .. default_icon.name, tooltip = { "exp-gui_warp-list.tooltip-main" }, visible = function(player, element) - return Roles.player_has_permission(player, "exp_scenario.gui.warp_list") + return feature:is_enabled() and Roles.player_has_permission(player, "exp_scenario.gui.warp_list") end }:on_click(function(def, player) -- A list the player opened themselves stays open when they leave a warp @@ -1202,7 +1235,7 @@ end local e = defines.events -return { +return feature:guard{ elements = Elements, events = { [e.on_player_created] = on_player_created,