diff --git a/changelog.txt b/changelog.txt index 22e1c62..f849fe0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,18 @@ --------------------------------------------------------------------------------------------------- +Version: 2.0.2 +Date: 2024-10-08 + Major Features: + - Added a new GUI config for all surface specific settings that can't be made available in the mod settings. This will let you easily configure all available settings via the in game custom GUI now. It is also possible to import and export settings as a serialized string. (You can still use a custom scenario to configure all settings as well using the template provided.) + - Added a player list tab to the custom GUI so you can see all players on the server, and their locations. + Bugfixes: + - Fixed a minor issue with resource placement not calculating the angle correctly when placing solid resources. + - Removed log spam related to offline enemy protection. + - Fixed an issue with regrowth that was causing log spam about chunks not being tracked properly. + Info: + - Added fish to moats. + - Doubled the default value for "minimum distance to existing chunks". There were some concerns bases might spawn too close together in some cases, this should help alleviate that. + - Changed safe_area radii config to use chunks instead of tiles. Makes more sense for the setting and is easier to visualize distance in chunks. (Older configs should get migrated automatically, but you may want to double check your settings.) +--------------------------------------------------------------------------------------------------- Version: 2.0.1 Date: 2024-09-25 Bugfixes: diff --git a/control.lua b/control.lua index dc1f4be..26b794e 100644 --- a/control.lua +++ b/control.lua @@ -68,9 +68,14 @@ end) -------------------------------------------------------------------------------- -- oarc_new_spawn_created = script.generate_event_name() --- script.on_configuration_changed(function(data) --- -- Regenerate event ID: --- end) +script.on_configuration_changed(function(data) + -- Regenerate event ID: + + -- Reset the players GUI + for _,player in pairs(game.players) do + RecreateOarcGui(player) + end +end) script.on_event(defines.events.on_runtime_mod_setting_changed, function(event) if (not StringStartsWith(event.setting, "oarc-mod")) then return end @@ -96,6 +101,7 @@ script.on_event(defines.events.on_player_changed_surface, function(event) SeparateSpawnsPlayerChangedSurface(event) end) + ---------------------------------------- -- Shared chat, so you don't have to type /s -- But you do lose your player colors across forces. @@ -248,30 +254,24 @@ end) -- Gui Events ---------------------------------------- script.on_event(defines.events.on_gui_click, function(event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end SeparateSpawnsGuiClick(event) - - ClickOarcGuiButton(event) - ServerInfoGuiClick(event) - SpawnCtrlGuiClick(event) - SettingsControlsTabGuiClick(event) - SettingsSurfaceControlsTabGuiClick(event) + OarcGuiTabsClick(event) end) --- Called when LuaGuiElement checked state is changed (related to checkboxes and radio buttons). script.on_event(defines.events.on_gui_checked_state_changed, function (event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end SeparateSpawnsGuiCheckedStateChanged(event) - - SpawnCtrlGuiOptionsSelect(event) + OarcGuiTabsCheckedStateChanged(event) end) script.on_event(defines.events.on_gui_selected_tab_changed, function (event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end - OarcGuiSelectedTabChanged(event) + OarcGuiTabsSelectedTabChanged(event) end) -- For capturing player escaping custom GUI so we can close it using ESC key. @@ -282,30 +282,36 @@ end) --- For sliders and other value changing elements. script.on_event(defines.events.on_gui_value_changed, function(event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end SeparateSpawnsGuiValueChanged(event) - SettingsControlsTabGuiValueChanged(event) + OarcGuiTabsValueChanged(event) end) --- For dropdowns and listboxes. script.on_event(defines.events.on_gui_selection_state_changed, function(event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end SeparateSpawnsGuiSelectionStateChanged(event) - SettingsControlsTabGuiSelectionStateChanged(event) + OarcGuiTabsSelectionStateChanged(event) end) script.on_event(defines.events.on_gui_text_changed, function(event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end - SettingsControlsTabGuiTextChanged(event) + OarcGuiTabsTextChanged(event) end) script.on_event(defines.events.on_gui_confirmed, function(event) - if not event.element.valid then return end -- Should we ever react to invalid GUI elements? + if not event.element.valid then return end + + OarcGuiTabsConfirmed(event) +end) + +script.on_event(defines.events.on_gui_elem_changed, function(event) + if not event.element.valid then return end - SettingsControlsTabGuiTextconfirmed(event) + OarcGuiTabsElemChanged(event) end) ---------------------------------------- diff --git a/devplan.txt b/devplan.txt index f1e2abe..fe896a1 100644 --- a/devplan.txt +++ b/devplan.txt @@ -35,6 +35,46 @@ Major: - Radar quality affects regrowth safe range? - Update electric pole connections for shared power if things change in V2.0 +- Source: https://forums.factorio.com/115737 +- Specifics that I might need to investigate: +Added LuaPlayer::land_on_planet() method. +Added LuaPlayer::enter_space_platform() and leave_space_platform() method. +Added cargo-landing-pad prototype. +Added space-platform-starter-pack, space-location, planet and space-connection prototypes. +Added surface-property and surface prototypes. +Added new controller type (remote), which is to build space platforms, so it allows ghost building but not any physical manipulation. +Added LuaPlayer::physical_surface, physical_surface_index, physical_vehicle and physical_position read. +Electric pole created through LuaSurface::create_entity can be requested to not auto connect. +Added LuaSurface::global_effect read/write. +Added LuaSurface::localised_name read/write. +LuaGameScript::print, LuaPlayer::print, LuaSurface::print and LuaForce::print no longer accept Color as a second parameter. +Added LuaSurface::set_property() and get_property() methods. +Added LuaSurface::execute_lightning() method. +Added LuaSurface::create_global_electric_network() and destroy_global_electric_network() methods. +Added LuaSurface::has_global_electric_network read. +Added LuaSurface::platform read. +Added LuaSurface::pollutant_type read. +Added airborne-pollutant prototype and changed various pollution related properties to support multiple pollution types. +Added LuaSurface::deletable read. +Added LuaForce::set_surface_hidden() and get_surface_hidden() methods. +Added cause argument to LuaSurface::create_entity. +Added LuaSurfacePrototype::surface_properties read. +Added on_player_controller_changed event. +Removed LuaPlayer::open_map, zoom_to_world, and close_map. LuaPlayer::set_controller with type 'remote' replaces these. +oved LuaGameScript::styles to LuaPrototypes::style. +Removed LuaGameScript::active_mods. Use LuaBootstrap::active_mods instead. +Renamed `global` into `storage`. +Added LuaGameScript::technology_notifications_enabled (read/write). +Added LuaGameScript::planets read. +Added LuaGameScript::get_vehicles. +Added LuaForce::platforms read. +Added LuaGameScript::set_win_ending_info() and set_lose_ending_info() methods. +Added LuaForce::unlock_space_location(), lock_space_location() and is_space_location_unlocked() methods. +Added LuaForce::create_space_platform() method. +Added LuaForce::unlock_space_platforms(), lock_space_platforms() and is_space_platforms_unlocked() methods. +Changed LuaForce::evolution_factor, evolution_factor_by_pollution, evolution_factor_by_time and evolution_factor_by_killing_spawners to get_* and set_* methods. +Added LuaForce::copy_from() and copy_chart() methods. + ------------------------------------------------------------------------------------------------------------------------ Other Ideas, Not Committed: diff --git a/info.json b/info.json index dcb4578..094c7d3 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { "name": "oarc-mod", - "version": "2.0.1", + "version": "2.0.2", "factorio_version": "1.1", "title": "Oarc Multiplayer Spawn", "author": "Oarcinae", @@ -14,6 +14,7 @@ "(?) space-exploration", "(?) alien-biomes", "(?) sonaxaton-research-queue", - "(?) helmod" + "(?) helmod", + "(?) rso-mod" ] } \ No newline at end of file diff --git a/lib/config.lua b/lib/config.lua index f1237b9..2d07cd9 100644 --- a/lib/config.lua +++ b/lib/config.lua @@ -26,6 +26,10 @@ SPAWN_SHAPE_CHOICE_SQUARE = "square" RESOURCES_SHAPE_CHOICE_CIRCLE = "circle" RESOURCES_SHAPE_CHOICE_SQUARE = "square" +MAX_CRASHED_SHIP_RESOURCES_ITEMS = 5 +MAX_CRASHED_SHIP_WRECKAGE_ITEMS = 1 + +-- THIS is used as the default starting items on all surfaces if no other settings are provided! ---@type OarcConfigStartingItems NAUVIS_STARTER_ITEMS = { @@ -54,6 +58,7 @@ NAUVIS_STARTER_ITEMS = }, } +-- THIS is used as the default spawn config on all surfaces if no other settings are provided! ---@type OarcConfigSpawn NAUVIS_SPAWN_CONFIG = { @@ -62,19 +67,19 @@ NAUVIS_SPAWN_CONFIG = safe_area = { -- Safe area has no aliens - -- This is the radius in tiles of safe area. - safe_radius = CHUNK_SIZE*6, + -- This is the radius in chunks of safe area. + safe_radius = 6, -- Warning area has significantly reduced aliens - -- This is the radius in tiles of warning area. - warn_radius = CHUNK_SIZE*12, + -- This is the radius in chunks of warning area. + warn_radius = 12, -- 1 : X (spawners alive : spawners destroyed) in this area warn_reduction = 20, -- Danger area has slightly reduced aliens - -- This is the radius in tiles of danger area. - danger_radius = CHUNK_SIZE*32, + -- This is the radius in chunks of danger area. + danger_radius = 32, -- 1 : X (spawners alive : spawners destroyed) in this area danger_reduction = 5, @@ -109,24 +114,32 @@ NAUVIS_SPAWN_CONFIG = ["iron-ore"] = { amount = 1500, size = 21, + + -- These are only used if not using automatic placing. x_offset = -29, y_offset = 16 }, ["copper-ore"] = { amount = 1200, size = 21, + + -- These are only used if not using automatic placing. x_offset = -28, y_offset = -3 }, ["stone"] = { amount = 1200, size = 21, + + -- These are only used if not using automatic placing. x_offset = -27, y_offset = -34 }, ["coal"] = { amount = 1200, size = 21, + + -- These are only used if not using automatic placing. x_offset = -27, y_offset = -20 } @@ -141,6 +154,8 @@ NAUVIS_SPAWN_CONFIG = { num_patches = 2, amount = 900000, + + -- These are only used if not using automatic placing. -- Starting position offset (relative to bottom/south of spawn area) x_offset_start = -3, y_offset_start = -10, @@ -202,7 +217,7 @@ OCFG = { -- chunks. It ensures the spawn area isn't too near generated/explored/existing -- area. The larger you make this, the further away players will spawn from -- generated map area (even if it is not visible on the map!). - minimum_distance_to_existing_chunks = 10, + minimum_distance_to_existing_chunks = 20, -- The range in which a player can select how close to the center of the map they want to spawn. near_spawn_distance = 100, @@ -330,11 +345,11 @@ OCFG = { -- At what angle (in radians) do resources start. -- 0 means starts directly east. -- Resources are placed clockwise from there. - angle_offset = 2.32, -- 2.32 is approx SSW. + angle_offset = 2.09, -- Approx SSW. -- At what andle do we place the last resource. -- angle_offset and angle_final determine spacing and placement. - angle_final = 4.46, -- 4.46 is approx NNW. + angle_final = 4.18, -- Approx NNW. -- Vertical offset in tiles for the deposit resource placement. Starting from top-left corner. -- Only applicable for square spawns. @@ -364,22 +379,22 @@ OCFG = { starting_items = NAUVIS_STARTER_ITEMS, spawn_config = NAUVIS_SPAWN_CONFIG }, - ["vulcanus"] = { - starting_items = NAUVIS_STARTER_ITEMS, - spawn_config = NAUVIS_SPAWN_CONFIG - }, - ["fulgora"] = { - starting_items = NAUVIS_STARTER_ITEMS, - spawn_config = NAUVIS_SPAWN_CONFIG - }, - ["gleba"] = { - starting_items = NAUVIS_STARTER_ITEMS, - spawn_config = NAUVIS_SPAWN_CONFIG - }, - ["aquilo"] = { - starting_items = NAUVIS_STARTER_ITEMS, - spawn_config = NAUVIS_SPAWN_CONFIG - } + -- ["vulcanus"] = { + -- starting_items = NAUVIS_STARTER_ITEMS, + -- spawn_config = NAUVIS_SPAWN_CONFIG + -- }, + -- ["fulgora"] = { + -- starting_items = NAUVIS_STARTER_ITEMS, + -- spawn_config = NAUVIS_SPAWN_CONFIG + -- }, + -- ["gleba"] = { + -- starting_items = NAUVIS_STARTER_ITEMS, + -- spawn_config = NAUVIS_SPAWN_CONFIG + -- }, + -- ["aquilo"] = { + -- starting_items = NAUVIS_STARTER_ITEMS, + -- spawn_config = NAUVIS_SPAWN_CONFIG + -- } }, -- Surfaces blacklist (Ignore these surfaces completely for spawning and regrowth!) @@ -490,10 +505,10 @@ OCFG = { ---@field shape SpawnShapeChoice Spawn a circle/octagon/square of trees around this base outline. ---@class OarcConfigSpawnSafeArea ----@field safe_radius number Safe area has no aliens This is the radius in tiles of safe area. ----@field warn_radius number Warning area has significantly reduced aliens This is the radius in tiles of warning area. +---@field safe_radius number Safe area has no aliens This is the radius in chunks of safe area. +---@field warn_radius number Warning area has significantly reduced aliens This is the radius in chunks of warning area. ---@field warn_reduction number 1 : X (spawners alive : spawners destroyed) in this area ----@field danger_radius number Danger area has slightly reduce aliens This is the radius in tiles of danger area. +---@field danger_radius number Danger area has slightly reduce aliens This is the radius in chunks of danger area. ---@field danger_reduction number 1 : X (spawners alive : spawners destroyed) in this area ---@class OarcConfigSpawnWater diff --git a/lib/config_parser.lua b/lib/config_parser.lua index 01ec7f5..91d0495 100644 --- a/lib/config_parser.lua +++ b/lib/config_parser.lua @@ -158,7 +158,7 @@ function ValidateAndLoadConfig() -- Load the template config into the global table. ---@class OarcConfig - global.ocfg = OCFG + global.ocfg = table.deepcopy(OCFG) -- Check that each entry in OCFG matches the default value of the mod setting. This is just for my own sanity. -- Helps make sure mod default settings and my internal config are in sync. @@ -178,11 +178,58 @@ function ValidateAndLoadConfig() GetScenarioOverrideSettings() -- Get any scenario settings and overwrite both the mod settings and OARC_CFG. + SyncModSettingsToOCFG() -- Make sure mod settings are in sync with global.ocfg table. + ValidateSettings() -- These are validation checks that can't be done within the mod settings natively. end +---DO some basic validation checks on the config settings. +---@return nil function ValidateSettings() + -- Verify the major sections exist. Not exhaustive but should catch missing sections. + if (global.ocfg["server_info"] == nil) then + log("ERROR - Missing server_info section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing server_info section in config! Loading defaults instead!") + global.ocfg.server_info = table.deepcopy(OCFG.server_info) + end + if (global.ocfg["gameplay"] == nil) then + log("ERROR - Missing gameplay section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing gameplay section in config! Loading defaults instead!") + global.ocfg.gameplay = table.deepcopy(OCFG.gameplay) + end + if (global.ocfg["regrowth"] == nil) then + log("ERROR - Missing regrowth section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing regrowth section in config! Loading defaults instead!") + global.ocfg.regrowth = table.deepcopy(OCFG.regrowth) + end + if (global.ocfg["spawn_general"] == nil) then + log("ERROR - Missing spawn_general section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing spawn_general section in config! Loading defaults instead!") + global.ocfg.spawn_general = table.deepcopy(OCFG.spawn_general) + end + if (global.ocfg["resource_placement"] == nil) then + log("ERROR - Missing resource_placement section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing resource_placement section in config! Loading defaults instead!") + global.ocfg.resource_placement = table.deepcopy(OCFG.resource_placement) + end + if (global.ocfg["surfaces_config"] == nil) then + log("ERROR - Missing surfaces_config section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing surfaces_config section in config! Loading defaults instead!") + global.ocfg.surfaces_config = table.deepcopy(OCFG.surfaces_config) + end + if (global.ocfg["surfaces_blacklist"] == nil) then + log("ERROR - Missing surfaces_blacklist section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing surfaces_blacklist section in config! Loading defaults instead!") + global.ocfg.surfaces_blacklist = table.deepcopy(OCFG.surfaces_blacklist) + end + if (global.ocfg["surfaces_blacklist_match"] == nil) then + log("ERROR - Missing surfaces_blacklist_match section in config! Loading defaults instead!") + SendBroadcastMsg("ERROR - Missing surfaces_blacklist_match section in config! Loading defaults instead!") + global.ocfg.surfaces_blacklist_match = table.deepcopy(OCFG.surfaces_blacklist_match) + end + + -- Validate enable_main_team and enable_separate_teams. -- Force enable_main_team if both are disabled. if (not global.ocfg.gameplay.enable_main_team and not global.ocfg.gameplay.enable_separate_teams) then @@ -221,6 +268,17 @@ function ValidateSettings() if (global.ocfg.surfaces_config["nauvis"] == nil) then error("nauvis surface config does not exist! Please check your mod settings or config!") end + + -- Very for each surface config that the item counts are valid. + for surface_name,surface_config in pairs(global.ocfg.surfaces_config) do + if (table_size(surface_config.starting_items.crashed_ship_resources) > MAX_CRASHED_SHIP_RESOURCES_ITEMS) then + error("Too many items in crashed_ship_resources for surface: " .. surface_name) + end + + if (table_size(surface_config.starting_items.crashed_ship_wreakage) > MAX_CRASHED_SHIP_WRECKAGE_ITEMS) then + error("Too many items in crashed_ship_wreakage for surface: " .. surface_name) + end + end end -- Read in the mod settings and copy them to the OARC_CFG table, overwriting the defaults in config.lua. @@ -241,6 +299,8 @@ function CacheModSettings() global.ocfg.gameplay.main_force_name = settings.startup["oarc-mod-main-force-name"].value --[[@as string]] end +---Get the scenario settings from the scenario if it exists. +---@return nil function GetScenarioOverrideSettings() if remote.interfaces["oarc_scenario"] then @@ -250,25 +310,30 @@ function GetScenarioOverrideSettings() -- Overwrite the non mod settings with the scenario settings. global.ocfg = scenario_settings + else + log("No scenario settings found.") + end +end + +---Syncs all mod settings to the OARC config table. +---@return nil +function SyncModSettingsToOCFG() - -- Override the mod settings with the scenario settings! - for _,entry in pairs(OCFG_KEYS) do - if (entry.type ~= "header") and (entry.type ~= "subheader") then - local mod_key = entry.mod_key - local oarc_key = entry.ocfg_keys - local scenario_value = GetGlobalOarcConfigUsingKeyTable(oarc_key) - if (scenario_value ~= nil) then - local ok,result = pcall(function() settings.global[mod_key] = { value = scenario_value } end) - if not ok then - error("Error setting mod setting: " .. mod_key .. " = " .. tostring(scenario_value) .. "\n" .. "If you see this, you probably picked an invalid value for a setting override in the custom scenario.") - end + -- Override the mod settings with the the global.ocfg settings. + for _,entry in pairs(OCFG_KEYS) do + if (entry.type ~= "header") and (entry.type ~= "subheader") then + local mod_key = entry.mod_key + local oarc_key = entry.ocfg_keys + local scenario_value = GetGlobalOarcConfigUsingKeyTable(oarc_key) + if (scenario_value ~= nil) then + local ok,result = pcall(function() settings.global[mod_key] = { value = scenario_value } end) + if not ok then + error("Error setting mod setting: " .. mod_key .. " = " .. tostring(scenario_value) .. "\n" .. "If you see this, you probably picked an invalid value for a setting override in the custom scenario.") end end end - - else - log("No scenario settings found.") end + end ---Handles the event when a mod setting is changed in the mod settings menu. diff --git a/lib/gui_tabs/player_list.lua b/lib/gui_tabs/player_list.lua new file mode 100644 index 0000000..1683972 --- /dev/null +++ b/lib/gui_tabs/player_list.lua @@ -0,0 +1,128 @@ +-- Contains the GUI for the player list tab. + +---Used by AddOarcGuiTab +---@param tab_container LuaGuiElement +---@param player LuaPlayer +---@return nil +function CreatePlayerListTab(tab_container, player) + + local scroll_pane = tab_container.add { + type = "scroll-pane", + direction = "vertical", + vertical_scroll_policy = "always", + } + scroll_pane.style.maximal_height = 500 + + + -- Make a table: player name, force name, home surface, time played, gps button + local player_table = scroll_pane.add { + type = "table", + column_count = 6, + style = "bordered_table", + } + + --- Add the header rows + AddLabel(player_table, nil, {"oarc-player-list-tab-column-header-player"}, "caption_label") + AddLabel(player_table, nil, {"oarc-player-list-tab-column-header-force"}, "caption_label") + AddLabel(player_table, nil, {"oarc-player-list-tab-column-header-surface"}, "caption_label") + AddLabel(player_table, nil, {"oarc-player-list-tab-column-header-time-player"}, "caption_label") + AddLabel(player_table, nil, {"oarc-player-list-tab-column-header-location"}, "caption_label") + AddLabel(player_table, nil, {"oarc-player-list-tab-column-header-status"}, "caption_label") + + -- List online players first + for _,online_player in pairs(game.connected_players) do + AddPlayerRow(player_table, online_player.name, true) + end + + -- List offline players later + for _,player in pairs(game.players) do + if (not player.connected) then + AddPlayerRow(player_table, player.name, false) + end + end + +end + + +---Add a row to the table for a player +---@param table LuaGuiElement +---@param player_name string +---@param online boolean +---@return nil +function AddPlayerRow(table, player_name, online) + local player = game.players[player_name] + if (player) then + if player.admin then + local label = AddLabel(table, nil, player.name, my_player_list_admin_style) + label.tooltip = "Admin" + else + AddLabel(table, nil, player.name, my_label_style) + end + AddLabel(table, nil, player.force.name, my_label_style) + + -- List home surface name or holding pen + if (player.surface.name == HOLDING_PEN_SURFACE_NAME) then + AddLabel(table, nil, {"oarc-player-waiting-to-spawn"}, my_label_style) + else + local spawn = FindPlayerHomeSpawn(player.name) + if (spawn) then + AddLabel(table, nil, spawn.surface_name, my_label_style) + else + AddLabel(table, nil, "Unknown?", my_label_style) -- Shouldn't happen + end + end + + AddLabel(table, nil, FormatTimeHoursSecs(player.online_time), my_label_style) + + CreatePlayerGPSButton(table, player.name) + + if online then + local label = AddLabel(table, nil, {"oarc-player-online"}, my_player_list_style) + label.style.font_color = {r=0.1, g=1, b=0.1} + else + AddLabel(table, nil, {"oarc-player-offline"}, my_player_list_offline_style) + end + end +end + + +---Display a GPS button for a specific location. (For the player list) +---@param container LuaGuiElement +---@param player_name string +---@return nil +function CreatePlayerGPSButton(container, player_name) + local gps_button = container.add { + type = "sprite-button", + sprite = "utility/gps_map_icon", + tags = { + action = "oarc_player_list_tab", + setting = "show_location", + player_name = player_name, + }, + style = "slot_button", + tooltip = {"", {"oarc-player-list-tab-location-button-tooltip"}, " (", game.players[player_name].surface.name, ")"}, + } + gps_button.style.width = 28 + gps_button.style.height = 28 +end + +---Handle the gui click of the player list tab in the Oarc GUI. +---@param event EventData.on_gui_click +---@return nil +function PlayerListTabGuiClick(event) + if not event.element.valid then return end + local player = game.players[event.player_index] + local tags = event.element.tags + + if (tags.action ~= "oarc_player_list_tab") then + return + end + + -- Shows the player's current location on the map + if (tags.setting == "show_location") then + local player_name = tags.player_name --[[@as string]] + local target_player = game.players[player_name] + + player.open_map(target_player.position, 0.05) -- TODO: Update this for spage age! + end +end \ No newline at end of file diff --git a/lib/gui_tabs/server_info.lua b/lib/gui_tabs/server_info.lua index 24b53f1..3752d53 100644 --- a/lib/gui_tabs/server_info.lua +++ b/lib/gui_tabs/server_info.lua @@ -92,7 +92,7 @@ end ---Server info gui click event handler ---@param event EventData.on_gui_click ---@return nil -function ServerInfoGuiClick(event) +function ServerInfoTabGuiClick(event) if not event.element.valid then return end local player = game.players[event.player_index] local tags = event.element.tags diff --git a/lib/gui_tabs/settings_controls.lua b/lib/gui_tabs/settings_controls.lua index 507f8b9..ea1849d 100644 --- a/lib/gui_tabs/settings_controls.lua +++ b/lib/gui_tabs/settings_controls.lua @@ -36,7 +36,13 @@ function CreateSettingsControlsTab(tab_container, player) scroll_pane_right.style.maximal_height = GENERIC_GUI_MAX_HEIGHT scroll_pane_right.style.padding = 5 scroll_pane_right.style.left_margin = 2 + CreateSurfaceSettingsSection(scroll_pane_right, player) + AddSpacerLine(scroll_pane_right) + + if (player.admin) then + CreateSettingsExportSection(scroll_pane_right, player) + end end ---Create the content for the mod settings section @@ -98,6 +104,53 @@ function CreateSurfaceSettingsSection(container, player) end +---Create the content for the settings export section. Exports the entire global.ocfg table into a string. +---@param container LuaGuiElement +---@param player LuaPlayer +---@return nil +function CreateSettingsExportSection(container, player) + AddLabel(container, nil, { "oarc-settings-tab-title-export" }, my_label_header2_style) + + local horizontal_flow = container.add { + type = "flow", + direction = "horizontal", + } + + local export_button = horizontal_flow.add { + type = "button", + caption = { "oarc-settings-tab-export-button" }, + style = "green_button", + tooltip = { "oarc-settings-tab-export-button-tooltip" }, + tags = { + action = "oarc_settings_tab_right_pane", + setting = "oarc_settings_export" + }, + } + + local import_button = horizontal_flow.add { + type = "button", + caption = { "oarc-settings-tab-import-button" }, + tooltip = { "oarc-settings-tab-import-button-tooltip" }, + style = "red_button", + tags = { + action = "oarc_settings_tab_right_pane", + setting = "oarc_settings_import" + }, + } + + local export_textfield = container.add { + type = "textfield", + name = "export_textfield", + text = " ", + tags = { + action = "oarc_settings_tab_right_pane", + setting = "oarc_settings_textfield" + }, + } + export_textfield.style.horizontally_stretchable = true + export_textfield.style.maximal_width = 500 +end + ---Handles the click event for the tab used by AddOarcGuiTab ---@param event EventData.on_gui_click ---@return nil @@ -105,7 +158,7 @@ function SettingsControlsTabGuiClick(event) if not (event.element.valid) then return end local gui_elem = event.element - if (gui_elem.tags.action ~= "oarc_settings_tab") then return end + if (gui_elem.tags.action ~= "oarc_settings_tab_left_pane") then return end local index = gui_elem.tags.setting local entry = OCFG_KEYS[index] @@ -121,7 +174,7 @@ function SettingsControlsTabGuiTextChanged(event) if not (event.element.valid) then return end local gui_elem = event.element - if (gui_elem.tags.action ~= "oarc_settings_tab") then return end + if (gui_elem.tags.action ~= "oarc_settings_tab_left_pane") then return end local index = gui_elem.tags.setting local value = gui_elem.text local entry = OCFG_KEYS[index] @@ -141,7 +194,7 @@ function SettingsControlsTabGuiTextconfirmed(event) if not (event.element.valid) then return end local gui_elem = event.element - if (gui_elem.tags.action ~= "oarc_settings_tab") then return end + if (gui_elem.tags.action ~= "oarc_settings_tab_left_pane") then return end local index = gui_elem.tags.setting local value = gui_elem.text local entry = OCFG_KEYS[index] @@ -260,7 +313,7 @@ function AddCheckboxSetting(tab_container, index, entry, enabled) state = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys), enabled = enabled, tooltip = { "mod-setting-description."..entry.mod_key }, - tags = { action = "oarc_settings_tab", setting = index }, + tags = { action = "oarc_settings_tab_left_pane", setting = index }, } end @@ -293,7 +346,7 @@ function AddTextfieldSetting(tab_container, index, entry, enabled) text = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys), enabled = enabled, tooltip = tooltip, - tags = { action = "oarc_settings_tab", setting = index }, + tags = { action = "oarc_settings_tab_left_pane", setting = index }, } end @@ -340,7 +393,7 @@ function AddIntegerSetting(tab_container, index, entry, enabled) text = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys), enabled = enabled, tooltip = tooltip, - tags = { action = "oarc_settings_tab", setting = index }, + tags = { action = "oarc_settings_tab_left_pane", setting = index }, } textfield.style.width = 50 end @@ -389,7 +442,7 @@ function AddDoubleSetting(tab_container, index, entry, enabled) text = string.format("%.2f", GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys)), enabled = enabled, tooltip = tooltip, - tags = { action = "oarc_settings_tab", setting = index }, + tags = { action = "oarc_settings_tab_left_pane", setting = index }, } textfield.style.width = 50 end @@ -431,7 +484,7 @@ function AddStringListDropdownSetting(tab_container, index, entry, enabled) selected_index = selected_index, enabled = enabled, tooltip = { "mod-setting-description."..entry.mod_key }, - tags = { action = "oarc_settings_tab", setting = index }, + tags = { action = "oarc_settings_tab_left_pane", setting = index }, } end @@ -448,7 +501,7 @@ function AddSurfaceCheckboxSetting(parent, surface_name, setting_name, state, ad name = surface_name.."_"..setting_name, type = "checkbox", state = state, - tags = { action = "oarc_settings_tab_surfaces", setting = setting_name, surface = surface_name }, + tags = { action = "oarc_settings_tab_right_pane", setting = setting_name, surface = surface_name }, enabled = admin, tooltip = tooltip, } @@ -461,11 +514,11 @@ function SettingsSurfaceControlsTabGuiClick(event) if not (event.element.valid) then return end local gui_elem = event.element - if (gui_elem.tags.action ~= "oarc_settings_tab_surfaces") then return end + if (gui_elem.tags.action ~= "oarc_settings_tab_right_pane") then return end local setting_name = gui_elem.tags.setting - local surface_name = gui_elem.tags.surface --[[@as string]] if (setting_name == "spawn_enabled") then + local surface_name = gui_elem.tags.surface --[[@as string]] global.oarc_surfaces[surface_name] = gui_elem.state if (#GetAllowedSurfaces() == 0) then @@ -473,7 +526,9 @@ function SettingsSurfaceControlsTabGuiClick(event) global.oarc_surfaces[global.ocfg.gameplay.default_surface] = true event.element.parent[global.ocfg.gameplay.default_surface.."_spawn_enabled"].state = true end + elseif (setting_name == "regrowth_enabled") then + local surface_name = gui_elem.tags.surface --[[@as string]] if (gui_elem.state) then if not IsRegrowthEnabledOnSurface(surface_name) then @@ -484,7 +539,34 @@ function SettingsSurfaceControlsTabGuiClick(event) RegrowthDisableSurface(surface_name) end end + + elseif (setting_name == "oarc_settings_textfield") then + gui_elem.select_all() -- Select all text when clicked + + elseif (setting_name == "oarc_settings_export") then + + log("Exported settings!") + local export_textfield = gui_elem.parent.parent["export_textfield"] + export_textfield.text = serpent.line(global.ocfg, {compact = true, sparse = true}) + + elseif (setting_name == "oarc_settings_import") then + local player = game.players[event.player_index] + local export_textfield = gui_elem.parent.parent["export_textfield"] + local import_text = export_textfield.text + local ok, copy = serpent.load(import_text) + if (not ok) or (type(copy) ~= "table") or (next(copy) == nil) then + log("Error importing settings!") + player.print("Error importing settings!") + else + global.ocfg = table.deepcopy(copy) + ValidateSettings() -- Some basic validation, not 100% foolproof + SyncModSettingsToOCFG() -- Sync the mod settings. + log("Imported settings!") + player.print("Imported settings!") + OarcGuiRefreshContent(player) + end end + end @@ -495,7 +577,7 @@ function SettingsControlsTabGuiSelectionStateChanged(event) if not (event.element.valid) then return end local gui_elem = event.element - if (gui_elem.tags.action ~= "oarc_settings_tab") then return end + if (gui_elem.tags.action ~= "oarc_settings_tab_left_pane") then return end local index = gui_elem.tags.setting local entry = OCFG_KEYS[index] diff --git a/lib/gui_tabs/spawn_controls.lua b/lib/gui_tabs/spawn_controls.lua index 2d034c1..ad87515 100644 --- a/lib/gui_tabs/spawn_controls.lua +++ b/lib/gui_tabs/spawn_controls.lua @@ -252,7 +252,7 @@ end ---Handle the gui checkboxes & radio buttons of the spawn control tab in the Oarc GUI. ---@param event EventData.on_gui_checked_state_changed ---@return nil -function SpawnCtrlGuiOptionsSelect(event) +function SpawnCtrlGuiOptionsCheckedStateChanged(event) if not event.element.valid then return end local player = game.players[event.player_index] local tags = event.element.tags @@ -282,7 +282,7 @@ end ---Handle the gui click of the spawn control tab in the Oarc GUI. ---@param event EventData.on_gui_click ---@return nil -function SpawnCtrlGuiClick(event) +function SpawnCtrlTabGuiClick(event) if not event.element.valid then return end local player = game.players[event.player_index] local tags = event.element.tags @@ -301,7 +301,7 @@ function SpawnCtrlGuiClick(event) elseif (tags.setting == "show_location") then local surface_name = tags.surface --[[@as string]] local position = tags.position --[[@as MapPosition]] - player.open_map(position, 0.05) + player.open_map(position, 0.05) -- TODO: Update this for spage age! player.print({"", { "oarc-spawn-gps-location" }, GetGPStext(surface_name, position)}) -- Accept or reject pending player join requests to a shared base diff --git a/lib/gui_tabs/surface_config.lua b/lib/gui_tabs/surface_config.lua new file mode 100644 index 0000000..569640f --- /dev/null +++ b/lib/gui_tabs/surface_config.lua @@ -0,0 +1,1112 @@ +--This file will let admins configure the "surfaces_config" table that is NOT available in the mod settings. + +---Creates the tab content, used by AddOarcGuiTab +---@param tab_container LuaGuiElement +---@param player LuaPlayer +---@return nil +function CreateSurfaceConfigTab(tab_container, player) + + local note = AddLabel(tab_container, nil, "This lets you configure the surface settings, it is only available to admins. These settings are NOT available in the mod settings page. If you want to automatically set these on the start of a new game using a custom file, please check out the included template scenario in the mod folder. I really question my sanity for bothering to make this GUI interface. Send help.", my_note_style) -- TODO: Localize + note.style.maximal_width = 600 + local warn = AddLabel(tab_container, nil, "WARNING: These settings are NOT sanitized, you might crash the game if you pick bad values!", my_warning_style) -- TODO: Localize + warn.style.maximal_width = 600 + + -- Drop down to select surface that you want to configure + local selected_surface_name = CreateSurfaceDropdown(tab_container) + -- AddSpacer(tab_container) + + local content = tab_container.add { + type = "flow", + direction = "vertical", + name = "surface_config_content_flow" + } + content.style.maximal_height = 600 + CreateSurfaceConfigContent(content, selected_surface_name) +end + +---Create surface config content section below the surface selection dropdown +---@param container LuaGuiElement +---@param surface_name string +---@return nil +function CreateSurfaceConfigContent(container, surface_name) + + -- Vertical scroll pane with dividers for each section for the surface config + local scroll_pane = container.add { + type = "scroll-pane", + direction = "vertical", + vertical_scroll_policy = "always", + } + scroll_pane.style.top_margin = 10 + + -- TODO: Localize EVERTYHING? + + AddLabel(scroll_pane, nil, "Starting And Respawn Items", my_label_header2_style) + local starting_items_flow = scroll_pane.add { type = "flow", direction = "horizontal" } + CreateItemsSection(starting_items_flow, surface_name, "Starting Items", "player_start_items") + CreateItemsSection(starting_items_flow, surface_name, "Respawn Items", "player_respawn_items") + AddSpacerLine(scroll_pane) + + AddLabel(scroll_pane, nil, "Crashed Ship Items", my_label_header2_style) + CreateCrashSiteEnable(scroll_pane, surface_name) + local crash_site_flow = scroll_pane.add { type = "flow", direction = "horizontal" } + CreateItemsSection(crash_site_flow, surface_name, "Crashed Ship (Max 5)", "crashed_ship_resources", MAX_CRASHED_SHIP_RESOURCES_ITEMS) + CreateItemsSection(crash_site_flow, surface_name, "Ship Wreckage (Max 1)", "crashed_ship_wreakage", MAX_CRASHED_SHIP_WRECKAGE_ITEMS) + AddSpacerLine(scroll_pane) + + AddLabel(scroll_pane, nil, "Spawn Area Resources", my_label_header2_style) + local spawn_config_flow = scroll_pane.add { type = "flow", direction = "horizontal" } + CreateSolidResourcesConfig(spawn_config_flow, surface_name) + CreateFluidResourcesConfig(spawn_config_flow, surface_name) + AddSpacerLine(scroll_pane) + + AddLabel(scroll_pane, nil, "Spawn Area Misc", my_label_header2_style) + local misc_config_flow = scroll_pane.add { type = "flow", direction = "horizontal" } + CreateMiscConfig(misc_config_flow, surface_name) + CreateSafeAreaConfig(misc_config_flow, surface_name) +end + + +---Create a checkbox to enable/disable the crashed ship +---@param container LuaGuiElement +---@param surface_name string +---@return nil +function CreateCrashSiteEnable(container, surface_name) + local crashed_ship_enabled = global.ocfg.surfaces_config[surface_name].starting_items.crashed_ship + + local crashed_ship_flow = container.add { + type = "flow", + direction = "horizontal" + } + crashed_ship_flow.style.vertical_align = "center" + + AddLabel(crashed_ship_flow, nil, "Enable Crashed Ship: ", "caption_label") -- TODO: Localize + local crashed_ship_enabled_checkbox = crashed_ship_flow.add { + type = "checkbox", + state = crashed_ship_enabled, + tags = { + action = "oarc_surface_config_tab", + setting = "crashed_ship_enabled", + surface_name = surface_name + }, + tooltip = "Enables the factorio style ship crash with items. If this is disabled, the crashed ship items and wreckage won't do anything." -- TODO: Localize + } +end + +---Create surface dropdown selection +---@param container LuaGuiElement +---@return string --The default selected surface name +function CreateSurfaceDropdown(container) + local surface_names = {} + for surface_name,_ in pairs(global.ocfg.surfaces_config) do + table.insert(surface_names, surface_name) + end + table.sort(surface_names) + + local selected_surface_name = surface_names[1] + + local horizontal_flow = container.add { + type = "flow", + direction = "horizontal" + } + horizontal_flow.style.vertical_align = "center" + AddLabel(horizontal_flow, nil, "Select Surface: ", "caption_label") + + local surface_dropdown = horizontal_flow.add { + type = "drop-down", + name = "surface_dropdown", + items = surface_names, + selected_index = 1, + tags = { + action = "oarc_surface_config_tab", + setting = "surface_dropdown" + }, + tooltip = "Select the surface you want to configure." -- TODO: Localize + } + + local dragger = horizontal_flow.add{ type="empty-widget", style="draggable_space_header" } + dragger.style.horizontally_stretchable = true + + -- A button to revert config to default (from OCFG hardcoded.) + local revert_button = horizontal_flow.add { + type = "button", + caption = "Revert", -- TODO: Localize + tooltip = "Revert to default.", -- TODO: Localize + style = "red_button", + tags = { + action = "oarc_surface_config_tab", + setting = "revert_to_default" + } + } + + -- Add button to copy nauvis config to current selected surface (if not nauvis) + local copy_button = horizontal_flow.add { + type = "button", + caption = "Copy Nauvis", -- TODO: Localize + tooltip = "Copy Nauvis settings to this surface.", -- TODO: Localize + style = "red_button", + tags = { + action = "oarc_surface_config_tab", + setting = "copy_nauvis" + } + } + + return selected_surface_name +end + + +---Create an items selection section +---@param container LuaGuiElement +---@param surface_name string +---@param header string +---@param setting_name string +---@param max_count integer? +---@return nil +function CreateItemsSection(container, surface_name, header, setting_name, max_count) + + local items = global.ocfg.surfaces_config[surface_name].starting_items[setting_name] + + if (items == nil) then + error("No items found for setting: " .. setting_name .. " for surface: " .. surface_name) + end + + if (max_count and (table_size(items) > max_count)) then + -- This would only happen with a bad config. + error("Too many items in starting items list!?") + end + + local vertical_flow = container.add { + type = "frame", + direction = "vertical", + -- style = "inside_shallow_frame" + } + vertical_flow.style.padding = 5 + vertical_flow.style.horizontally_stretchable = false + vertical_flow.style.vertically_stretchable = true + + AddLabel(vertical_flow, nil, header, my_label_header2_style) + + local table = vertical_flow.add { + type = "table", + column_count = 3, + -- style = "bordered_table", + tags = { + surface_name = surface_name, + setting = setting_name, + max_count = max_count or 0 + } + } + + --Add headers + AddLabel(table, nil, "Item", my_label_style) + AddLabel(table, nil, "Count", my_label_style) + AddLabel(table, nil, "", my_label_style) + + for item_name, item_count in pairs(items) do + SurfaceConfigItemListDisplayRow(table, item_name, item_count) + end + + -- Add a button to add another row + if (max_count == nil) or (max_count == 0) or (table_size(items) < max_count) then + SurfaceConfigItemListAddRowButton(table) + end +end + + + + +---Adds a row to a table with an item and count +---@param table LuaGuiElement +---@param item_name string? +---@param item_count integer? +---@return nil +function SurfaceConfigItemListDisplayRow(table, item_name, item_count) + -- Create choose elem button + local button = table.add { + type = "choose-elem-button", + elem_type = "item", + item = item_name, + tags = { + action = "oarc_surface_config_tab", + elem_button = true, + item_name = item_name or "" + }, + } + button.style.width = 28 + button.style.height = 28 + + -- Create number textfield + local textfield = table.add { + type = "textfield", + text = tostring(item_count or 0), + numeric = true, + allow_decimal = false, + tooltip = {"oarc-settings-tab-text-field-enter-tooltip" }, + tags = { + action = "oarc_surface_config_tab", + item_number_textfield = true, + item_name = item_name or "" + } + } + if (item_name == "") then + textfield.style = "invalid_value_textfield" + end + textfield.style.width = 40 + + -- Create a button to remove the row + local remove_button = table.add { + type = "sprite-button", + sprite = "utility/deconstruction_mark", + tooltip = "Remove Item", -- TODO: Localize + tags = { + action = "oarc_surface_config_tab", + remove_row_button = true, + item_name = item_name or "" + } + } + remove_button.style.width = 28 + remove_button.style.height = 28 +end + + +---Add the add row button to the table +---@param table LuaGuiElement +---@return nil +function SurfaceConfigItemListAddRowButton(table) + -- Add a button to add another row + local add_row_button = table.add { + type = "choose-elem-button", + elem_type = "entity", + -- type = "sprite-button", + -- sprite = "utility/check_mark_green", + tooltip = "Add Item", -- TODO: Localize + tags = { + action = "oarc_surface_config_tab", + add_row_button = true + } + } + add_row_button.style.width = 28 + add_row_button.style.height = 28 +end + + + + +---Create the safe area config section +---@param container LuaGuiElement +---@param surface_name string +---@return nil +function CreateSafeAreaConfig(container, surface_name) + + local safe_area_flow = container.add { + type = "frame", + direction = "vertical", + -- style = "inside_shallow_frame" + } + safe_area_flow.style.padding = 5 + safe_area_flow.style.horizontally_stretchable = false + safe_area_flow.style.vertically_stretchable = true + + local header = AddLabel(safe_area_flow, nil, "Safe Area Config", my_label_header2_style) -- TODO: Localize + header.tooltip = "This controls how safe the area around the spawns is." -- TODO: Localize + + -- TODO: Localize + CreateSpawnConfigIntegerField(safe_area_flow, surface_name, "Safe Area Radius", "safe_area", "safe_radius", "This is the radius in chunks around the spawn in which no enemies will spawn.") + CreateSpawnConfigIntegerField(safe_area_flow, surface_name, "Warn Area Radius", "safe_area", "warn_radius", "This is the radius in chunks around the spawn in which enemies will be significantly reduced.") + CreateSpawnConfigIntegerField(safe_area_flow, surface_name, "Warn Area Reduction", "safe_area", "warn_reduction", "This is the reduction factor to reduce the number of enemies in the warn area. 10 means (1/10)th the number of enemies.") + CreateSpawnConfigIntegerField(safe_area_flow, surface_name, "Danger Area Radius", "safe_area", "danger_radius", "This is the radius in chunks around the spawn in which enemies will be slightly reduced.") + CreateSpawnConfigIntegerField(safe_area_flow, surface_name, "Danger Area Reduction", "safe_area", "danger_reduction", "This is the reduction factor to reduce the number of enemies in the danger area. 10 means (1/10)th the number of enemies.") +end + + +---Create an integer textfield with a label +---@param container LuaGuiElement +---@param surface_name string +---@param label string +---@param setting_name string +---@param entry_name string +---@param tooltip string +---@return nil +function CreateSpawnConfigIntegerField(container, surface_name, label, setting_name, entry_name, tooltip) + + local value = global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][entry_name] + + local flow = container.add { + type = "flow", + direction = "horizontal" + } + + AddLabel(flow, nil, label, my_label_style) + + local dragger = flow.add{ type="empty-widget", style="draggable_space_header" } + dragger.style.horizontally_stretchable = true + + -- Create number textfield + local textfield = flow.add { + type = "textfield", + text = tostring(value), + tooltip = tooltip, + numeric = true, + allow_decimal = false, + tags = { + action = "oarc_surface_config_tab", + surface_name = surface_name, + setting = setting_name, + entry = entry_name, + spawn_config_textfield = true + } + } + textfield.style.width = 50 +end + + +---Create the solid resources config section +---@param container LuaGuiElement +---@param surface_name string +---@return nil +function CreateSolidResourcesConfig(container, surface_name) + + local solid_resources = global.ocfg.surfaces_config[surface_name].spawn_config.solid_resources + + local solid_resources_flow = container.add { + type = "frame", + direction = "vertical", + -- style = "inside_shallow_frame" + } + solid_resources_flow.style.padding = 5 + solid_resources_flow.style.horizontally_stretchable = false + solid_resources_flow.style.vertically_stretchable = true + + local header = AddLabel(solid_resources_flow, nil, "Solid Resources Config", my_label_header2_style) -- TODO: Localize + header.tooltip = "This controls the resources that will spawn around the spawn area." -- TODO: Localize + + -- Create a table to display the resources + local table = solid_resources_flow.add { + type = "table", + column_count = 4, + tags = { + surface_name = surface_name, + setting = "solid_resources" + } + } + + --Add headers + AddLabel(table, nil, "Type", my_label_style) + AddLabel(table, nil, "Amount", my_label_style) + AddLabel(table, nil, "Size", my_label_style) + AddLabel(table, nil, "", my_label_style) + + for resource_name, resource_data in pairs(solid_resources) do + log("Resource: " .. resource_name) + SolidResourcesConfigDisplayRow(table, resource_name, resource_data.amount, resource_data.size) + end + + SurfaceConfigSolidResourcesAddRowButton(table) +end + +---Create the fluid resources config section +---@param container LuaGuiElement +---@param surface_name string +---@return nil +function CreateFluidResourcesConfig(container, surface_name) + + local fluid_resources = global.ocfg.surfaces_config[surface_name].spawn_config.fluid_resources + + local fluid_resources_flow = container.add { + type = "frame", + direction = "vertical", + -- style = "inside_shallow_frame" + } + fluid_resources_flow.style.padding = 5 + fluid_resources_flow.style.horizontally_stretchable = false + fluid_resources_flow.style.vertically_stretchable = true + + local header = AddLabel(fluid_resources_flow, nil, "Fluid Resources Config", my_label_header2_style) -- TODO: Localize + header.tooltip = "This controls the fluid resources that will spawn around the spawn area." -- TODO: Localize + + -- Create a table to display the resources + local fluid_table = fluid_resources_flow.add { + type = "table", + column_count = 4, + tags = { + surface_name = surface_name, + setting = "fluid_resources" + } + } + + --Add headers + AddLabel(fluid_table, nil, "Type", my_label_style) + AddLabel(fluid_table, nil, "Count", my_label_style) + AddLabel(fluid_table, nil, "Amount", my_label_style) + AddLabel(fluid_table, nil, "", my_label_style) + + for resource_name, resource_data in pairs(fluid_resources) do + FluidResourcesConfigDisplayRow(fluid_table, resource_name, resource_data.num_patches, resource_data.amount) + end + + SurfaceConfigFluidResourcesAddRowButton(fluid_table) +end + +---Create the water strip config section +---@param container LuaGuiElement +---@param surface_name string +---@return nil +function CreateMiscConfig(container, surface_name) + + local misc_flow = container.add { + type = "frame", + direction = "vertical", + -- style = "inside_shallow_frame" + } + misc_flow.style.padding = 5 + misc_flow.style.horizontally_stretchable = false + + AddLabel(misc_flow, nil, "Misc Config", my_label_header2_style) -- TODO: Localize + + AddLabel(misc_flow, nil, "Water", my_player_list_style) + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Water Length", "water", "length", "This is the length in tiles of the water strip around the spawn area.") + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Water X Offset", "water", "x_offset", "This is the x_offset in tiles, from the north of the spawn area.") + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Water Y Offset", "water", "y_offset", "This is the y_offset in tiles, from the north of the spawn area.") + AddSpacerLine(misc_flow) + + AddLabel(misc_flow, nil, "Shared Chest", my_player_list_style) + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Chest X Offset", "shared_chest_position", "x_offset", "This is the x_offset in tiles, from the east of the spawn area.") + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Chest Y Offset", "shared_chest_position", "y_offset", "This is the y_offset in tiles, from the east of the spawn area.") + AddSpacerLine(misc_flow) + + AddLabel(misc_flow, nil, "Shared Power Pole", my_player_list_style) + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Power X Offset", "shared_power_pole_position", "x_offset", "This is the x_offset in tiles, from the east of the spawn area.") + CreateSpawnConfigIntegerField(misc_flow, surface_name, "Power Y Offset", "shared_power_pole_position", "y_offset", "This is the y_offset in tiles, from the east of the spawn area.") +end + +---Adds a row to a table with a resource, amount, and size +---@param table LuaGuiElement +---@param resource_name string? +---@param amount integer +---@param size integer +---@return nil +function SolidResourcesConfigDisplayRow(table, resource_name, amount, size) + -- Create choose elem button + local button = table.add { + type = "choose-elem-button", + elem_type = "entity", + elem_filters = {{filter = "type", type = "resource"}}, + tags = { + action = "oarc_surface_config_tab", + resource_elem_button = true, + resource_name = resource_name or "" + }, + } + button.elem_value = resource_name + button.style.width = 28 + button.style.height = 28 + + -- Create number textfield + local amount_textfield = table.add { + type = "textfield", + text = tostring(amount), + numeric = true, + allow_decimal = false, + tags = { + action = "oarc_surface_config_tab", + resource_amount_textfield = true, + resource_name = resource_name or "" + } + } + amount_textfield.style.width = 50 + + -- Create number textfield + local size_textfield = table.add { + type = "textfield", + text = tostring(size), + numeric = true, + allow_decimal = false, + tags = { + action = "oarc_surface_config_tab", + resource_size_textfield = true, + resource_name = resource_name or "" + } + } + size_textfield.style.width = 50 + + -- Create a button to remove the row + local remove_button = table.add { + type = "sprite-button", + sprite = "utility/deconstruction_mark", + tooltip = "Remove Resource", -- TODO: Localize + tags = { + action = "oarc_surface_config_tab", + resource_remove_row_button = true, + resource_name = resource_name or "" + } + } + remove_button.style.width = 28 + remove_button.style.height = 28 +end + +---Add the add row button to the table for solid resources +---@param table LuaGuiElement +---@return nil +function SurfaceConfigSolidResourcesAddRowButton(table) + -- Add a button to add another row + local add_row_button = table.add { + type = "choose-elem-button", + elem_type = "entity", + -- type = "sprite-button", + -- sprite = "utility/check_mark_green", + tooltip = "Add Item", -- TODO: Localize + tags = { + action = "oarc_surface_config_tab", + resource_add_row_button = true + } + } + add_row_button.style.width = 28 + add_row_button.style.height = 28 +end + +---Adds a row to a table with a fluid resource, amount, count, spacing, and vertical offset +---@param table LuaGuiElement +---@param resource_name string? +---@param count integer +---@param amount integer +---@return nil +function FluidResourcesConfigDisplayRow(table, resource_name, count, amount) + + -- Create choose elem button + local button = table.add { + type = "choose-elem-button", + elem_type = "entity", + elem_filters = {{filter = "type", type = "resource"}}, + tags = { + action = "oarc_surface_config_tab", + fluid_resource_elem_button = true, + resource_name = resource_name or "" + }, + } + button.elem_value = resource_name + button.style.width = 28 + button.style.height = 28 + + + -- Number of fluid patches + local count_textfield = table.add { + type = "textfield", + text = tostring(count), + numeric = true, + allow_decimal = false, + tags = { + action = "oarc_surface_config_tab", + fluid_resource_count_textfield = true, + resource_name = resource_name or "" + } + } + count_textfield.style.width = 40 + + -- Amount of fluid per patch + local amount_textfield = table.add { + type = "textfield", + text = tostring(amount), + numeric = true, + allow_decimal = false, + tags = { + action = "oarc_surface_config_tab", + fluid_resource_amount_textfield = true, + resource_name = resource_name or "" + } + } + amount_textfield.style.width = 60 + + -- Create a button to remove the row + local remove_button = table.add { + type = "sprite-button", + sprite = "utility/deconstruction_mark", + tooltip = "Remove Resource", + tags = { + action = "oarc_surface_config_tab", + resource_remove_row_button = true, + resource_name = resource_name or "" + } + } + remove_button.style.width = 28 + remove_button.style.height = 28 +end + +---Add the add row button to the table for fluid resources +---@param table LuaGuiElement +---@return nil +function SurfaceConfigFluidResourcesAddRowButton(table) + -- Add a button to add another row + local add_row_button = table.add { + type = "choose-elem-button", + elem_type = "entity", + -- type = "sprite-button", + -- sprite = "utility/check_mark_green", + tooltip = "Add Item", -- TODO: Localize + tags = { + action = "oarc_surface_config_tab", + fluid_resource_add_row_button = true + } + } + add_row_button.style.width = 28 + add_row_button.style.height = 28 +end + +--[[ + _____ _____ _ _ _____ _ _ _ _ _ ___ _ ___ ___ ___ + | __\ \ / / __| \| |_ _| | || | /_\ | \| | \| | | __| _ \/ __| + | _| \ V /| _|| .` | | | | __ |/ _ \| .` | |) | |__| _|| /\__ \ + |___| \_/ |___|_|\_| |_| |_||_/_/ \_\_|\_|___/|____|___|_|_\|___/ + +]] + +---Handle the surface dropdown selection +---@param event EventData.on_gui_selection_state_changed +---@return nil +function SurfaceConfigTabGuiSelect(event) + if not event.element.valid then return end + local player = game.players[event.player_index] + local tags = event.element.tags + + if (tags.action ~= "oarc_surface_config_tab") then + return + end + + if (tags.setting == "surface_dropdown") then + local selected_surface_name = event.element.items[event.element.selected_index] --[[@as string]] + + local content_flow = event.element.parent.parent["surface_config_content_flow"] + if (content_flow == nil) then + error("Content flow is nil? This shouldn't happen on surface dropdown select! " .. selected_surface_name) + end + + -- Recreate the content section + content_flow.clear() + CreateSurfaceConfigContent(content_flow, selected_surface_name) + end +end + + +---Handle on_gui_text_changed events +---@param event EventData.on_gui_text_changed +---@return nil +function SurfaceConfigTabGuiTextChanged(event) + if not event.element.valid then return end + local player = game.players[event.player_index] + local tags = event.element.tags + + if (tags.action ~= "oarc_surface_config_tab") then + return + end + + if (tags.item_number_textfield or tags.fluid_resource_count_textfield) then + event.element.style = "invalid_value_textfield" + event.element.style.width = 40 + elseif (tags.resource_amount_textfield or tags.resource_size_textfield or tags.spawn_config_textfield) then + event.element.style = "invalid_value_textfield" + event.element.style.width = 50 + elseif tags.fluid_resource_amount_textfield then + event.element.style = "invalid_value_textfield" + event.element.style.width = 60 + end +end + + +---Handle on_gui_confirmed events +---@param event EventData.on_gui_confirmed +---@return nil +function SurfaceConfigTabGuiConfirmed(event) + if not event.element.valid then return end + local player = game.players[event.player_index] + local tags = event.element.tags + + if (tags.action ~= "oarc_surface_config_tab") then + return + end + + if (tags.item_number_textfield) then + + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + local item_name = tags.item_name --[[@as string]] + + -- Check if an item is selected first. + if (tags.item_name == "") then + player.print("Please select an item first!") + event.element.text = "0" + return + end + + -- Update the count + local count = tonumber(event.element.text) or 0 + global.ocfg.surfaces_config[surface_name].starting_items[setting_name][item_name] = count + + event.element.style = "textbox" + event.element.style.width = 40 + + elseif (tags.resource_amount_textfield or tags.resource_size_textfield) then + + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + local resource_name = tags.resource_name --[[@as string]] + + -- Check if an item is selected first. + if (tags.resource_name == "") then + player.print("Please select a resource first!") + event.element.text = "0" + return + end + + -- Update the count + local count = tonumber(event.element.text) or 0 + + if (tags.resource_amount_textfield) then + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][resource_name].amount = count + elseif (tags.resource_size_textfield) then + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][resource_name].size = count + end + + event.element.style = "textbox" + event.element.style.width = 50 + + elseif (tags.fluid_resource_count_textfield or tags.fluid_resource_amount_textfield) then + + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + local resource_name = tags.resource_name --[[@as string]] + + -- Check if an item is selected first. + if (tags.resource_name == "") then + player.print("Please select a resource first!") + event.element.text = "0" + return + end + + -- Update the count + local count = tonumber(event.element.text) or 0 + + event.element.style = "textbox" + if (tags.fluid_resource_count_textfield) then + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][resource_name].num_patches = count + event.element.style.width = 40 + elseif (tags.fluid_resource_amount_textfield) then + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][resource_name].amount = count + event.element.style.width = 60 + end + + + elseif (tags.spawn_config_textfield) then + local surface_name = tags.surface_name --[[@as string]] + local setting_name = tags.setting --[[@as string]] + local entry_name = tags.entry --[[@as string]] + + local value = tonumber(event.element.text) or 0 + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][entry_name] = value + + event.element.style = "textbox" + event.element.style.width = 50 + + end +end + +---Handle elem changed events +---@param event EventData.on_gui_elem_changed +---@return nil +function SurfaceConfigTabGuiElemChanged(event) + if not event.element.valid then return end + local player = game.players[event.player_index] + local tags = event.element.tags + + if (tags.action ~= "oarc_surface_config_tab") then + return + end + + if (tags.elem_button) then + local new_item_name = event.element.elem_value --[[@as string]] + + if (new_item_name == nil) then + return + end + + local old_item_name = tags.item_name --[[@as string]] + + -- if the new item name is the same as the old item name, do nothing. + if (new_item_name == tags.item_name) then + return + end + + -- otherwise, check if the new item name is already in the list. + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + + if (global.ocfg.surfaces_config[surface_name].starting_items[setting_name][new_item_name]) then + player.print("Item already exists in list! " .. new_item_name) + event.element.elem_value = nil + return + end + + -- Update the item name in the list, keep the old count. + if (old_item_name ~= "") then + global.ocfg.surfaces_config[surface_name].starting_items[setting_name][new_item_name] = global.ocfg.surfaces_config[surface_name].starting_items[setting_name][old_item_name] + global.ocfg.surfaces_config[surface_name].starting_items[setting_name][old_item_name] = nil + else + global.ocfg.surfaces_config[surface_name].starting_items[setting_name][new_item_name] = 0 + end + + -- Update all tags with the new item name. + for _, child in pairs(event.element.parent.children) do + if (child.tags.item_name == old_item_name) then + local tags_copy = child.tags + tags_copy.item_name = new_item_name + child.tags = tags_copy + end + end + + elseif (tags.resource_elem_button) then + local new_resource_name = event.element.elem_value --[[@as string]] + + if (new_resource_name == nil) then + return + end + + if (game.entity_prototypes[new_resource_name].resource_category ~= "basic-solid") then + player.print("Resource must be a solid resource! " .. new_resource_name) + event.element.elem_value = nil + return + end + + local old_resource_name = tags.resource_name --[[@as string]] + + -- if the new resource name is the same as the old resource name, do nothing. + if (new_resource_name == tags.resource_name) then + return + end + + -- otherwise, check if the new resource name is already in the list. + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + + if (global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][new_resource_name]) then + player.print("Resource already exists in list! " .. new_resource_name) + event.element.elem_value = nil + return + end + + -- Update the resource name in the list, keep the old amount and size. + if (old_resource_name ~= "") then + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][new_resource_name] = global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][old_resource_name] + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][old_resource_name] = nil + else + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][new_resource_name] = {amount=0, size=0, x_offset=0, y_offset=0} + end + + -- Update all tags with the new resource name. + for _, child in pairs(event.element.parent.children) do + if (child.tags.resource_name == old_resource_name) then + local tags_copy = child.tags + tags_copy.resource_name = new_resource_name + child.tags = tags_copy + end + end + + elseif (tags.fluid_resource_elem_button) then + local new_resource_name = event.element.elem_value --[[@as string]] + + if (new_resource_name == nil) then + return + end + + if (game.entity_prototypes[new_resource_name].resource_category ~= "basic-fluid") then + player.print("Resource must be a fluid resource! " .. new_resource_name) + event.element.elem_value = nil + return + end + + local old_resource_name = tags.resource_name --[[@as string]] + + -- if the new resource name is the same as the old resource name, do nothing. + if (new_resource_name == tags.resource_name) then + return + end + + -- otherwise, check if the new resource name is already in the list. + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + + if (global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][new_resource_name]) then + player.print("Resource already exists in list! " .. new_resource_name) + event.element.elem_value = nil + return + end + + -- Update the resource name in the list, keep the old amount and size. + if (old_resource_name ~= "") then + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][new_resource_name] = global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][old_resource_name] + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][old_resource_name] = nil + else + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][new_resource_name] = {num_patches=0, amount=0, x_offset=0, y_offset=0} + end + + -- Update all tags with the new resource name. + for _, child in pairs(event.element.parent.children) do + if (child.tags.resource_name == old_resource_name) then + local tags_copy = child.tags + tags_copy.resource_name = new_resource_name + child.tags = tags_copy + end + end + end +end + +---Handle the surface config tab button clicks +---@param event EventData.on_gui_click +---@return nil +function SurfaceConfigTabGuiClick(event) + if not event.element.valid then return end + local player = game.players[event.player_index] + local tags = event.element.tags + + if (tags.action ~= "oarc_surface_config_tab") then + return + end + + if (tags.remove_row_button) then + + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + local item_name = tags.item_name --[[@as string]] + local max_count = parent.tags.max_count --[[@as integer]] + + -- Nil the entry + global.ocfg.surfaces_config[surface_name].starting_items[setting_name][item_name] = nil + + -- Delete the row by removing the child elements from the table. + local parent = event.element.parent --[[@as LuaGuiElement]] -- Ass(u)me that the parent is a table. + for _, child in pairs(parent.children) do + if (child.tags and child.tags.item_name == tags.item_name) then + child.destroy() + end + end + + -- Only add the add row if we haven't reached the max count AND the children count is 1 more than % 3. + local table_modulo_3 = #parent.children % 3 + if table_modulo_3 == 0 then + SurfaceConfigItemListAddRowButton(parent) + end + + + elseif (tags.add_row_button) then + + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + local max_count = parent.tags.max_count --[[@as integer]] + if (parent == nil) then + error("Parent is nil? This shouldn't happen on add row button click! " .. setting_name) + end + + -- Delete the button and add a new row and then add the button back. + event.element.destroy() + SurfaceConfigItemListDisplayRow(parent) + + -- Only add the add row if we haven't reached the max count. + local item_count = (#parent.children - 3) / 3 + if (max_count == nil) or (max_count == 0) or (item_count < max_count) then + SurfaceConfigItemListAddRowButton(parent) + end + + elseif (tags.setting == "crashed_ship_enabled") then + local surface_name = tags.surface_name --[[@as string]] + global.ocfg.surfaces_config[surface_name].starting_items.crashed_ship = event.element.state + + elseif (tags.setting == "revert_to_default") then + + local surface_name = event.element.parent["surface_dropdown"].items[event.element.parent["surface_dropdown"].selected_index] --[[@as string]] + + player.print("Revert to default: " .. surface_name) + global.ocfg.surfaces_config[surface_name].starting_items = table.deepcopy(NAUVIS_STARTER_ITEMS) + global.ocfg.surfaces_config[surface_name].spawn_config = table.deepcopy(NAUVIS_SPAWN_CONFIG) + + -- Recreate the content section + local content_flow = event.element.parent.parent["surface_config_content_flow"] + if (content_flow == nil) then + error("Content flow is nil? This shouldn't happen on revert to default! " .. surface_name) + end + content_flow.clear() + CreateSurfaceConfigContent(content_flow, surface_name) + + elseif (tags.setting == "copy_nauvis") then + + local surface_name = event.element.parent["surface_dropdown"].items[event.element.parent["surface_dropdown"].selected_index] --[[@as string]] + + if (surface_name == "nauvis") then + player.print("Already on nauvis, select a different surface to copy nauvis settings to!") + return + end + + player.print("Copy nauvis to " .. surface_name) + global.ocfg.surfaces_config[surface_name].starting_items = global.ocfg.surfaces_config["nauvis"].starting_items + global.ocfg.surfaces_config[surface_name].spawn_config = global.ocfg.surfaces_config["nauvis"].spawn_config + + -- Recreate the content section + local content_flow = event.element.parent.parent["surface_config_content_flow"] + if (content_flow == nil) then + error("Content flow is nil? This shouldn't happen on copy nauvis! " .. surface_name) + end + content_flow.clear() + CreateSurfaceConfigContent(content_flow, surface_name) + + elseif (tags.resource_remove_row_button) then + local resource_name = tags.resource_name --[[@as string]] + local parent = event.element.parent + local surface_name = parent.tags.surface_name --[[@as string]] + local setting_name = parent.tags.setting --[[@as string]] + + -- Nil the entry + global.ocfg.surfaces_config[surface_name].spawn_config[setting_name][resource_name] = nil + + -- Delete the row by removing the child elements from the table. + for _, child in pairs(parent.children) do + if (child.tags.resource_name == resource_name) then + child.destroy() + end + end + + elseif (tags.resource_add_row_button) then + + local parent = event.element.parent + local setting_name = parent.tags.setting --[[@as string]] + + if (parent == nil) then + error("Parent is nil? This shouldn't happen on add row button click! " .. setting_name) + end + + -- Delete the button and add a new row and then add the button back. + event.element.destroy() + SolidResourcesConfigDisplayRow(parent, nil, 0, 0) + + -- Add the add row button back. + SurfaceConfigSolidResourcesAddRowButton(parent) + + elseif (tags.fluid_resource_add_row_button) then + + local parent = event.element.parent + local setting_name = parent.tags.setting --[[@as string]] + + if (parent == nil) then + error("Parent is nil? This shouldn't happen on add row button click! " .. setting_name) + end + + -- Delete the button and add a new row and then add the button back. + event.element.destroy() + FluidResourcesConfigDisplayRow(parent, nil, 0, 0) + + -- Add the add row button back. + SurfaceConfigFluidResourcesAddRowButton(parent) + end +end \ No newline at end of file diff --git a/lib/oarc_gui_tabs.lua b/lib/oarc_gui_tabs.lua index 4703383..2e6f0a5 100644 --- a/lib/oarc_gui_tabs.lua +++ b/lib/oarc_gui_tabs.lua @@ -5,6 +5,8 @@ require("lib/gui_tabs/server_info") require("lib/gui_tabs/spawn_controls") require("lib/gui_tabs/settings_controls") require("lib/gui_tabs/mod_info_faq") +require("lib/gui_tabs/player_list") +require("lib/gui_tabs/surface_config") -------------------------------------------------------------------------------- -- GUI Tab Handler @@ -18,17 +20,23 @@ OARC_SERVER_INFO_TAB_NAME = "server_info" OARC_SPAWN_CTRL_TAB_NAME = "spawn_controls" OARC_CONFIG_CTRL_TAB_NAME = "settings" OARC_MOD_INFO_CTRL_TAB_NAME = "mod_info" +OARC_MOD_PLAYER_LIST_TAB_NAME = "player_list" +OARC_SURFACE_CONFIG_TAB_NAME = "surface_config" OARC_SERVER_INFO_TAB_LOCALIZED = {"oarc-server-info-tab-title"} OARC_SPAWN_CTRL_TAB_LOCALIZED = {"oarc-spawn-ctrls-tab-title"} OARC_CONFIG_CTRL_TAB_LOCALIZED = {"oarc-settings-tab-title"} OARC_MOD_INFO_CTRL_TAB_LOCALIZED = {"oarc-mod-info-tab-title"} +OARC_PLAYER_LIST_TAB_LOCALIZED = {"oarc-player-list-tab-title"} +OARC_SURFACE_CONFIG_TAB_LOCALIZED = {"oarc-surface-config-tab-title"} local OARC_GUI_TAB_CONTENT_FUNCTIONS = { [OARC_SERVER_INFO_TAB_NAME] = CreateServerInfoTab, [OARC_SPAWN_CTRL_TAB_NAME] = CreateSpawnControlsTab, [OARC_MOD_INFO_CTRL_TAB_NAME] = CreateModInfoTab, [OARC_CONFIG_CTRL_TAB_NAME] = CreateSettingsControlsTab, + [OARC_MOD_PLAYER_LIST_TAB_NAME] = CreatePlayerListTab, + [OARC_SURFACE_CONFIG_TAB_NAME] = CreateSurfaceConfigTab, } ---@param player LuaPlayer @@ -57,6 +65,16 @@ function InitOarcGuiTabs(player) AddOarcGuiTab(player, OARC_CONFIG_CTRL_TAB_NAME, OARC_CONFIG_CTRL_TAB_LOCALIZED) SetOarcGuiTabEnabled(player, OARC_CONFIG_CTRL_TAB_NAME, true) + -- Player list tab + AddOarcGuiTab(player, OARC_MOD_PLAYER_LIST_TAB_NAME, OARC_PLAYER_LIST_TAB_LOCALIZED) + SetOarcGuiTabEnabled(player, OARC_MOD_PLAYER_LIST_TAB_NAME, true) + + -- Surface config tab + if (player.admin) then + AddOarcGuiTab(player, OARC_SURFACE_CONFIG_TAB_NAME, OARC_SURFACE_CONFIG_TAB_LOCALIZED) + SetOarcGuiTabEnabled(player, OARC_SURFACE_CONFIG_TAB_NAME, true) + end + HideOarcGui(player) end @@ -142,12 +160,7 @@ function ClickOarcGuiButton(event) end end ----@param event EventData.on_gui_selected_tab_changed ----@return nil -function OarcGuiSelectedTabChanged(event) - if (event.element.name ~= "oarc_tabs") then return end - OarcGuiCreateContentOfTab(game.players[event.player_index]) -end + ---Set tab content to currently selected tab, clears all other tab content and refreshes the selected tab content! ---Safe to call just to refresh the current tab. @@ -298,9 +311,107 @@ function SwitchOarcGuiTab(player, tab_name) end end ---@param event EventData.on_gui_closed +---Completely destroys and recreates the OARC GUI for a player. +---@param player LuaPlayer +---@return nil +function RecreateOarcGui(player) + if (mod_gui.get_button_flow(player).oarc_button ~= nil) then + mod_gui.get_button_flow(player).oarc_button.destroy() + end + + if (mod_gui.get_frame_flow(player)[OARC_GUI] ~= nil) then + mod_gui.get_frame_flow(player)[OARC_GUI].destroy() + end + + InitOarcGuiTabs(player) +end + +--[[ + _____ _____ _ _ _____ _ _ _ _ _ ___ _ ___ ___ ___ + | __\ \ / / __| \| |_ _| | || | /_\ | \| | \| | | __| _ \/ __| + | _| \ V /| _|| .` | | | | __ |/ _ \| .` | |) | |__| _|| /\__ \ + |___| \_/ |___|_|\_| |_| |_||_/_/ \_\_|\_|___/|____|___|_|_\|___/ + +]] + +---Handles the closing of the OARC GUI. +---@param event EventData.on_gui_closed +---@return nil function OarcGuiClosed(event) if (event.element and (event.element.name == "oarc_gui")) then HideOarcGui(game.players[event.player_index]) end end + +---@param event EventData.on_gui_selected_tab_changed +---@return nil +function OarcGuiTabsSelectedTabChanged(event) + if (event.element.name ~= "oarc_tabs") then return end + OarcGuiCreateContentOfTab(game.players[event.player_index]) +end + +---All gui tabs click event handler +---@param event EventData.on_gui_click +---@return nil +function OarcGuiTabsClick(event) + if not event.element.valid then return end + ClickOarcGuiButton(event) + ServerInfoTabGuiClick(event) + SpawnCtrlTabGuiClick(event) + SettingsControlsTabGuiClick(event) + SettingsSurfaceControlsTabGuiClick(event) + PlayerListTabGuiClick(event) + SurfaceConfigTabGuiClick(event) +end + +---All gui tabs on_gui_checked_state_changed event handler +---@param event EventData.on_gui_checked_state_changed +---@return nil +function OarcGuiTabsCheckedStateChanged(event) + if not event.element.valid then return end + SpawnCtrlGuiOptionsCheckedStateChanged(event) +end + + +---Handles the `on_gui_value_changed` event. +---@param event EventData.on_gui_value_changed +---@return nil +function OarcGuiTabsValueChanged(event) + if not event.element.valid then return end + SettingsControlsTabGuiValueChanged(event) +end + +---Handles the `on_gui_selection_state_changed` event. +---@param event EventData.on_gui_selection_state_changed +---@return nil +function OarcGuiTabsSelectionStateChanged(event) + if not event.element.valid then return end + SettingsControlsTabGuiSelectionStateChanged(event) + SurfaceConfigTabGuiSelect(event) +end + +---Handles the `on_gui_text_changed` event. +---@param event EventData.on_gui_text_changed +---@return nil +function OarcGuiTabsTextChanged(event) + if not event.element.valid then return end + SettingsControlsTabGuiTextChanged(event) + SurfaceConfigTabGuiTextChanged(event) +end + +---Handles the `on_gui_confirmed` event. +---@param event EventData.on_gui_confirmed +---@return nil +function OarcGuiTabsConfirmed(event) + if not event.element.valid then return end + SettingsControlsTabGuiTextconfirmed(event) + SurfaceConfigTabGuiConfirmed(event) +end + +---Handles the `on_gui_elem_changed` event. +---@param event EventData.on_gui_elem_changed +---@return nil +function OarcGuiTabsElemChanged(event) + if not event.element.valid then return end + SurfaceConfigTabGuiElemChanged(event) +end diff --git a/lib/oarc_gui_utils.lua b/lib/oarc_gui_utils.lua index 4bef959..fff1801 100644 --- a/lib/oarc_gui_utils.lua +++ b/lib/oarc_gui_utils.lua @@ -58,6 +58,7 @@ my_note_style = { top_padding = 0, bottom_padding = 0 } + ---@type LuaStyle ---@diagnostic disable-next-line: missing-fields my_warning_style = { @@ -99,8 +100,8 @@ my_shared_item_list_fixed_width_style = { ---@diagnostic disable-next-line: missing-fields my_player_list_admin_style = { font = "default-semibold", - font_color = {r=1,g=0.5,b=0.5}, - minimal_width = 200, + font_color = { r=0.9, g=0.7, b=0.3 }, + -- minimal_width = 200, top_padding = 0, bottom_padding = 0, single_line = false, @@ -109,7 +110,7 @@ my_player_list_admin_style = { ---@diagnostic disable-next-line: missing-fields my_player_list_style = { font = "default-semibold", - minimal_width = 200, + -- minimal_width = 200, top_padding = 0, bottom_padding = 0, single_line = false, @@ -119,7 +120,7 @@ my_player_list_style = { my_player_list_offline_style = { -- font = "default-semibold", font_color = {r=0.5,g=0.5,b=0.5}, - minimal_width = 200, + -- minimal_width = 200, top_padding = 0, bottom_padding = 0, single_line = false, diff --git a/lib/oarc_tests.lua b/lib/oarc_tests.lua index a81351c..a3092a9 100644 --- a/lib/oarc_tests.lua +++ b/lib/oarc_tests.lua @@ -162,18 +162,6 @@ function ClearTestButtons(player) end end -function RecreateOarcGui(player) - if (mod_gui.get_button_flow(player).oarc_button ~= nil) then - mod_gui.get_button_flow(player).oarc_button.destroy() - end - - if (mod_gui.get_frame_flow(player)[OARC_GUI] ~= nil) then - mod_gui.get_frame_flow(player)[OARC_GUI].destroy() - end - - InitOarcGuiTabs(player) -end - function SetNauvisChunksGenerated() local nauvis = game.surfaces["nauvis"] diff --git a/lib/oarc_utils.lua b/lib/oarc_utils.lua index 0087f13..af669fd 100644 --- a/lib/oarc_utils.lua +++ b/lib/oarc_utils.lua @@ -208,12 +208,13 @@ end ---@return string function FormatTimeHoursSecs(ticks) local seconds = ticks / 60 - local minutes = math.floor((seconds)/60) - local hours = math.floor((minutes)/60) - local minutes = math.floor(minutes - 60*hours) + local total_minutes = math.floor((seconds)/60) + local hours = math.floor((total_minutes)/60) + local minutes = math.floor(total_minutes - 60*hours) return string.format("%dh:%02dm", hours, minutes) end + -- -- Simple math clamp -- function clamp(val, min, max) -- if (val > max) then @@ -1345,10 +1346,10 @@ function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile, m end end - -- Create a tree ring - if ((distSqr < tree_radius_sqr_outer) and (distSqr > tree_radius_sqr_inner)) then - surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) - end + -- -- Create a tree ring + -- if ((distSqr < tree_radius_sqr_outer) and (distSqr > tree_radius_sqr_inner)) then + -- surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) + -- end -- Fill moat with water. if (moat) then @@ -1357,12 +1358,27 @@ function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile, m -- land connections if the spawn is on or near land. elseif ((distSqr < moat_radius_sqr) and (distSqr > tile_radius_sqr)) then table.insert(dirtTiles, { name = "water", position = { i, j } }) + + --5% chance of fish in water + if (math.random(1,20) == 1) then + surface.create_entity({name="fish", position={i + 0.5, j + 0.5}}) + end end end end end surface.set_tiles(dirtTiles) + + --Create trees (needs to be done after setting tiles!) + for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do + for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do + local distSqr = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2) + if ((distSqr < tree_radius_sqr_outer) and (distSqr > tree_radius_sqr_inner)) then + surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) + end + end + end end ---` spawn shape (handles land, trees and moat) (Curtesy of jvmguy) @@ -1398,10 +1414,10 @@ function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile, end end - -- Create a tree ring - if ((distVar < tileRadius) and (distVar >= tree_distance_inner)) then - surface.create_entity({ name = "tree-01", amount = 1, position = { i, j } }) - end + -- -- Create a tree ring + -- if ((distVar < tileRadius) and (distVar >= tree_distance_inner)) then + -- surface.create_entity({ name = "tree-01", amount = 1, position = { i, j } }) + -- end -- Fill moat with water if (moat) then @@ -1410,11 +1426,29 @@ function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile, -- land connections if the spawn is on or near land. elseif ((distVar > tileRadius) and (distVar <= moat_width_outer)) then table.insert(dirtTiles, { name = "water", position = { i, j } }) + + --5% chance of fish in water + if (math.random(1,20) == 1) then + surface.create_entity({name="fish", position={i + 0.5, j + 0.5}}) + end end end end end surface.set_tiles(dirtTiles) + + --Create trees (needs to be done after setting tiles!) + for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do + for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do + local distVar1 = math.floor(math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j))) + local distVar2 = math.floor(math.abs(centerPos.x - i) + math.abs(centerPos.y - j)) + local distVar = math.max(distVar1, distVar2 * 0.707); + + if ((distVar < tileRadius) and (distVar >= tree_distance_inner)) then + surface.create_entity({ name = "tree-01", amount = 1, position = { i, j } }) + end + end + end end ---Square spawn shape (handles land, trees and moat) @@ -1449,10 +1483,10 @@ function CreateCropSquare(surface, centerPos, chunkArea, tileRadius, fillTile, m end end - -- Create a tree ring - if ((max_distance < tileRadius) and (max_distance >= tree_distance_inner)) then - surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) - end + -- -- Create a tree ring + -- if ((max_distance < tileRadius) and (max_distance >= tree_distance_inner)) then + -- surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) + -- end -- Fill moat with water if (moat) then @@ -1461,12 +1495,27 @@ function CreateCropSquare(surface, centerPos, chunkArea, tileRadius, fillTile, m -- land connections if the spawn is on or near land. elseif ((max_distance > tileRadius) and (max_distance <= moat_width_outer)) then table.insert(dirtTiles, { name = "water", position = { i, j } }) + + --5% chance of fish in water + if (math.random(1,20) == 1) then + surface.create_entity({name="fish", position={i + 0.5, j + 0.5}}) + end end end end end surface.set_tiles(dirtTiles) + + --Create trees (needs to be done after setting tiles!) + for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do + for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do + local max_distance = math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j)) + if ((max_distance < tileRadius) and (max_distance >= tree_distance_inner)) then + surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) + end + end + end end ---Add a circle of water diff --git a/lib/offline_protection.lua b/lib/offline_protection.lua index a303c41..ebc64fb 100644 --- a/lib/offline_protection.lua +++ b/lib/offline_protection.lua @@ -19,7 +19,7 @@ function OarcModifyEnemyGroup(event) -- Check validity if ((group == nil) or (group.command == nil) or not TableContains(ENEMY_FORCES_NAMES, group.force.name)) then - log("OarcModifyEnemyGroup ignoring INVALID group/command") + log("WARN - OarcModifyEnemyGroup ignoring INVALID group/command " .. serpent.block(group)) return end @@ -72,7 +72,7 @@ function OarcModifyEnemyGroup(event) -- This is unexpected, not sure under which conditions this would happen. if (group.command.type == defines.command.attack_area) then -- SendBroadcastMsg("OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!? " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, group.command.destination)) - log("ERROR - OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!?") + log("ERROR - OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!?" .. serpent.block(group)) -- for _,member in pairs(group.members) do -- member.destroy() -- end @@ -95,7 +95,7 @@ function OarcModifyEnemyGroup(event) -- I don't think this should happen ever... if ((target_player == nil) or (not target_player.valid)) then -- SendBroadcastMsg("ERROR?? target_player nil/invalid " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position)) - log("ERROR - OarcModifyEnemyGroup target_player nil/invalid?") + log("ERROR - OarcModifyEnemyGroup target_player nil/invalid?" .. serpent.block(group)) -- for _,member in pairs(group.members) do -- member.destroy() -- end @@ -125,6 +125,6 @@ function OarcModifyEnemyGroup(event) member.destroy() end -- SendBroadcastMsg("Enemy group deleted: " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name) - log("OarcModifyEnemyGroup REMOVED enemy group since nobody was online? " .. target_player.name) + -- log("OarcModifyEnemyGroup REMOVED enemy group since nobody was online? " .. target_player.name) end \ No newline at end of file diff --git a/lib/regrowth_map.lua b/lib/regrowth_map.lua index e82d078..d7a91c9 100644 --- a/lib/regrowth_map.lua +++ b/lib/regrowth_map.lua @@ -493,6 +493,7 @@ function OarcRegrowthRemoveAllChunks() -- If it is FORCE removal, then remove it regardless of pollution. if (c_remove.force) then game.surfaces[surface_name].delete_chunk(c_pos) + global.rg[surface_name].map[c_pos.x][c_pos.y] = nil elseif (global.rg[surface_name].map[c_pos.x][c_pos.y] == REGROWTH_FLAG_REMOVAL) then @@ -506,9 +507,12 @@ function OarcRegrowthRemoveAllChunks() global.rg[surface_name].map[c_pos.x][c_pos.y] = nil end end + + -- If we hit here, the chunk was probably refreshed or something and so we don't want to delete it. + -- We won't check it again since we clear the removal list after this. This should be correct. else -- This should never happen, TODO: check if it does? - error("ERROR - OarcRegrowthRemoveAllChunks: Chunk not in map: " .. c_pos.x .. "," .. c_pos.y .. " on surface: " .. surface_name) + log("WARN - OarcRegrowthRemoveAllChunks: Chunk not in map: " .. c_pos.x .. "," .. c_pos.y .. " on surface: " .. surface_name) end -- Remove entry diff --git a/lib/scaled_enemies.lua b/lib/scaled_enemies.lua index 311628a..3bcce0f 100644 --- a/lib/scaled_enemies.lua +++ b/lib/scaled_enemies.lua @@ -132,11 +132,11 @@ function DowngradeAndReduceEnemiesOnChunkGenerate(event) } -- Make chunks near a spawn safe by removing enemies - if (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.safe_radius) then + if (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.safe_radius * CHUNK_SIZE) then RemoveEnemiesInArea(surface, chunk_area) -- Create a warning area with heavily reduced enemies - elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.warn_radius) then + elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.warn_radius * CHUNK_SIZE) then -- TODO: Refactor this to reduce calls to find_entities_filtered! ReduceEnemiesInArea(surface, chunk_area, spawn_config.safe_area.warn_reduction) @@ -144,7 +144,7 @@ function DowngradeAndReduceEnemiesOnChunkGenerate(event) ConvertEnemiesToOtherForceInArea(surface, chunk_area, ENEMY_FORCE_EASY) -- Create a third area with moderately reduced enemies - elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.danger_radius) then + elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.danger_radius * CHUNK_SIZE) then -- TODO: Refactor this to reduce calls to find_entities_filtered! ReduceEnemiesInArea(surface, chunk_area, spawn_config.safe_area.danger_reduction) @@ -287,15 +287,15 @@ function ChangeEnemySpawnersToOtherForceOnBuilt(event) if (closest_spawn == nil) then return end -- No enemies inside safe radius! - if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius) then + if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius * CHUNK_SIZE) then event.entity.destroy() -- Warn distance should be EASY - elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius) then + elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius * CHUNK_SIZE) then event.entity.force = game.forces[ENEMY_FORCE_EASY] -- Danger distance should be MEDIUM - elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius) then + elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius * CHUNK_SIZE) then event.entity.force = game.forces[ENEMY_FORCE_MEDIUM] -- Otherwise make sure they are on the base enemy force (stops easy enemies from spreading out too far). @@ -328,11 +328,11 @@ function ModifyEnemySpawnsNearPlayerStartingAreas(event) end -- No enemies inside safe radius! - if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius) then + if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius * CHUNK_SIZE) then event.entity.destroy() -- Warn distance is all SMALL only. - elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius) then + elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius * CHUNK_SIZE) then if ((enemy_name == "biter-spawner") or (enemy_name == "spitter-spawner")) then event.entity.force = game.forces["enemy-easy"] @@ -351,7 +351,7 @@ function ModifyEnemySpawnsNearPlayerStartingAreas(event) end -- Danger distance is MEDIUM max. - elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius) then + elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius * CHUNK_SIZE) then if ((enemy_name == "big-biter") or (enemy_name == "behemoth-biter")) then event.entity.destroy() surface.create_entity { name = "medium-biter", position = enemy_pos, force = game.forces.enemy } diff --git a/lib/separate_spawns.lua b/lib/separate_spawns.lua index b7d7447..63035db 100644 --- a/lib/separate_spawns.lua +++ b/lib/separate_spawns.lua @@ -141,10 +141,10 @@ function SeparateSpawnsInitSurface(surface_name) global.oarc_surfaces[surface_name] = (surface_name == global.ocfg.gameplay.default_surface) end - -- Make sure it has a surface configuration entry - if (global.oarc_surfaces[surface_name] and global.ocfg.surfaces_config[surface_name] == nil) then + -- Make sure it has a surface configuration entry! + if (global.ocfg.surfaces_config[surface_name] == nil) then log("Surface does NOT have a config entry, defaulting to nauvis entry for new surface: " .. surface_name) - global.ocfg.surfaces_config[surface_name] = global.ocfg.surfaces_config["nauvis"] + global.ocfg.surfaces_config[surface_name] = table.deepcopy(global.ocfg.surfaces_config["nauvis"]) end end @@ -272,37 +272,62 @@ function GenerateStartingResources(surface, position) local amount_mod = global.ocfg.resource_placement.amount_multiplier -- Generate all resource tile patches - if (not global.ocfg.resource_placement.enabled) then - for r_name, r_data in pairs(global.ocfg.surfaces_config[surface.name].spawn_config.solid_resources --[[@as table]]) do - local pos = { x = position.x + r_data.x_offset, y = position.y + r_data.y_offset } - GenerateResourcePatch(surface, r_name, r_data.size * size_mod, pos, r_data.amount * amount_mod) - end - - -- Generate resources in random order around the spawn point. Tweak in config.lua - else - + -- Generate resources in random order around the spawn point. + if global.ocfg.resource_placement.enabled then if (global.ocfg.spawn_general.shape == SPAWN_SHAPE_CHOICE_CIRCLE) or (global.ocfg.spawn_general.shape == SPAWN_SHAPE_CHOICE_OCTAGON) then PlaceResourcesInSemiCircle(surface, position, size_mod, amount_mod) elseif (global.ocfg.spawn_general.shape == SPAWN_SHAPE_CHOICE_SQUARE) then PlaceResourcesInSquare(surface, position, size_mod, amount_mod) end + + -- Generate resources using specified offsets if auto placement is disabled. + else + for r_name, r_data in pairs(global.ocfg.surfaces_config[surface.name].spawn_config.solid_resources --[[@as table]]) do + local pos = { x = position.x + r_data.x_offset, y = position.y + r_data.y_offset } + GenerateResourcePatch(surface, r_name, r_data.size * size_mod, pos, r_data.amount * amount_mod) + end end -- Generate special fluid resource patches (oil) + -- Autoplace using spacing and vertical offset. -- Reference position is the bottom of the spawn area. - local fluid_ref_pos = { x = position.x, - y = position.y + global.ocfg.spawn_general.spawn_radius_tiles } - for r_name, r_data in pairs(global.ocfg.surfaces_config[surface.name].spawn_config.fluid_resources --[[@as table]]) do - local oil_patch_x = fluid_ref_pos.x + r_data.x_offset_start - local oil_patch_y = fluid_ref_pos.y + r_data.y_offset_start - for i = 1, r_data.num_patches do - surface.create_entity({ - name = r_name, - amount = r_data.amount, - position = { oil_patch_x, oil_patch_y } - }) - oil_patch_x = oil_patch_x + r_data.x_offset_next - oil_patch_y = oil_patch_y + r_data.y_offset_next + if global.ocfg.resource_placement.enabled then + local y_offset = global.ocfg.resource_placement.distance_to_edge + local spacing = 4 -- HARDCODED FLUID PATCH SPACING SIZE! + local fluid_ref_pos = { x = position.x, y = position.y + global.ocfg.spawn_general.spawn_radius_tiles - y_offset } + + for r_name, r_data in pairs(global.ocfg.surfaces_config[surface.name].spawn_config.fluid_resources --[[@as table]]) do + + local oil_patch_x = fluid_ref_pos.x - (((r_data.num_patches-1) * spacing) / 2) + local oil_patch_y = fluid_ref_pos.y + + for i = 1, r_data.num_patches do + surface.create_entity({ + name = "crude-oil", + amount = r_data.amount, + position = { oil_patch_x, oil_patch_y } + }) + oil_patch_x = oil_patch_x + spacing + end + + fluid_ref_pos.y = fluid_ref_pos.y - spacing + end + + -- This places using specified offsets if auto placement is disabled. + else + local fluid_ref_pos = { x = position.x, y = position.y + global.ocfg.spawn_general.spawn_radius_tiles } + for r_name, r_data in pairs(global.ocfg.surfaces_config[surface.name].spawn_config.fluid_resources --[[@as table]]) do + local oil_patch_x = fluid_ref_pos.x + r_data.x_offset_start + local oil_patch_y = fluid_ref_pos.y + r_data.y_offset_start + for i = 1, r_data.num_patches do + surface.create_entity({ + name = r_name, + amount = r_data.amount, + position = { oil_patch_x, oil_patch_y } + }) + oil_patch_x = oil_patch_x + r_data.x_offset_next + oil_patch_y = oil_patch_y + r_data.y_offset_next + end end end end @@ -329,7 +354,7 @@ function PlaceResourcesInSemiCircle(surface, position, size_mod, amount_mod) -- This places resources in a semi-circle local angle_offset = global.ocfg.resource_placement.angle_offset local num_resources = table_size(global.ocfg.surfaces_config[surface.name].spawn_config.solid_resources) - local theta = ((global.ocfg.resource_placement.angle_final - global.ocfg.resource_placement.angle_offset) / num_resources); + local theta = ((global.ocfg.resource_placement.angle_final - global.ocfg.resource_placement.angle_offset) / (num_resources-1)); local count = 0 local radius = global.ocfg.spawn_general.spawn_radius_tiles - global.ocfg.resource_placement.distance_to_edge @@ -392,7 +417,7 @@ function SendPlayerToNewSpawnAndCreateIt(delayed_spawn) local spawn_config = ocfg.surfaces_config[delayed_spawn.surface].spawn_config -- DOUBLE CHECK and make sure the area is super safe. - ClearNearbyEnemies(delayed_spawn.position, spawn_config.safe_area.safe_radius, + ClearNearbyEnemies(delayed_spawn.position, spawn_config.safe_area.safe_radius * CHUNK_SIZE, game.surfaces[delayed_spawn.surface]) -- Generate water strip only if we don't have a moat. @@ -624,7 +649,7 @@ function DowngradeResourcesDistanceBasedOnChunkGenerate(surface, chunkArea) local distance = util.distance(chunkArea.left_top, closestSpawn.position) -- Adjust multiplier to bring it in or out - local modifier = (distance / (global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius * 1)) ^ 3 + local modifier = (distance / (global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius * CHUNK_SIZE * 1)) ^ 3 if modifier < 0.1 then modifier = 0.1 end if modifier > 1 then return end @@ -737,7 +762,7 @@ function RemoveOrResetPlayer(player, remove_player) end end ----Searches all unique spawns for the primary one for a player. +---Searches all unique spawns for the primary one for a player. This will return null if they joined someeone else's spawn. ---@param player_name string ---@return OarcUniqueSpawn? function FindPrimaryUniqueSpawn(player_name) @@ -748,6 +773,19 @@ function FindPrimaryUniqueSpawn(player_name) end end +---Find the primary home spawn of a player, if one exists. It could be they joined a shared spawn. +---@param player_name string +---@return OarcUniqueSpawn? +function FindPlayerHomeSpawn(player_name) + for _,spawns in pairs(global.unique_spawns) do + for _,spawn in pairs(spawns) do + if (spawn.primary) and ((spawn.host_name == player_name) or TableContains(spawn.joiners, player_name)) then + return spawn + end + end + end +end + ---Searches all unique spawns for a list of secondary ones for a player. ---@param player_name string ---@return table -- Indexed by surface name! diff --git a/locale/en/locale.cfg b/locale/en/locale.cfg index 0335831..28f0788 100644 --- a/locale/en/locale.cfg +++ b/locale/en/locale.cfg @@ -181,6 +181,8 @@ oarc-server-info-tab-title=Server Info oarc-spawn-ctrls-tab-title=Spawn Controls oarc-settings-tab-title=Settings oarc-mod-info-tab-title=Mod Info +oarc-player-list-tab-title=Player List +oarc-surface-config-tab-title=Surface Config oarc-server-info-tab-welcome-msg-title=Welcome Message oarc-server-info-tab-discord-invite=Discord Invite: @@ -197,11 +199,24 @@ oarc-player-not-found=Player __1__ is not found? oarc-player-about-to-spawn=Player __1__ is about to spawn, try again later. oarc-player-none-selected=No player selected! +oarc-player-list-tab-column-header-player=Player +oarc-player-list-tab-column-header-force=Team (Force) +oarc-player-list-tab-column-header-surface=Home Surface +oarc-player-list-tab-column-header-time-player=Time Played +oarc-player-list-tab-column-header-location=Location +oarc-player-list-tab-column-header-status=Status + +oarc-player-online=Online +oarc-player-offline=Offline +oarc-player-waiting-to-spawn=Not Spawned Yet + +oarc-player-list-tab-location-button-tooltip=Click to view map location. + oarc-settings-tab-title-mod-settings=Mod Settings oarc-settings-tab-admin-warning=You are an admin. Changing these settings late in the game may cause issues!\nChanging settings will not modify existing spawns. BE CAREFUL! oarc-settings-tab-player-warning=You are not an admin. These settings are read-only for you. -oarc-settings-tab-description=This tab contains the same mod settings in the mod settings menu AND some additional settings that can only be changed in game. +oarc-settings-tab-description=This tab contains the same mod settings in the mod settings menu AND some additional settings that can only be changed in game. Check the Surface Config tab for surface-specific settings. oarc-settings-tab-text-field-enter-tooltip=[color=red]You must press ENTER after typing in a text field to save the value![/color] oarc-settings-tab-title-surface=Surface Settings oarc-settings-tab-surface-checkbox-tooltip=Enabling this will allow custom spawn areas (the main feature of this mod) on this surface. You need at least one of these enabled for the mod to work. @@ -209,7 +224,11 @@ oarc-settings-tab-surface-regrowth-checkbox-tooltip=Enabling this will allow the oarc-settings-tab-surface-column-header=Surface oarc-settings-tab-surface-spawning-enabled=Custom Spawns oarc-settings-tab-surface-regrowth-enabled=Regrowth - +oarc-settings-tab-title-export=Import/Export Settings +oarc-settings-tab-export-button=Export +oarc-settings-tab-export-button-tooltip=Exports all settings to a string that can be shared with other players or saved for later. +oarc-settings-tab-import-button=Import +oarc-settings-tab-import-button-tooltip=Imports settings from a string. This will overwrite all settings with the imported values! oarc-settings-section-header-server-info=Server Info oarc-settings-section-header-gameplay=Gameplay diff --git a/migrations/oarc-mod_V2.0.2.lua b/migrations/oarc-mod_V2.0.2.lua new file mode 100644 index 0000000..7f875bf --- /dev/null +++ b/migrations/oarc-mod_V2.0.2.lua @@ -0,0 +1,16 @@ +-- Migrate safe_radius, warn_radius and danger_radius to be chunks instead of tiles. +for surface_name, surface_config in pairs(global.ocfg.surfaces_config) do + + -- If the safe_area is greater than 3 chunks, then it's likely in chunks... + if surface_config.spawn_config.safe_area.safe_radius >= (3 * 32) then + log("Oarc-mod: Migrating safe_radius, warn_radius and danger_radius to be chunks instead of tiles.") + local safe_radius = surface_config.spawn_config.safe_area.safe_radius + global.ocfg.surfaces_config[surface_name].spawn_config.safe_area.safe_radius = math.ceil(safe_radius / 32) + + local warn_radius = surface_config.spawn_config.safe_area.warn_radius + global.ocfg.surfaces_config[surface_name].spawn_config.safe_area.warn_radius = math.ceil(warn_radius / 32) + + local danger_radius = surface_config.spawn_config.safe_area.danger_radius + global.ocfg.surfaces_config[surface_name].spawn_config.safe_area.danger_radius = math.ceil(danger_radius / 32) + end +end \ No newline at end of file diff --git a/settings.lua b/settings.lua index 7bcd7ff..83948ef 100644 --- a/settings.lua +++ b/settings.lua @@ -80,9 +80,9 @@ data:extend({ type = "int-setting", name = "oarc-mod-minimum-distance-to-existing-chunks", setting_type = "runtime-global", - default_value = 10, + default_value = 20, minimum_value = 5, - maximum_value = 25, + maximum_value = 50, order = "c1" }, { @@ -341,7 +341,7 @@ data:extend({ type = "double-setting", name = "oarc-mod-resource-placement-angle-offset", setting_type = "runtime-global", - default_value = 2.32, + default_value = 2.09, minimum_value = 0, maximum_value = 6.28, order = "i3" @@ -350,7 +350,7 @@ data:extend({ type = "double-setting", name = "oarc-mod-resource-placement-angle-final", setting_type = "runtime-global", - default_value = 4.46, + default_value = 4.18, minimum_value = 0, maximum_value = 6.28, order = "i4"