diff --git a/changelog.txt b/changelog.txt index a67ffcf..0c70b01 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,21 @@ --------------------------------------------------------------------------------------------------- +Version: 2.1.10 +Date: 2024-11-11 + Major Features: + - Adding support for secondary spawns on Fulgora. This is still VERY experimental and will likely have some hidden issues. Please report any bugs you find. If you want to use this feature, you must go into the in game GUI and check the enable secondary spawns checkbox for Fulgora specifically. + - Major work done under the hood to support secondary spawns. Depending on how Fulgora testing goes, I will add support for other planets soon. + Minor Features: + - Added some new commands: "oarc-wheres-my-cargo-pod", "oarc-reroll-spawn", "oarc-trigger-cleanup". See the in game help text for info. + - I now disable technology_notifications_enabled by default since it seems like this is highly requested. Only finished research is announced. I will expose settings for this later if needed. + - If you land on a planet where you have a custom spawn, but there is no landing-pad, you will be automatically teleported to your home spawn. + Info: + - Removed redundant "enable secondary spawns" setting. You just enable/disable per surface using the in game GUI now. + - Renamed "force grass" setting to "force tiles" since it can be used for more than just grass and change it to be default on. + - New config options (not shown in GUI) for configuring other planet/surface spawns. See planet_configs folder for examples. + Bugfixes: + - Fixed a crash if surface only had 1 basic-solid resource to place. + - Fix some ordering issues where enemies were removed after a spawn was placed resulting in gaps in the treeline. +--------------------------------------------------------------------------------------------------- Version: 2.1.9 Date: 2024-11-06 Bugfixes: diff --git a/control.lua b/control.lua index 76cf0c1..d0b40b9 100644 --- a/control.lua +++ b/control.lua @@ -36,6 +36,7 @@ require("lib/sharing") -- TODO: Possibly remove this later? require("lib/oarc_tests") +require("lib/oarc_commands") -------------------------------------------------------------------------------- @@ -69,6 +70,8 @@ script.on_init(function(event) for _,player in pairs(game.players) do SeparateSpawnsInitPlayer(player.index) end + + game.technology_notifications_enabled = false end) @@ -82,14 +85,9 @@ end) -------------------------------------------------------------------------------- -- On Configuration Changed - Only runs when the mod configuration changes -------------------------------------------------------------------------------- --- oarc_new_spawn_created = script.generate_event_name() - script.on_configuration_changed(function(data) - -- Regenerate event ID: - - -- Reset the players GUI for _,player in pairs(game.players) do - RecreateOarcGui(player) + RecreateOarcGui(player) -- Reset the players GUI end end) @@ -149,6 +147,12 @@ script.on_event(defines.events.on_player_driving_changed_state, function (event) end end) +script.on_event(defines.events.on_research_finished, function(event) + local research = event.research + -- TODO: Add a non-mod setting to disable this. + SendBroadcastMsg({"oarc-research-finished", research.force.name, research.name}) +end) + ---------------------------------------- -- CUSTOM OARC Events (shown here for demo and logging purposes) ---------------------------------------- @@ -161,33 +165,25 @@ end) ---@class OarcModOnSpawnCreatedEvent: OarcCustomEventBase ---@field spawn_data OarcUniqueSpawn script.on_event("oarc-mod-on-spawn-created", function(event) - log("Custom event oarc-mod-on-spawn-created") - log(serpent.block(event --[[@as OarcModOnSpawnCreatedEvent]])) + log("EVENT - oarc-mod-on-spawn-created:" .. serpent.block(event --[[@as OarcModOnSpawnCreatedEvent]])) end) ---@class OarcModOnSpawnRemoveRequestEvent: OarcCustomEventBase ---@field spawn_data OarcUniqueSpawn script.on_event("oarc-mod-on-spawn-remove-request", function(event) - log("Custom event oarc-mod-on-spawn-remove-request") - log(serpent.block(event --[[@as OarcModOnSpawnRemoveRequestEvent]])) + log("EVENT - oarc-mod-on-spawn-remove-request:" .. serpent.block(event --[[@as OarcModOnSpawnRemoveRequestEvent]])) end) ---@class OarcModOnPlayerResetEvent: OarcCustomEventBase ---@field player_index integer script.on_event("oarc-mod-on-player-reset", function(event) - log("Custom event oarc-mod-on-player-reset") - log(serpent.block(event --[[@as OarcModOnPlayerResetEvent]])) - if (game.players[event.player_index]) then - log("Player is still valid: " .. game.players[event.player_index].name) - end + log("EVENT - oarc-mod-on-player-reset:" .. serpent.block(event --[[@as OarcModOnPlayerResetEvent]])) end) ---@class OarcModOnPlayerSpawnedEvent: OarcCustomEventBase ---@field player_index integer script.on_event("oarc-mod-on-player-spawned", function(event) - log("Custom event oarc-mod-on-player-spawned") - log(serpent.block(event --[[@as OarcModOnPlayerSpawnedEvent]])) - log("Player spawned: " .. game.players[event.player_index].name) + log("EVENT - oarc-mod-on-player-spawned:" .. serpent.block(event --[[@as OarcModOnPlayerSpawnedEvent]])) end) ---@class OarcModCharacterSurfaceChangedEvent: OarcCustomEventBase @@ -195,11 +191,14 @@ end) ---@field old_surface_name string ---@field new_surface_name string script.on_event("oarc-mod-character-surface-changed", function(event) - log("Custom event oarc-mod-character-surface-changed") - log(serpent.block(event --[[@as OarcModCharacterSurfaceChangedEvent]])) + log("EVENT - oarc-mod-character-surface-changed:" .. serpent.block(event --[[@as OarcModCharacterSurfaceChangedEvent]])) + + --This is just here so I don't get lua warnings about unused variables. + ---@type OarcModCharacterSurfaceChangedEvent + local custom_event = event --[[@as OarcModCharacterSurfaceChangedEvent]] - local player = game.players[event.player_index] - SeparateSpawnsPlayerChangedSurface(player, event.old_surface_name, event.new_surface_name) + local player = game.players[custom_event.player_index] + SeparateSpawnsPlayerChangedSurface(player, custom_event.old_surface_name, custom_event.new_surface_name) end) -- I raise this event whenever teleporting the player! @@ -232,6 +231,7 @@ end) script.on_event(defines.events.on_tick, function(event) DelayedSpawnOnTick() FadeoutRenderOnTick() + OnTickNilCharacterTeleportQueue() if storage.ocfg.regrowth.enable_regrowth then RegrowthOnTick() @@ -248,12 +248,13 @@ script.on_event(defines.events.on_chunk_generated, function(event) end CreateHoldingPenChunks(event) - SeparateSpawnsGenerateChunk(event) if storage.ocfg.gameplay.modified_enemy_spawning then DowngradeWormsDistanceBasedOnChunkGenerate(event) DowngradeAndReduceEnemiesOnChunkGenerate(event) end + + SeparateSpawnsGenerateChunk(event) end) ---------------------------------------- diff --git a/devplan.txt b/devplan.txt index 4c9cdf0..34b33ee 100644 --- a/devplan.txt +++ b/devplan.txt @@ -1,20 +1,20 @@ -ACTIVE ITEMS: +ACTIVE NOTES: --- Look into on_player_driving_changed_state for detecting player rocket/platform transitions. --- Look at rocket launch events and this: `event.rocket.cargo_pod` ------------------------------------------------------------------------------------------------------------------------- +- What happens to multiple unique spawn hosts on "main force" when they go to a new planet? +- They get unique spawn areas, but they have to coordinate regarding the landing pad, OR they can install a mod to have multiple but it is still up to them to coordinate. -BACKLOG: +- Change permissions to be enabled/disabled when entering/leaving the holding pen (use custom surface changed event?) -Not specific: +- Get rid of storage.buddy_pairs ?? Is basically duplicate info inside the spawns right? +------------------------------------------------------------------------------------------------------------------------ +BACKLOG: Minor: - If dead when resetting spawn... possibly delay the opening of the welcome GUI or block spawning until character is spawned? - Expose old enemy scaling as an option? And/or remove unnecessary checks/logs - Refresh players in admin controls when dropdown is clicked -- Add a setting for forcing primary spawns to only be on default surface maybe? Performance: - User on_nth_tick for any tick % (modulo) operations. @@ -22,7 +22,7 @@ Performance: - Rework world eater to use less find_entities_filtered Major: -- Space Age Support (TBD) +- Space Age Support (In Progress) ------------------------------------------------------SPACE AGE--------------------------------------------------------- @@ -31,12 +31,7 @@ Major: - Pollution changes (regrowth)? - Enemy changes? - Landing pad locations per FORCE limited to 1? -- Surface names for space ships? - Spawner health tied to evolution? -- Respawn position is surface specific? Each surface needs a separate respawn point? Default respawn behavior? -- Confirm launch into scenario works (V2.0 fix supposedly) -- https://forums.factorio.com/110708 -- 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: @@ -47,9 +42,6 @@ Added space-platform-starter-pack, space-location, planet and space-connection p 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. @@ -58,24 +50,10 @@ 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. ------------------------------------------------------------------------------------------------------------------------ @@ -91,7 +69,6 @@ Other Ideas, Not Committed: - Change enable_shared_team_vision to allow players to change this per player (like BNO) - Change enable_friendly_fire to be per team? - Allow players to spawn "near" an existing player (by request) -- Allow players to restart at anytime via GUI button (configurable setting by admin) - Change regrowth to be list of surfaces indexed by surface name? - Figure out how to reset player inventory on player reset to avoid extra items? (save and load items?) - Work on space ex support? @@ -182,4 +159,5 @@ Other Ideas, Not Committed: - Pull out general spawn config from surfaces config - Redo resource placement to be simpler (and make a linear layout for square base) - Default to selecting SELF in admin controls player dropdown? -- Add refresh chunks around spidertrons based on their vision \ No newline at end of file +- Add refresh chunks around spidertrons based on their vision +- Allow players to restart at anytime via GUI button (configurable setting by admin) \ No newline at end of file diff --git a/example/example-map-gen-settings.json b/example/example-map-gen-settings.json index ab0693e..8f5dd59 100644 --- a/example/example-map-gen-settings.json +++ b/example/example-map-gen-settings.json @@ -10,15 +10,15 @@ "autoplace_controls": { - "coal" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20}, - "iron-ore" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20}, - "copper-ore" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20}, - "stone" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20}, - "uranium-ore" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20}, - "crude-oil" : {"frequency" : 0.20, "richness" : 10.00, "size" : 0.20}, + "coal" : {"frequency" : 1.00, "richness" : 1.00, "size" : 1.00}, + "iron-ore" : {"frequency" : 1.00, "richness" : 1.00, "size" : 1.00}, + "copper-ore" : {"frequency" : 1.00, "richness" : 1.00, "size" : 1.00}, + "stone" : {"frequency" : 1.00, "richness" : 1.00, "size" : 1.00}, + "uranium-ore" : {"frequency" : 1.00, "richness" : 1.00, "size" : 1.00}, + "crude-oil" : {"frequency" : 1.00, "richness" : 1.00, "size" : 1.00}, "water" : {"frequency": 1, "size": 1}, "trees" : {"frequency": 1, "size": 1}, - "enemy-base" : {"frequency": 0.4, "size": 0.5} + "enemy-base" : {"frequency": 0.5, "size": 0.5} }, "cliff_settings": diff --git a/info.json b/info.json index 5475ce9..706cad6 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { "name": "oarc-mod", - "version": "2.1.9", + "version": "2.1.10", "factorio_version": "2.0", "title": "Oarc Multiplayer Spawn", "author": "Oarcinae", diff --git a/lib/config.lua b/lib/config.lua index f415383..7b38f2c 100644 --- a/lib/config.lua +++ b/lib/config.lua @@ -15,6 +15,8 @@ ]] +require("lib/planet_configs/nauvis") +require("lib/planet_configs/fulgora") ---@alias SpawnShapeChoice "circle" | "octagon" | "square" SPAWN_SHAPE_CHOICE_CIRCLE = "circle" @@ -28,148 +30,6 @@ 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 = -{ - player_start_items = { - ["iron-plate"] = 8, - ["wood"] = 1, - ["pistol"] = 1, - ["firearm-magazine"] = 10, - ["burner-mining-drill"] = 1, - ["stone-furnace"] = 1 - }, - player_respawn_items = { - ["pistol"] = 1, - ["firearm-magazine"] = 10 - }, - - crashed_ship = true, - crashed_ship_resources = { - ["firearm-magazine"] = 8 -- Max of 5 inventory slots! - }, - crashed_ship_wreakage = { - ["iron-plate"] = 8 -- I don't recommend more than 1 item type here! - }, -} - --- THIS is used as the default spawn config on all surfaces if no other settings are provided! ----@type OarcConfigSpawn -NAUVIS_SPAWN_CONFIG = -{ - -- Safe Spawn Area Options - -- The default settings here are balanced for my recommended map gen settings (close to train world). - safe_area = - { - -- Safe area has no aliens - -- This is the radius in chunks of safe area. - safe_radius = 6, - - -- Warning area has significantly reduced aliens - -- 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 chunks of danger area. - danger_radius = 32, - - -- 1 : X (spawners alive : spawners destroyed) in this area - danger_reduction = 5, - }, - - -- Location of water strip within the spawn area (2 horizontal rows) - -- The offset is from the TOP (NORTH) of the spawn area. - water = { - x_offset = -4, - y_offset = 10, - length = 8, - }, - - -- Location of shared power pole within the spawn area (if enabled) - -- The offset is from the RIGHT (WEST) of the spawn area. - shared_power_pole_position = { - x_offset=-10, - y_offset=0 - }, - - -- Location of shared chest within the spawn area (if enabled) - -- The offset is from the RIGHT (WEST) of the spawn area. - shared_chest_position = { - x_offset=-10, - y_offset=1 - }, - - -- Solid resource tiles - -- If you are running with mods that add or change resources, you'll want to customize this. - -- Offsets only are applicable if auto placement is disabled. Offsets are from CENTER of spawn area. - solid_resources = { - ["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 - } - }, - - -- Fluid resource patches like oil - -- If you are running with mods that add or change resources, you'll want to customize this. - -- The offset is from the BOTTOM (SOUTH) of the spawn area. - fluid_resources = - { - ["crude-oil"] = - { - num_patches = 2, - amount = 900000, - spacing = 6, -- Spacing between each patch, only used for automatic placing. - - -- 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, - -- Additional position offsets for each new oil patch (relative to previous oil patch) - x_offset_next = 6, - y_offset_next = 0 - } - }, -} - ----@type OarcConfigSurface -NAUVIS_SURFACE_CONFIG = -{ - starting_items = NAUVIS_STARTER_ITEMS, - spawn_config = NAUVIS_SPAWN_CONFIG -} - ---This only matters if you have the coin shop enabled. ---@type OarcStoreList OARC_SHOP_ITEMS = @@ -295,12 +155,13 @@ OCFG = { -- General gameplay related settings that I didn't want to expose in the mod settings since these should -- basically always be enabled unless you're making serious changes. + ---@type OarcConfigGameplaySettings gameplay = { - -- Default setting for enabling spawning on other surfaces other than the default_surface. + -- Default setting for if secondary spawns are enabled on other surfaces other than the default_surface. -- This is a STARTUP setting, so it can't be changed in game!! -- This is a STARTUP setting, so it can't be changed in game!! - default_allow_spawning_on_other_surfaces = false, + default_enable_secondary_spawns_on_other_surfaces = false, -- The name of the main force. -- This is a STARTUP setting, so it can't be changed in game!! @@ -363,10 +224,6 @@ OCFG = { -- The default starting surface. default_surface = "nauvis", - -- Enable secondary spawns for players. - -- This automatically creates a new spawn point when they first move to a separate spawns enabled surface. - enable_secondary_spawns = false, - -- This scales resources so that even if you spawn "far away" from the center -- of the map, resources near to your spawn point scale so you aren't -- surrounded by 100M patches or something. This is useful depending on what @@ -410,6 +267,7 @@ OCFG = { }, -- This is a separate feature that is part of the mod that helps keep the map size down. Not required but useful. + ---@type OarcConfigRegrowth regrowth = { -- Cleans up unused chunks periodically. Helps keep map size down. -- See description in regrowth_map.lua for more details. @@ -429,6 +287,7 @@ OCFG = { }, -- General spawn settings (size, shape, etc.) + ---@type OarcConfigSpawnGeneral spawn_general = { -- Create a circle of land area for the spawn @@ -447,15 +306,16 @@ OCFG = { -- Starting resources deposits shape. resources_shape = RESOURCES_SHAPE_CHOICE_CIRCLE, - -- Force the land area circle at the spawn to be fully grass, otherwise it defaults to the existing terrain - -- or uses landfill. - force_grass = false, + -- Force the land area circle at the spawn to be a single tile (default grass on Nauvis), otherwise it defaults + -- to the existing terrain and uses landfill to fill gaps. + force_tiles = true, -- Spawn a circle/octagon/square of trees around this base outline. shape = SPAWN_SHAPE_CHOICE_CIRCLE, }, -- Handle placement of starting resources within the spawn area. + ---@type OarcConfigSpawnResourcePlacementSettings resource_placement = { -- Autoplace resources (randomly in circle) @@ -507,10 +367,10 @@ OCFG = { -- starting_items = NAUVIS_STARTER_ITEMS, -- spawn_config = NAUVIS_SPAWN_CONFIG -- }, - -- ["fulgora"] = { - -- starting_items = NAUVIS_STARTER_ITEMS, - -- spawn_config = NAUVIS_SPAWN_CONFIG - -- }, + ["fulgora"] = { + starting_items = FULGORA_STARTER_ITEMS, + spawn_config = FULGORA_SPAWN_CONFIG + }, -- ["gleba"] = { -- starting_items = NAUVIS_STARTER_ITEMS, -- spawn_config = NAUVIS_SPAWN_CONFIG @@ -591,7 +451,7 @@ OCFG = { ---@field discord_invite string Discord invite for easy copy paste. ---@class OarcConfigGameplaySettings ----@field default_allow_spawning_on_other_surfaces boolean Default setting for enabling spawning on other surfaces other than the default_surface. This is a STARTUP setting, so it can't be changed in game. +---@field default_enable_secondary_spawns_on_other_surfaces boolean Default setting for if secondary spawns are enabled on other surfaces other than the default_surface. This is a STARTUP setting, so it can't be changed in game. ---@field main_force_name string The name of the main force. This is a STARTUP setting, so it can't be changed in game. ---@field enable_main_team boolean Allows all players to join a primary force(team). ---@field enable_separate_teams boolean Allows players to create their own force(team). @@ -610,7 +470,6 @@ OCFG = { ---@field number_of_players_per_shared_spawn number Number of players allowed to join a shared spawn. ---@field enable_friendly_fire boolean Set to true if you want to shoot your own chests and stuff. ---@field default_surface string The starting surface of the main force. ----@field enable_secondary_spawns boolean Enable secondary spawns for players. This automatically creates a new spawn point when they first move to a separate spawns enabled surface. ---@field scale_resources_around_spawns boolean Scales resources so that even if you spawn "far away" from the center of the map, resources near to your spawn point scale so you aren't surrounded by 100M patches or something. This is useful depending on what map gen settings you pick. ---@field modified_enemy_spawning boolean Adjust enemy spawning based on distance to spawns. All it does it make things more balanced based on your distance and makes the game a little easier. No behemoth worms everywhere just because you spawned far away. ---@field scale_spawner_damage boolean Scale damage to spawners based on distance to spawn. @@ -641,6 +500,11 @@ OCFG = { ---@field player_respawn_items table Items provided after EVERY respawn (disabled by default) ---@class OarcConfigSpawn +---@field fill_tile string Fill tile for the spawn area (grass on Nauvis) +---@field liquid_tile string Moat and liquid strip for the spawn area (water on Nauvis) +---@field tree_entity string Tree ring entity for the spawn area (tree-02 on Nauvis) +---@field random_entities table Random entities to place around the spawn area (like rocks on nauvis, ruins on fulgora) +---@field radius_modifier number Radius modifier for the spawn area (1.0 is default) ---@field safe_area OarcConfigSpawnSafeArea How safe is the spawn area? ---@field water OarcConfigSpawnWater Water strip settings ---@field shared_power_pole_position OarcOffsetPosition Location of shared power pole relative to spawn center (if enabled) @@ -653,7 +517,7 @@ OCFG = { ---@field moat_width_tiles number Width of the moat around the spawn area. If you change the spawn area size, you might have to adjust this as well. ---@field tree_width_tiles number Width of the tree ring around the spawn area. If you change the spawn area size, you might have to adjust this as well. ---@field resources_shape SpawnResourcesShapeChoice The starting resources deposits shape. ----@field force_grass boolean Force the land area circle at the spawn to be fully grass, otherwise it defaults to the existing terrain. +---@field force_tiles boolean Force the land area circle at the spawn to be a single tile (default grass on Nauvis), otherwise it defaults to the existing terrain and uses landfill to fill gaps. ---@field shape SpawnShapeChoice Spawn a circle/octagon/square of trees around this base outline. ---@class OarcConfigSpawnSafeArea diff --git a/lib/config_parser.lua b/lib/config_parser.lua index d4622e1..75fa894 100644 --- a/lib/config_parser.lua +++ b/lib/config_parser.lua @@ -16,7 +16,7 @@ OCFG_KEYS = ["gameplay_spawn_choices_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-spawn-choices"}}, ["gameplay.enable_main_team"] = {mod_key = "oarc-mod-enable-main-team" , ocfg_keys = {"gameplay", "enable_main_team"}, type = "boolean"}, ["gameplay.enable_separate_teams"] = {mod_key = "oarc-mod-enable-separate-teams" , ocfg_keys = {"gameplay", "enable_separate_teams"}, type = "boolean"}, - -- STARTUP ["gameplay.enable_spawning_on_other_surfaces"] = {mod_key = "oarc-mod-default-allow-spawning-on-other-surfaces" , ocfg_keys = {"gameplay", "enable_spawning_on_other_surfaces"}, type = "boolean"}, + -- STARTUP ["gameplay.default_enable_secondary_spawns_on_other_surfaces"] = {mod_key = "oarc-mod-default-enable-secondary-spawns-on-other-surfaces" , ocfg_keys = {"gameplay", "default_enable_secondary_spawns_on_other_surfaces"}, type = "boolean"}, ["gameplay.allow_moats_around_spawns"] = {mod_key = "oarc-mod-allow-moats-around-spawns" , ocfg_keys = {"gameplay", "allow_moats_around_spawns"}, type = "boolean"}, ["gameplay.enable_moat_bridging"] = {mod_key = "oarc-mod-enable-moat-bridging" , ocfg_keys = {"gameplay", "enable_moat_bridging"}, type = "boolean"}, ["gameplay.minimum_distance_to_existing_chunks"] = {mod_key = "oarc-mod-minimum-distance-to-existing-chunks" , ocfg_keys = {"gameplay", "minimum_distance_to_existing_chunks"}, type = "integer"}, @@ -26,15 +26,12 @@ OCFG_KEYS = ["gameplay.enable_shared_spawns"] = {mod_key = "oarc-mod-enable-shared-spawns" , ocfg_keys = {"gameplay", "enable_shared_spawns"}, type = "boolean"}, ["gameplay.number_of_players_per_shared_spawn"] = {mod_key = "oarc-mod-number-of-players-per-shared-spawn" , ocfg_keys = {"gameplay", "number_of_players_per_shared_spawn"}, type = "integer"}, ["gameplay.default_surface"] = {mod_key = "oarc-mod-default-surface" , ocfg_keys = {"gameplay", "default_surface"}, type = "string"}, - ["gameplay.enable_secondary_spawns"] = {mod_key = "oarc-mod-enable-secondary-spawns" , ocfg_keys = {"gameplay", "enable_secondary_spawns"}, type = "boolean"}, -- STARTUP ["gameplay.main_force_name"] = {mod_key = "oarc-mod-main-force-name" , ocfg_keys = {"gameplay", "main_force_name"}, type = "string"}, ["gameplay_difficulty_scaling_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-difficulty-scaling"}}, ["gameplay.enable_offline_protection"] = {mod_key = "oarc-mod-enable-offline-protection" , ocfg_keys = {"gameplay", "enable_offline_protection"}, type = "boolean"}, ["gameplay.scale_resources_around_spawns"] = {mod_key = "oarc-mod-scale-resources-around-spawns" , ocfg_keys = {"gameplay", "scale_resources_around_spawns"}, type = "boolean"}, ["gameplay.modified_enemy_spawning"] = {mod_key = "oarc-mod-modified-enemy-spawning" , ocfg_keys = {"gameplay", "modified_enemy_spawning"}, type = "boolean"}, - -- ["gameplay.modified_enemy_easy_evo"] = {mod_key = "oarc-mod-modified-enemy-easy-evo" , ocfg_keys = {"gameplay", "modified_enemy_easy_evo"}, type = "double"}, - -- ["gameplay.modified_enemy_medium_evo"] = {mod_key = "oarc-mod-modified-enemy-medium-evo" , ocfg_keys = {"gameplay", "modified_enemy_medium_evo"}, type = "double"}, ["gameplay_misc_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-gameplay-misc"}}, ["gameplay.enable_friendly_fire"] = {mod_key = "oarc-mod-enable-friendly-fire" , ocfg_keys = {"gameplay", "enable_friendly_fire"}, type = "boolean"}, @@ -62,7 +59,7 @@ OCFG_KEYS = ["spawn_general.moat_width_tiles"] = {mod_key = "oarc-mod-spawn-general-moat-width-tiles" , ocfg_keys = {"spawn_general", "moat_width_tiles"}, type = "integer"}, ["spawn_general.tree_width_tiles"] = {mod_key = "oarc-mod-spawn-general-tree-width-tiles" , ocfg_keys = {"spawn_general", "tree_width_tiles"}, type = "integer"}, ["spawn_general.resources_shape"] = {mod_key = "oarc-mod-spawn-general-enable-resources-circle-shape" , ocfg_keys = {"spawn_general", "resources_shape"}, type = "string-list"}, - ["spawn_general.force_grass"] = {mod_key = "oarc-mod-spawn-general-enable-force-grass" , ocfg_keys = {"spawn_general", "force_grass"}, type = "boolean"}, + ["spawn_general.force_tiles"] = {mod_key = "oarc-mod-spawn-general-enable-force-tiles" , ocfg_keys = {"spawn_general", "force_tiles"}, type = "boolean"}, ["spawn_general.shape"] = {mod_key = "oarc-mod-spawn-general-shape" , ocfg_keys = {"spawn_general", "shape"}, type = "string-list"}, ["resource_placement_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-resource-placement"}}, @@ -265,7 +262,7 @@ function CacheModSettings() end -- Special case for startup settings - storage.ocfg.gameplay.default_allow_spawning_on_other_surfaces = settings.startup["oarc-mod-default-allow-spawning-on-other-surfaces"].value --[[@as boolean]] + storage.ocfg.gameplay.default_enable_secondary_spawns_on_other_surfaces = settings.startup["oarc-mod-default-enable-secondary-spawns-on-other-surfaces"].value --[[@as boolean]] storage.ocfg.gameplay.main_force_name = settings.startup["oarc-mod-main-force-name"].value --[[@as string]] end diff --git a/lib/gui_tabs/spawn_controls.lua b/lib/gui_tabs/spawn_controls.lua index dcbe858..79bed19 100644 --- a/lib/gui_tabs/spawn_controls.lua +++ b/lib/gui_tabs/spawn_controls.lua @@ -526,17 +526,27 @@ function SpawnCtrlTabGuiClick(event) game.players[join_queue_player_choice].gui.screen.join_shared_spawn_wait_menu.destroy() end - -- Spawn the player local joining_player = game.players[join_queue_player_choice] - SetPlayerRespawn(joining_player.name, primary_spawn.surface_name, primary_spawn.position, true) - SendPlayerToSpawn(primary_spawn.surface_name, joining_player, true) - script.raise_event("oarc-mod-on-player-spawned", {player_index = joining_player.index}) - GivePlayerStarterItems(joining_player) - table.insert(storage.unique_spawns[primary_spawn.surface_name][player.name].joiners, joining_player.name) + local joining_player_name = joining_player.name + + -- Assign force joining_player.force = game.players[player.name].force + -- Add the player to ALL spawns owned by the host. + for surface_name, unique_spawn_entry in pairs(storage.unique_spawns) do + for player_name, unique_spawn in pairs(unique_spawn_entry) do + if (player_name == player.name) then + table.insert(unique_spawn.joiners, joining_player_name) + SetPlayerRespawn(joining_player_name, surface_name, unique_spawn.position, true) + end + end + end + + -- Send player to the host's primary spawn. + SendPlayerToNewSpawn(joining_player_name, primary_spawn.surface_name, true) + -- Render some welcoming text... - DisplayWelcomeGroundTextAtSpawn(joining_player, primary_spawn.surface_name, primary_spawn.position) + DisplayWelcomeGroundTextAtSpawn(primary_spawn.surface_name, primary_spawn.position) -- Unlock spawn control gui tab SetOarcGuiTabEnabled(joining_player, OARC_SPAWN_CTRL_TAB_NAME, true) diff --git a/lib/gui_tabs/surface_config.lua b/lib/gui_tabs/surface_config.lua index 6ed9c0b..1aac8e6 100644 --- a/lib/gui_tabs/surface_config.lua +++ b/lib/gui_tabs/surface_config.lua @@ -461,10 +461,10 @@ function CreateMiscConfig(container, surface_name) AddLabel(misc_flow, nil, { "oarc-misc-config" }, my_label_header2_style) - AddLabel(misc_flow, nil, { "oarc-water" }, my_player_list_style) - CreateSpawnConfigIntegerField(misc_flow, surface_name, { "oarc-water-length" }, "water", "length", { "oarc-water-length-tooltip" }) - CreateSpawnConfigIntegerField(misc_flow, surface_name, { "oarc-water-x-offset" }, "water", "x_offset", { "oarc-water-x-offset-tooltip" }) - CreateSpawnConfigIntegerField(misc_flow, surface_name, { "oarc-water-y-offset" }, "water", "y_offset", { "oarc-water-y-offset-tooltip" }) + AddLabel(misc_flow, nil, { "oarc-liquid" }, my_player_list_style) + CreateSpawnConfigIntegerField(misc_flow, surface_name, { "oarc-liquid-length" }, "water", "length", { "oarc-liquid-length-tooltip" }) + CreateSpawnConfigIntegerField(misc_flow, surface_name, { "oarc-liquid-x-offset" }, "water", "x_offset", { "oarc-liquid-x-offset-tooltip" }) + CreateSpawnConfigIntegerField(misc_flow, surface_name, { "oarc-liquid-y-offset" }, "water", "y_offset", { "oarc-liquid-y-offset-tooltip" }) AddSpacerLine(misc_flow) AddLabel(misc_flow, nil, { "oarc-shared-chest" }, my_player_list_style) diff --git a/lib/holding_pen.lua b/lib/holding_pen.lua index b438b22..846bfc1 100644 --- a/lib/holding_pen.lua +++ b/lib/holding_pen.lua @@ -86,8 +86,8 @@ function CreateHoldingPenChunks(event) PlaceResourcesInSemiCircleHoldingPen(surface, {x=0,y=0}, 0.2, 0.1) - CreateWaterStrip(surface, {x=-2,y=-11}, 4) - CreateWaterStrip(surface, {x=-2,y=-10}, 4) + CreateTileStrip(surface, {x=-2,y=-11}, 4, "water") + CreateTileStrip(surface, {x=-2,y=-10}, 4, "water") surface.create_entity({ name = "crude-oil", diff --git a/lib/oarc_commands.lua b/lib/oarc_commands.lua new file mode 100644 index 0000000..d8fb6a4 --- /dev/null +++ b/lib/oarc_commands.lua @@ -0,0 +1,17 @@ +-- Add a command to let people call droppods to themselves +commands.add_command("oarc-wheres-my-cargo-pod", {"oarc-command-dude-wheres-my-cargo-pod"}, function(command) + if command.player_index == nil then return end -- Ignore if it's not a player + DudeWheresMyCargoPod(game.players[command.player_index]) +end) + +-- Allow spawn rerolls +commands.add_command("oarc-reroll-spawn", {"oarc-command-reroll-spawn"}, function(command) + if command.player_index == nil then return end -- Ignore if it's not a player + RerollSpawn(game.players[command.player_index]) +end) + +-- Trigger immediate regrowth cleanups +commands.add_command("oarc-trigger-cleanup", {"oarc-command-trigger-cleanup"}, function(command) + if command.player_index ~= nil and not game.get_player(command.player_index).admin then return end -- Only admins can call this. + TriggerCleanup() +end) \ No newline at end of file diff --git a/lib/oarc_tests.lua b/lib/oarc_tests.lua index db87e20..91c40c7 100644 --- a/lib/oarc_tests.lua +++ b/lib/oarc_tests.lua @@ -203,7 +203,8 @@ end ---Searchs a 3x3 chunk around the map origin for "cargo-pod-container" entities and if they are on the same force ----as the player it will teleport the cargo pod to the player. +---as the player it will teleport the cargo pod to the player. This is meant to be a temporary work around since I don't +---know how to detect the cargo pod that is associated with the player or when it is sent/landed. ---@param player LuaPlayer ---@return nil function DudeWheresMyCargoPod(player) @@ -219,16 +220,95 @@ function DudeWheresMyCargoPod(player) local pods = surface.find_entities_filtered{area=search_area, name="cargo-pod-container", force=player.force} + if #pods == 0 then + player.print({ "oarc-no-cargo-pods" }) + return + end + for _,cargo_pod in pairs(pods) do local new_position = surface.find_non_colliding_position("cargo-pod-container", player.character.position, CHUNK_SIZE, 1) if new_position == nil then - player.print({ "oarc-teleport-fail" }) + player.print({ "oarc-teleport-cargo-pod-fail" }) return end cargo_pod.teleport(new_position) - player.print({ "oarc-teleport-success" }) + player.print({ "oarc-teleport-cargo-pod-success" }) + end +end + + + +---Allow players to reroll their spawn point, dangerous because this doesn't do a lot of checks. +---@param player LuaPlayer +---@return nil +function RerollSpawn(player) + + -- Make sure character is valid + if not player.character then + log("ERROR - RerollSpawn - No valid character for player: " .. player.name) + return + end + + -- Ensure we still have their previous spawn choices + local spawn_choices = storage.spawn_choices[player.name] + if (spawn_choices == nil) then + log("ERROR - RerollSpawn - No spawn choices for player: " .. player.name) + return + end + + -- If it is a buddy spawn, tell them we don't support this: + if (spawn_choices.buddy ~= nil) then + player.print({ "oarc-no-reroll-buddy-spawn" }) + return + end + + local surface = player.character.surface + local surface_name = surface.name + + -- Confirm there is AN existing spawn point for this player on this surface + if (storage.unique_spawns[surface_name] == nil or storage.unique_spawns[surface_name][player.name] == nil) then + log("ERROR - RerollSpawn - Can't reroll? No existing spawn for " .. player.name) + return + end + + -- Save a copy of the previous spawn point + local old_spawn_point = table.deepcopy(storage.unique_spawns[surface_name][player.name]) + + -- Find a new spawn point + local spawn_position = FindUngeneratedCoordinates(surface_name, spawn_choices.distance, 3) + -- If that fails, just throw a warning and don't spawn them. They can try again. + if ((spawn_position.x == 0) and (spawn_position.y == 0)) then + player.print({ "oarc-no-ungenerated-land-error" }) + return end + + -- Remove the old spawn point + if (storage.ocfg.regrowth.enable_abandoned_base_cleanup) then + log("Removing base: " .. spawn_position.x .. "," .. spawn_position.y .. " on surface: " .. surface_name) + + -- Clear an area around the spawn that SHOULD not include any other bases. + local clear_radius = storage.ocfg.gameplay.minimum_distance_to_existing_chunks - 2 -- Bring in a bit for safety. + RegrowthMarkAreaForRemoval(surface_name, old_spawn_point.position, clear_radius) + TriggerCleanup() + script.raise_event("oarc-mod-on-spawn-remove-request", {spawn_data = old_spawn_point}) + end + + -- Queue spawn generation and the player. + local delayed_spawn = GenerateNewSpawn(player.name, surface_name, spawn_position, spawn_choices, old_spawn_point.primary) + QueuePlayerForSpawn(player.name, delayed_spawn) + + -- Send them to the holding pen + SafeTeleport(player, game.surfaces[HOLDING_PEN_SURFACE_NAME], {x=0,y=0}) + + -- Queue joiners too! + for _,joiner in pairs(old_spawn_point.joiners) do + QueuePlayerForSpawn(joiner, delayed_spawn) + SafeTeleport(game.players[joiner], game.surfaces[HOLDING_PEN_SURFACE_NAME], {x=0,y=0}) + end + + -- Announce + SendBroadcastMsg({"", { "oarc-spawn-rerolled", player.name, surface.name }, " ", GetGPStext(surface.name, spawn_position)}) end diff --git a/lib/oarc_utils.lua b/lib/oarc_utils.lua index 23d9567..313f57e 100644 --- a/lib/oarc_utils.lua +++ b/lib/oarc_utils.lua @@ -295,6 +295,18 @@ function TableRemoveOneUsingPairs(t, val) end end +---Gets a random point within a circle of a given radius and center point. +---@param radius number +---@param center MapPosition +---@return MapPosition +function GetRandomPointWithinCircle(radius, center) + local angle = math.random() * 2 * math.pi + local distance = math.random() * radius + local x = center.x + distance * math.cos(angle) + local y = center.y + distance * math.sin(angle) + return {x=x, y=y} +end + -- -- Get a random KEY from a table. -- function GetRandomKeyFromTable(t) -- local keyset = {} @@ -454,6 +466,27 @@ function DeleteAllChunks(surface) end +---Get position for buddy spawn (for buddy placement) +---@param position MapPosition +---@param surface_name string +---@param moat_enabled boolean +---@return MapPosition +function GetBuddySpawnPosition(position, surface_name, moat_enabled) + + local spawn_config = storage.ocfg.surfaces_config[surface_name].spawn_config + + local x_offset = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier * 2 + x_offset = x_offset + storage.ocfg.spawn_general.moat_width_tiles + -- distance = distance + 5 -- EXTRA BUFFER? + + -- Create that spawn in the global vars + local buddy_position = table.deepcopy(position) + -- The x_offset must be big enough to ensure the spawns DO NOT overlap! + buddy_position.x = buddy_position.x + x_offset + + return buddy_position +end + -- -- Modular armor quick start -- function GiveQuickStartModularArmor(player) -- player.insert{name="modular-armor", count = 1} @@ -675,7 +708,6 @@ function GetRandomVector() local magnitude = math.sqrt((randVec.x^2) + (randVec.y^2)) randVec.x = randVec.x / magnitude randVec.y = randVec.y / magnitude - log("direction: x=" .. randVec.x .. ", y=" .. randVec.y) return randVec end @@ -762,17 +794,32 @@ end ---Pick a random direction, go at least the minimum distance, and start looking for ungenerated chunks ---We try a few times (hardcoded) and then try a different random direction if we fail (up to max_tries) ----@param surface LuaSurface +---@param surface_name string Surface name because we might need to force the creation of a new surface ---@param minimum_distance_chunks number Distance in chunks to start looking for ungenerated chunks ---@param max_tries integer Maximum number of tries to find a spawn point ---@return MapPosition -function FindUngeneratedCoordinates(surface, minimum_distance_chunks, max_tries) +function FindUngeneratedCoordinates(surface_name, minimum_distance_chunks, max_tries) + + local final_position = {x=0,y=0} + + -- If surface is nil, it is probably a planet? Check and create if needed. + local surface = game.surfaces[surface_name] + if (surface == nil) then + if (game.planets[surface_name] == nil) then + error("ERROR! No surface or planet found for requested player spawn!") + return final_position + end + surface = game.planets[surface_name].create_surface() + if (surface == nil) then + error("ERROR! Failed to create planet surface for player spawn!") + return final_position + end + end --- Get a random vector, figure out how many times to multiply it to get the minimum distance local direction_vector = GetRandomVector() local start_distance_tiles = minimum_distance_chunks * CHUNK_SIZE - local final_position = {x=0,y=0} local tries_remaining = max_tries - 1 -- Starting search position @@ -794,7 +841,7 @@ function FindUngeneratedCoordinates(surface, minimum_distance_chunks, max_tries) if (jumps_count <= 0) then if (tries_remaining > 0) then - return FindUngeneratedCoordinates(surface, minimum_distance_chunks, tries_remaining) + return FindUngeneratedCoordinates(surface_name, minimum_distance_chunks, tries_remaining) else log("WARNING - FindUngeneratedCoordinates - Hit max distance!") break @@ -1355,6 +1402,10 @@ function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile, m local tree_radius_sqr_inner = ((tileRadius - 1 - tree_width) ^ 2) -- 1 less to make sure trees are inside the spawn area local tree_radius_sqr_outer = ((tileRadius - 1) ^ 2) + local surface_config = storage.ocfg.surfaces_config[surface.name] + local liquid_tile = surface_config.spawn_config.liquid_tile + local fish_enabled = (liquid_tile == "water") + local dirtTiles = {} for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do @@ -1382,10 +1433,10 @@ function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile, m -- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating -- 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 } }) + table.insert(dirtTiles, { name = liquid_tile, position = { i, j } }) --5% chance of fish in water - if (math.random(1,20) == 1) then + if fish_enabled and (math.random(1,20) == 1) then surface.create_entity({name="fish", position={i + 0.5, j + 0.5}}) end end @@ -1396,11 +1447,18 @@ function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile, m surface.set_tiles(dirtTiles) --Create trees (needs to be done after setting tiles!) + local tree_entity = surface_config.spawn_config.tree_entity + if (tree_entity == nil) then return end + 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 } }) + local pos = surface.find_non_colliding_position(tree_entity, { i, j }, 2, 0.5) + if (pos ~= nil) then + surface.create_entity({ name = tree_entity, amount = 1, position = pos }) + end + -- surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) end end end @@ -1423,6 +1481,8 @@ function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile, local tree_width = storage.ocfg.spawn_general.tree_width_tiles local tree_distance_inner = tileRadius - tree_width + local surface_config = storage.ocfg.surfaces_config[surface.name] + local dirtTiles = {} for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do @@ -1462,6 +1522,11 @@ function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile, end surface.set_tiles(dirtTiles) + + --Create trees (needs to be done after setting tiles!) + local tree_entity = surface_config.spawn_config.tree_entity + if (tree_entity == nil) then return end + --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 @@ -1493,6 +1558,8 @@ function CreateCropSquare(surface, centerPos, chunkArea, tileRadius, fillTile, m local tree_width = storage.ocfg.spawn_general.tree_width_tiles local tree_distance_inner = tileRadius - tree_width + local surface_config = storage.ocfg.surfaces_config[surface.name] + local dirtTiles = {} for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do @@ -1532,6 +1599,10 @@ function CreateCropSquare(surface, centerPos, chunkArea, tileRadius, fillTile, m surface.set_tiles(dirtTiles) + --Create trees (needs to be done after setting tiles!) + local tree_entity = surface_config.spawn_config.tree_entity + if (tree_entity == nil) then return end + --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 @@ -1578,14 +1649,16 @@ function CreateMoat(surface, centerPos, chunkArea, tileRadius, moatTile, bridge, surface.set_tiles(tiles) end --- Create a horizontal line of water +-- Create a horizontal line of tiles (typically used for water) ---@param surface LuaSurface ---@param leftPos TilePosition ---@param length integer -function CreateWaterStrip(surface, leftPos, length) +---@param tile_name string +---@return nil +function CreateTileStrip(surface, leftPos, length, tile_name) local waterTiles = {} for i = 0, length-1, 1 do - table.insert(waterTiles, { name = "water", position = { leftPos.x + i, leftPos.y } }) + table.insert(waterTiles, { name = tile_name, position = { leftPos.x + i, leftPos.y } }) end surface.set_tiles(waterTiles) end @@ -1620,6 +1693,65 @@ function GenerateResourcePatch(surface, resourceName, diameter, position, amount end end +--- Function to generate a resource patch, of a certain size/amount at a pos. +---@param surface LuaSurface +---@param position MapPosition +---@return nil +function PlaceRandomEntities(surface, position) + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + local random_entities = spawn_config.random_entities + if (random_entities == nil) then return end + + local tree_width = storage.ocfg.spawn_general.tree_width_tiles + local radius = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier - tree_width + + --Iterate through the random entities and place them + for _, entry in pairs(random_entities) do + local entity_name = entry.name + + for i = 1, entry.count do + + local random_pos = GetRandomPointWithinCircle(radius, position) + local open_pos = surface.find_non_colliding_position(entity_name, random_pos, tree_width, 0.5) + + if (open_pos ~= nil) then + surface.create_entity({ + name = entity_name, + position = open_pos + }) + end + end + end +end + +--- Randomly place lightning attractors specific for Fulgora. This should space them out so they don't overlap too much. +---@param surface LuaSurface +---@param position MapPosition +---@return nil +function PlaceFulgoranLightningAttractors(surface, position, count) + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + local radius = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier + + -- HARDCODED FOR NOW + local ATTRACTOR_NAME = "fulgoran-ruin-attractor" + local ATTRACTOR_RADIUS = 20 + + --Iterate through and place them and use the largest available entity + for i = 1, count do + + local random_pos = GetRandomPointWithinCircle(radius, position) + local open_pos = surface.find_non_colliding_position("crash-site-spaceship", random_pos, 1, 0.5) + + if (open_pos ~= nil) then + surface.create_entity({ + name = ATTRACTOR_NAME, + position = open_pos, + force = "player" -- Same as native game + }) + end + end +end + -- -------------------------------------------------------------------------------- -- -- Holding pen for new players joining the map -- -------------------------------------------------------------------------------- diff --git a/lib/planet_configs/fulgora.lua b/lib/planet_configs/fulgora.lua new file mode 100644 index 0000000..6a93d91 --- /dev/null +++ b/lib/planet_configs/fulgora.lua @@ -0,0 +1,35 @@ +-- This config is used as the default config for the planet fulgora. + +---@type OarcConfigStartingItems +FULGORA_STARTER_ITEMS = table.deepcopy(NO_STARTER_ITEMS) + +---@type OarcConfigSpawn +FULGORA_SPAWN_CONFIG = table.deepcopy(NAUVIS_SPAWN_CONFIG) +FULGORA_SPAWN_CONFIG.fill_tile = "fulgoran-dunes" +FULGORA_SPAWN_CONFIG.liquid_tile = "oil-ocean-deep" +FULGORA_SPAWN_CONFIG.tree_entity = nil +FULGORA_SPAWN_CONFIG.random_entities = { + {name = "fulgoran-ruin-vault", count = 2}, + {name = "fulgoran-ruin-stonehenge", count = 5}, + {name = "fulgoran-ruin-colossal", count = 5}, + {name = "fulgoran-ruin-medium", count = 20}, + {name = "fulgoran-ruin-small", count = 30}, + {name = "fulgurite", count = 20}, + {name = "fulgurite-small", count = 30}, +} +-- I think Fulgora should be smaller in general. +FULGORA_SPAWN_CONFIG.radius_modifier = 0.7 + +FULGORA_SPAWN_CONFIG.solid_resources = +{ + ["scrap"] = { + amount = 2000, + size = 25, + + -- These are only used if not using automatic placing. + x_offset = -29, + y_offset = 16 + } +} + +FULGORA_SPAWN_CONFIG.fluid_resources = { } \ No newline at end of file diff --git a/lib/planet_configs/nauvis.lua b/lib/planet_configs/nauvis.lua new file mode 100644 index 0000000..eb7f055 --- /dev/null +++ b/lib/planet_configs/nauvis.lua @@ -0,0 +1,180 @@ +-- THIS is used as the default starting items on all surfaces if no other settings are provided! +---@type OarcConfigStartingItems +NAUVIS_STARTER_ITEMS = +{ + player_start_items = { + ["iron-plate"] = 8, + ["wood"] = 1, + ["pistol"] = 1, + ["firearm-magazine"] = 10, + ["burner-mining-drill"] = 1, + ["stone-furnace"] = 1 + }, + player_respawn_items = { + ["pistol"] = 1, + ["firearm-magazine"] = 10 + }, + + crashed_ship = true, + crashed_ship_resources = { + ["firearm-magazine"] = 8 -- Max of 5 inventory slots! + }, + crashed_ship_wreakage = { + ["iron-plate"] = 8 -- I don't recommend more than 1 item type here! + }, +} + +-- THIS is used when disabling starter items on a surface? +---@type OarcConfigStartingItems +NO_STARTER_ITEMS = +{ + player_start_items = { }, + player_respawn_items = { + ["pistol"] = 1, + ["firearm-magazine"] = 10 + }, + + crashed_ship = false, + crashed_ship_resources = { + -- ["firearm-magazine"] = 8 -- Max of 5 inventory slots! + }, + crashed_ship_wreakage = { + -- ["iron-plate"] = 8 -- I don't recommend more than 1 item type here! + }, +} + +-- THIS is used as the default spawn config on all surfaces if no other settings are provided! +---@type OarcConfigSpawn +NAUVIS_SPAWN_CONFIG = +{ + -- Used to fill in area that collides with water layer, IF force grass is enabled. + fill_tile = "grass-1", + + -- Used to fill in the moat and the liquid strip at the top of the circle if no moat. + liquid_tile = "water", + + -- Used to circle the base. Set to nil to disable. + tree_entity = "tree-02", + + -- Random entities to place around the base. + random_entities = { + {name = "big-rock", count = 5}, + {name = "huge-rock", count = 5}, + {name = "dead-grey-trunk", count = 5}, + {name = "big-sand-rock", count = 5}, + }, + + -- Used to modify the size of the base. + radius_modifier = 1, + + -- Safe Spawn Area Options + -- The default settings here are balanced for my recommended map gen settings (close to train world). + safe_area = + { + -- Safe area has no aliens + -- This is the radius in chunks of safe area. + safe_radius = 6, + + -- Warning area has significantly reduced aliens + -- 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 chunks of danger area. + danger_radius = 32, + + -- 1 : X (spawners alive : spawners destroyed) in this area + danger_reduction = 5, + }, + + -- Location of water strip within the spawn area (2 horizontal rows) + -- The offset is from the TOP (NORTH) of the spawn area. + water = { + x_offset = -4, + y_offset = 10, + length = 8, + }, + + -- Location of shared power pole within the spawn area (if enabled) + -- The offset is from the RIGHT (WEST) of the spawn area. + shared_power_pole_position = { + x_offset=-10, + y_offset=0 + }, + + -- Location of shared chest within the spawn area (if enabled) + -- The offset is from the RIGHT (WEST) of the spawn area. + shared_chest_position = { + x_offset=-10, + y_offset=1 + }, + + -- Solid resource tiles + -- If you are running with mods that add or change resources, you'll want to customize this. + -- Offsets only are applicable if auto placement is disabled. Offsets are from CENTER of spawn area. + solid_resources = { + ["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 + } + }, + + -- Fluid resource patches like oil + -- If you are running with mods that add or change resources, you'll want to customize this. + -- The offset is from the BOTTOM (SOUTH) of the spawn area. + fluid_resources = + { + ["crude-oil"] = + { + num_patches = 2, + amount = 900000, + spacing = 6, -- Spacing between each patch, only used for automatic placing. + + -- 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, + -- Additional position offsets for each new oil patch (relative to previous oil patch) + x_offset_next = 6, + y_offset_next = 0 + } + }, +} + +---@type OarcConfigSurface +NAUVIS_SURFACE_CONFIG = +{ + starting_items = NAUVIS_STARTER_ITEMS, + spawn_config = NAUVIS_SPAWN_CONFIG +} \ No newline at end of file diff --git a/lib/regrowth_map.lua b/lib/regrowth_map.lua index 9b79f19..d51cee9 100644 --- a/lib/regrowth_map.lua +++ b/lib/regrowth_map.lua @@ -114,14 +114,16 @@ function RegrowthDisableSurface(surface_name) storage.rg[surface_name].active = false TableRemoveOneUsingPairs(storage.rg.active_surfaces, surface_name) - -- Make sure indices are reset if needed + -- Make sure indices are reset and the iterator made invalid too if (storage.rg.current_surface == surface_name) then storage.rg.current_surface = nil storage.rg.current_surface_index = 1 + storage.rg.chunk_iter = nil end if (storage.rg.we_current_surface == surface_name) then storage.rg.we_current_surface = nil storage.rg.we_current_surface_index = 1 + storage.rg.we_chunk_iter = nil end if #storage.rg.active_surfaces > 0 then storage.rg.current_surface = storage.rg.active_surfaces[1] @@ -396,6 +398,7 @@ function GetNextChunkAndUpdateIter() -- Make sure we have a valid iterator! if (not storage.rg.chunk_iter or not storage.rg.chunk_iter.valid) then + if game.surfaces[storage.rg.current_surface] == nil then return nil end storage.rg.chunk_iter = game.surfaces[storage.rg.current_surface].get_chunks() end @@ -408,8 +411,12 @@ function GetNextChunkAndUpdateIter() local next_surface_info = GetNextActiveSurface(storage.rg.current_surface_index) storage.rg.current_surface = next_surface_info.surface storage.rg.current_surface_index = next_surface_info.index - storage.rg.chunk_iter = game.surfaces[storage.rg.current_surface].get_chunks() - next_chunk = storage.rg.chunk_iter() + + -- Surface may not exist + if game.surfaces[storage.rg.current_surface] ~= nil then + storage.rg.chunk_iter = game.surfaces[storage.rg.current_surface].get_chunks() + next_chunk = storage.rg.chunk_iter() + end end return next_chunk @@ -421,6 +428,7 @@ function GetNextChunkAndUpdateWorldEaterIter() -- Make sure we have a valid iterator! if (not storage.rg.we_chunk_iter or not storage.rg.we_chunk_iter.valid) then + if game.surfaces[storage.rg.we_current_surface] == nil then return nil end storage.rg.we_chunk_iter = game.surfaces[storage.rg.we_current_surface].get_chunks() end @@ -433,8 +441,12 @@ function GetNextChunkAndUpdateWorldEaterIter() local next_surface_info = GetNextActiveSurface(storage.rg.we_current_surface_index) storage.rg.we_current_surface = next_surface_info.surface storage.rg.we_current_surface_index = next_surface_info.index - storage.rg.we_chunk_iter = game.surfaces[storage.rg.we_current_surface].get_chunks() - next_chunk = storage.rg.we_chunk_iter() + + -- Surface may not exist + if game.surfaces[storage.rg.we_current_surface] ~= nil then + storage.rg.we_chunk_iter = game.surfaces[storage.rg.we_current_surface].get_chunks() + next_chunk = storage.rg.we_chunk_iter() + end end return next_chunk @@ -477,7 +489,8 @@ function RegrowthSingleStepArray() table.insert(storage.rg.removal_list, removal_entry) storage.rg[current_surface].map[next_chunk.x][next_chunk.y] = REGROWTH_FLAG_REMOVAL else - log("WARN - RegrowthSingleStepArray: Chunk not generated?: " .. next_chunk.x .. "," .. next_chunk.y .. " on surface: " .. current_surface) + -- Sometimes chunks that are in the iterator aren't fully generated yet, so we can ignore these. + -- log("WARN - RegrowthSingleStepArray: Chunk not generated?: " .. next_chunk.x .. "," .. next_chunk.y .. " on surface: " .. current_surface) storage.rg[current_surface].map[next_chunk.x][next_chunk.y] = nil end end diff --git a/lib/separate_spawns.lua b/lib/separate_spawns.lua index e514f27..e46cab1 100644 --- a/lib/separate_spawns.lua +++ b/lib/separate_spawns.lua @@ -2,6 +2,7 @@ local util = require("util") local crash_site = require("crash-site") +require("lib/spawn_area_generation") --[[ ___ _ _ ___ _____ @@ -25,6 +26,7 @@ function InitSpawnGlobalsAndForces() for _, surface in pairs(game.surfaces) do SeparateSpawnsInitSurface(surface.name) end + SeparateSpawnsInitPlanets() -- This contains each player's respawn point. Literally where they will respawn on death -- There is a way in game to change this under one of the little menu features I added. This allows players to @@ -65,6 +67,10 @@ function InitSpawnGlobalsAndForces() --[[@type table]] storage.oarc_renders_fadeout = {} + -- This is a queue of players that need to be teleported to their spawn point. + --[[@type table]] + storage.nil_character_teleport_queue = {} + -- Special forces for when players with their own force want a reset. game.create_force(ABANDONED_FORCE_NAME) -- game.create_force(DESTROYED_FORCE_NAME) @@ -129,16 +135,20 @@ function SeparateSpawnsInitSurface(surface_name) if IsSurfaceBlacklisted(surface_name) then return end - -- Add the surface to the list of surfaces that allow spawns with value from config. - if storage.ocfg.gameplay.default_allow_spawning_on_other_surfaces then - storage.oarc_surfaces[surface_name] = { primary = true, secondary = true } - - -- Otherwise only allow the default surface (by default) - else - storage.oarc_surfaces[surface_name] = { - primary = (surface_name == storage.ocfg.gameplay.default_surface), - secondary = false - } + if storage.oarc_surfaces[surface_name] == nil then + -- Default surface is set to primary only, all others are secondary only if + -- default_enable_secondary_spawns_on_other_surfaces is set to true. + if (surface_name == storage.ocfg.gameplay.default_surface) then + storage.oarc_surfaces[surface_name] = { + primary = true, + secondary = false + } + else + storage.oarc_surfaces[surface_name] = { + primary = false, + secondary = storage.ocfg.gameplay.default_enable_secondary_spawns_on_other_surfaces + } + end end -- Make sure it has a surface configuration entry! @@ -148,6 +158,14 @@ function SeparateSpawnsInitSurface(surface_name) end end +---Init surface globals using game.planets +---@return nil +function SeparateSpawnsInitPlanets() + for _, planet in pairs(game.planets) do + SeparateSpawnsInitSurface(planet.name) + end +end + ---Detects when surfaces are deleted and removes them from the list of surfaces that allow spawns. ---@param event EventData.on_pre_surface_deleted ---@return nil @@ -219,7 +237,7 @@ function SeparateSpawnsPlayerRespawned(event) return end - SendPlayerToSpawn(surface_name, player, false) + TeleportPlayerToRespawnPoint(surface_name, player, false) GivePlayerRespawnItems(player) end @@ -271,26 +289,49 @@ function SeparateSpawnsPlayerChangedSurface(player, previous_surface_name, new_s -- If we are NOT arriving from space, then ignore the rest of this?? -- if (not arriving_from_space) then return end + -- Check if there is already a landing pad for their force on the surface + local landing_pad = surface.find_entities_filtered{name = "cargo-landing-pad", force = player.force, limit = 1} + if (#landing_pad > 0) then + -- If there's a landing pad for the force, I don't think we should interfere with the native behavior until + -- there is an API that lets us do it properly. Otherwise I'm having to track landing pads per spawn... + -- TODO: Implement this properly. + if (player.force.name ~= storage.ocfg.gameplay.main_force_name) then + log("Player has a landing pad on this surface, let them land there?") + else + log("WARNING - I haven't fully implemented this yet! Player has a landing pad on this surface but is on the main force so it might not be at their own spawn?!") + end + return + end + local player_spawn = FindPlayerSpawnOnSurface(player.name, new_surface_name) -- Either they are host or joiner. - -- If there IS a spawn for them on their new surface, then just send them there. + -- If there IS a spawn for them on their new surface if (player_spawn ~= nil) then - SendPlayerToSpawn(new_surface_name, player, false) - return - end - -- If there is no spawn for them on their new surface, generate one based on previous choices. + -- Then just send them to their respawn point which they should have. + if (player_spawn.generated) then + TeleportPlayerToRespawnPoint(new_surface_name, player, false) + return - -- Check if secondary spawns are enabled (both globally and on this surface) - if (not storage.ocfg.gameplay.enable_secondary_spawns) or - (not storage.oarc_surfaces[new_surface_name].secondary) then - return + -- Unless they have to wait for it to be generated. + else + local delayed_spawn = FindDelayedSpawn(player.name, new_surface_name) + if (delayed_spawn == nil) then + error("FindPlayerSpawnOnSurface is ungenerated but FindDelayedSpawn returned nil? " .. player.name) + end + QueuePlayerForSpawn(player.name, delayed_spawn) + return + end end - -- TODO: Need to figure out buddy spawns and other stuff still! + -- Check if secondary spawns are disabled + if (not storage.oarc_surfaces[new_surface_name].secondary) then + return + end - log("WARNING - THIS IS NOT FULLY IMPLEMENTED YET!!") - SecondarySpawn(player, game.surfaces[new_surface_name]) + -- If there is no spawn for them on their new surface, generate one based on previous choices. + log("WARNING - SECONDARY SPAWNS ARE STILL EXPERIMENTAL!!") + SecondarySpawn(player, new_surface_name) end ---Updates the player's surface and raises an event if it changes. @@ -326,6 +367,31 @@ end --]] +---Generate liquid strip +---@param delayed_spawn OarcDelayedSpawn +---@param spawn_config OarcConfigSpawn +---@param surface LuaSurface +---@return nil +function GenerateStartingLiquedStrip(delayed_spawn, spawn_config, surface) + + local water_data = spawn_config.water + -- Reference position is the top of the spawn area. + local reference_pos = { + x = delayed_spawn.position.x, + y = delayed_spawn.position.y - (storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier) + } + CreateTileStrip(surface, + { x = reference_pos.x + water_data.x_offset, y = reference_pos.y + water_data.y_offset }, + water_data.length, + spawn_config.liquid_tile) + CreateTileStrip(surface, + { x = reference_pos.x + water_data.x_offset, y = reference_pos.y + water_data.y_offset + 1 }, + water_data.length, + spawn_config.liquid_tile) + +end + + -- Generate the basic starter resource around a given location. ---@param surface LuaSurface ---@param position TilePosition --The center of the spawn area @@ -367,7 +433,7 @@ function GenerateStartingResources(surface, position) for i = 1, r_data.num_patches do surface.create_entity({ - name = "crude-oil", + name = r_name, amount = r_data.amount, position = { oil_patch_x, oil_patch_y } }) @@ -416,25 +482,41 @@ function PlaceResourcesInSemiCircle(surface, position, size_mod, amount_mod) local shuffled_list = FYShuffle(r_list) -- This places resources in a semi-circle + local surface_config = storage.ocfg.surfaces_config[surface.name] local angle_offset_radians = math.rad(storage.ocfg.resource_placement.angle_offset) local angle_final_radians = math.rad(storage.ocfg.resource_placement.angle_final) local num_resources = table_size(storage.ocfg.surfaces_config[surface.name].spawn_config.solid_resources) - local theta = ((angle_final_radians - angle_offset_radians) / (num_resources-1)); - local count = 0 - - local radius = storage.ocfg.spawn_general.spawn_radius_tiles - storage.ocfg.resource_placement.distance_to_edge + local radius = storage.ocfg.spawn_general.spawn_radius_tiles * surface_config.spawn_config.radius_modifier - storage.ocfg.resource_placement.distance_to_edge - for _, r_name in pairs(shuffled_list) do - local angle = (theta * count) + angle_offset_radians; + -- Special case for only one resource, place it in the middle of the semi-circle. + if (num_resources == 1) then + local r_name = shuffled_list[1] + local angle = ((angle_final_radians - angle_offset_radians) / 2) + angle_offset_radians; local tx = (radius * math.cos(angle)) + position.x local ty = (radius * math.sin(angle)) + position.y local pos = { x = math.floor(tx), y = math.floor(ty) } - local resourceConfig = storage.ocfg.surfaces_config[surface.name].spawn_config.solid_resources[r_name] + local resourceConfig = surface_config.spawn_config.solid_resources[r_name] GenerateResourcePatch(surface, r_name, resourceConfig.size * size_mod, pos, resourceConfig.amount * amount_mod) - count = count + 1 + else + local theta = ((angle_final_radians - angle_offset_radians) / (num_resources-1)); + local count = 0 + + + for _, r_name in pairs(shuffled_list) do + local angle = (theta * count) + angle_offset_radians; + + local tx = (radius * math.cos(angle)) + position.x + local ty = (radius * math.sin(angle)) + position.y + + local pos = { x = math.floor(tx), y = math.floor(ty) } + + local resourceConfig = storage.ocfg.surfaces_config[surface.name].spawn_config.solid_resources[r_name] + GenerateResourcePatch(surface, r_name, resourceConfig.size * size_mod, pos, resourceConfig.amount * amount_mod) + count = count + 1 + end end end @@ -457,9 +539,13 @@ function PlaceResourcesInSquare(surface, position, size_mod, amount_mod) ---@type table local shuffled_list = FYShuffle(r_list) + local spawn_general = storage.ocfg.spawn_general + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + local radius = spawn_general.spawn_radius_tiles * spawn_config.radius_modifier + -- Get the top left position of the spawn area - local resource_position = { x = position.x - storage.ocfg.spawn_general.spawn_radius_tiles, - y = position.y - storage.ocfg.spawn_general.spawn_radius_tiles } + local resource_position = { x = position.x - radius, + y = position.y - radius } -- Offset the starting position resource_position.x = resource_position.x + storage.ocfg.resource_placement.horizontal_offset @@ -474,35 +560,26 @@ function PlaceResourcesInSquare(surface, position, size_mod, amount_mod) end end ----Sends the player to their spawn point +---Places the final spawn elements after chunk generation is complete. ---@param delayed_spawn OarcDelayedSpawn ---@return nil -function SendPlayerToNewSpawnAndCreateIt(delayed_spawn) +function GenerateFinalSpawnPieces(delayed_spawn) local ocfg --[[@as OarcConfig]] = storage.ocfg - local spawn_config = ocfg.surfaces_config[delayed_spawn.surface].spawn_config + local spawn_config = ocfg.surfaces_config[delayed_spawn.surface_name].spawn_config + + local surface = game.surfaces[delayed_spawn.surface_name] -- DOUBLE CHECK and make sure the area is super safe. ClearNearbyEnemies(delayed_spawn.position, spawn_config.safe_area.safe_radius * CHUNK_SIZE, - game.surfaces[delayed_spawn.surface]) + game.surfaces[delayed_spawn.surface_name]) -- Generate water strip only if we don't have a moat. if (not delayed_spawn.moat) then - local water_data = spawn_config.water - -- Reference position is the top of the spawn area. - local reference_pos = { - x = delayed_spawn.position.x, - y = delayed_spawn.position.y - storage.ocfg.spawn_general.spawn_radius_tiles - } - CreateWaterStrip(game.surfaces[delayed_spawn.surface], - { x = reference_pos.x + water_data.x_offset, y = reference_pos.y + water_data.y_offset }, - water_data.length) - CreateWaterStrip(game.surfaces[delayed_spawn.surface], - { x = reference_pos.x + water_data.x_offset, y = reference_pos.y + water_data.y_offset + 1 }, - water_data.length) + GenerateStartingLiquedStrip(delayed_spawn, spawn_config, surface) end -- Create the spawn resources here - GenerateStartingResources(game.surfaces[delayed_spawn.surface], delayed_spawn.position) + GenerateStartingResources(surface, delayed_spawn.position) -- Reference position is RIGHT (WEST) of the spawn area. local sharing_ref_pos = { @@ -515,7 +592,7 @@ function SendPlayerToNewSpawnAndCreateIt(delayed_spawn) local power_pole_position = { x = sharing_ref_pos.x + spawn_config.shared_power_pole_position.x_offset, y = sharing_ref_pos.y + spawn_config.shared_power_pole_position.y_offset } - CreateSharedPowerPolePair(game.surfaces[delayed_spawn.surface], power_pole_position) + CreateSharedPowerPolePair(surface, power_pole_position) end -- Create shared chest @@ -523,47 +600,77 @@ function SendPlayerToNewSpawnAndCreateIt(delayed_spawn) local chest_position = { x = sharing_ref_pos.x + spawn_config.shared_chest_position.x_offset, y = sharing_ref_pos.y + spawn_config.shared_chest_position.y_offset } - CreateSharedChest(game.surfaces[delayed_spawn.surface], chest_position) + CreateSharedChest(surface, chest_position) end - -- Send the player to that position - local player = game.players[delayed_spawn.playerName] - SendPlayerToSpawn(delayed_spawn.surface, player, delayed_spawn.primary) - GivePlayerStarterItems(player) + -- Place randomized entities + if (delayed_spawn.surface_name == "fulgora") then + PlaceFulgoranLightningAttractors(surface, delayed_spawn.position, 10) + end + + PlaceRandomEntities(surface, delayed_spawn.position) + + -- Only primary spawns get a crashed ship. + if delayed_spawn.primary then + -- Create crash site if configured + if (ocfg.surfaces_config[delayed_spawn.surface_name].starting_items.crashed_ship) then + crash_site.create_crash_site(surface, + { x = delayed_spawn.position.x + 15, y = delayed_spawn.position.y - 25 }, + ocfg.surfaces_config[delayed_spawn.surface_name].starting_items.crashed_ship_resources, + ocfg.surfaces_config[delayed_spawn.surface_name].starting_items.crashed_ship_wreakage) + end + end -- Render some welcoming text... - DisplayWelcomeGroundTextAtSpawn(player, delayed_spawn.surface, delayed_spawn.position) + DisplayWelcomeGroundTextAtSpawn(delayed_spawn.surface_name, delayed_spawn.position) + + -- -- Chart the area. + -- local player = game.players[delayed_spawn.host_name] + -- ChartArea(player.force, delayed_spawn.position, math.ceil(storage.ocfg.spawn_general.spawn_radius_tiles / CHUNK_SIZE), + -- surface) - -- Chart the area. - ChartArea(player.force, delayed_spawn.position, math.ceil(storage.ocfg.spawn_general.spawn_radius_tiles / CHUNK_SIZE), - player.character.surface) + -- Trigger the event that the spawn was created. + script.raise_event("oarc-mod-on-spawn-created", {spawn_data = storage.unique_spawns[delayed_spawn.surface_name][delayed_spawn.host_name]}) +end + +---Sends the player to a NEW spawn point (primary OR secondary) +---@param player_name string +---@param surface_name string +---@param first_spawn boolean +---@return nil +function SendPlayerToNewSpawn(player_name, surface_name, first_spawn) + + local player = game.players[player_name] + + -- Check if player character is nil + if (player.character == nil) then + log("Player character is nil, can't send to spawn point just yet: " .. player_name) + QueueNilCharacterForNewSpawnTeleport(player_name, surface_name, first_spawn) + return + end + + -- Send the player to that position + TeleportPlayerToRespawnPoint(surface_name, player, first_spawn) -- Remove waiting dialog if (player.gui.screen.wait_for_spawn_dialog ~= nil) then player.gui.screen.wait_for_spawn_dialog.destroy() end - -- Create crash site if configured - if (ocfg.surfaces_config[delayed_spawn.surface].starting_items.crashed_ship) then - crash_site.create_crash_site(game.surfaces[delayed_spawn.surface], - { x = delayed_spawn.position.x + 15, y = delayed_spawn.position.y - 25 }, - ocfg.surfaces_config[delayed_spawn.surface].starting_items.crashed_ship_resources, - ocfg.surfaces_config[delayed_spawn.surface].starting_items.crashed_ship_wreakage) - end - - -- Trigger the event that the spawn was created. - script.raise_event("oarc-mod-on-spawn-created", {spawn_data = storage.unique_spawns[delayed_spawn.surface][delayed_spawn.playerName]}) + -- Only first time spawns get starter items. + if first_spawn then + GivePlayerStarterItems(player) - -- Trigger the event that player was spawned too. - script.raise_event("oarc-mod-on-player-spawned", {player_index = player.index}) + -- Trigger the event that player was spawned too. + script.raise_event("oarc-mod-on-player-spawned", {player_index = player.index}) + end end ---Displays some welcoming text at the spawn point on the ground. Fades out over time. ----@param player LuaPlayer ---@param surface LuaSurface|string ---@param position MapPosition ---@return nil -function DisplayWelcomeGroundTextAtSpawn(player, surface, position) +function DisplayWelcomeGroundTextAtSpawn(surface, position) -- Render some welcoming text... local tcolor = { 0.9, 0.7, 0.3, 0.8 } local ttl = 2000 @@ -574,7 +681,6 @@ function DisplayWelcomeGroundTextAtSpawn(player, surface, position) scale = 20, font = "compi", time_to_live = ttl, - -- players={player}, draw_on_ground = true, orientation = 0, -- alignment=center, @@ -587,7 +693,6 @@ function DisplayWelcomeGroundTextAtSpawn(player, surface, position) scale = 20, font = "compi", time_to_live = ttl, - -- players={player}, draw_on_ground = true, orientation = 0, -- alignment=center, @@ -622,6 +727,8 @@ function SetupAndClearSpawnAreas(surface, chunkArea) --[[@type OarcConfigSpawnGeneral]] local general_spawn_config = storage.ocfg.spawn_general + local surface_spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + local radius = general_spawn_config.spawn_radius_tiles * surface_spawn_config.radius_modifier local chunkAreaCenter = { x = chunkArea.left_top.x + (CHUNK_SIZE / 2), @@ -638,54 +745,24 @@ function SetupAndClearSpawnAreas(surface, chunkArea) for _, spawn in pairs(spawns) do -- If the chunk is within the main land area, then clear trees/resources and create the land spawn areas -- (guaranteed land with a circle of trees) - local landArea = GetAreaAroundPos(spawn.position, general_spawn_config.spawn_radius_tiles + CHUNK_SIZE) + local landArea = GetAreaAroundPos(spawn.position, radius + CHUNK_SIZE) if not CheckIfInArea(chunkAreaCenter, landArea) then goto CONTINUE end -- Remove trees/resources inside the spawn area if (general_spawn_config.shape == SPAWN_SHAPE_CHOICE_CIRCLE) or (general_spawn_config.shape == SPAWN_SHAPE_CHOICE_OCTAGON) then - RemoveInCircle(surface, chunkArea, {"resource", "cliff", "tree"}, spawn.position, general_spawn_config.spawn_radius_tiles + 5) + RemoveInCircle(surface, chunkArea, {"resource", "cliff", "tree", "lightning-attractor", "simple-entity"}, spawn.position, radius + 5) elseif (general_spawn_config.shape == SPAWN_SHAPE_CHOICE_SQUARE) then - RemoveInSquare(surface, chunkArea, {"resource", "cliff", "tree"}, spawn.position, general_spawn_config.spawn_radius_tiles + 5) - end - - -- Fill in the spawn area with landfill and create a circle of trees around it. - local fill_tile = "landfill" - if general_spawn_config.force_grass then - fill_tile = "grass-1" + RemoveInSquare(surface, chunkArea, {"resource", "cliff", "tree", "lightning-attractor", "simple-entity"}, spawn.position, radius + 5) end if (general_spawn_config.shape == SPAWN_SHAPE_CHOICE_CIRCLE) then - CreateCropCircle( - surface, - spawn.position, - chunkArea, - general_spawn_config.spawn_radius_tiles, - fill_tile, - spawn.moat, - storage.ocfg.gameplay.enable_moat_bridging - ) + CreateCropCircle(surface, spawn, chunkArea) elseif (general_spawn_config.shape == SPAWN_SHAPE_CHOICE_OCTAGON) then - CreateCropOctagon( - surface, - spawn.position, - chunkArea, - general_spawn_config.spawn_radius_tiles, - fill_tile, - spawn.moat, - storage.ocfg.gameplay.enable_moat_bridging - ) + CreateCropOctagon(surface, spawn, chunkArea) elseif (general_spawn_config.shape == SPAWN_SHAPE_CHOICE_SQUARE) then - CreateCropSquare( - surface, - spawn.position, - chunkArea, - general_spawn_config.spawn_radius_tiles, - fill_tile, - spawn.moat, - storage.ocfg.gameplay.enable_moat_bridging - ) + CreateCropSquare(surface, spawn, chunkArea) end :: CONTINUE :: @@ -736,7 +813,7 @@ function DowngradeResourcesDistanceBasedOnChunkGenerate(surface, chunkArea) if (new_amount < 1) then entity.destroy() else - if (entity.name ~= "crude-oil") then + if (entity.prototype.resource_category ~= "basic-fluid") then entity.amount = math.min(new_amount, ore_per_tile_cap) else entity.amount = new_amount @@ -818,12 +895,16 @@ function RemoveOrResetPlayer(player, remove_player) SafeTeleport(player, game.surfaces[HOLDING_PEN_SURFACE_NAME], {x=0,y=0}) local player_old_force = player.force player.force = storage.ocfg.gameplay.main_force_name + local player_name = player.name - -- Clear globals - CleanupPlayerGlobals(player.name) -- This cleans storage.unique_spawns IF we are transferring ownership. + -- Clear globals and transfers spawn ownership if needed. + CleanupPlayerGlobals(player_name) -- This cleans storage.unique_spawns IF we are transferring ownership. - -- Safely clear the unique spawn IF it is still valid. - UniqueSpawnCleanupRemove(player.name) -- Specifically storage.unique_spawns + -- Safely clear all spawns if they are the host. + local spawns = FindAllUniqueSpawnsWherePlayerIsTheHost(player_name) + for _,spawn in pairs(spawns) do + UniqueSpawnCleanupRemove(player_name, spawn) + end -- Remove a force if this player created it and they are the only one on it if ((#player_old_force.players == 0) and (player_old_force.name ~= storage.ocfg.gameplay.main_force_name)) then @@ -836,15 +917,15 @@ function RemoveOrResetPlayer(player, remove_player) -- Remove the character completely if (remove_player and not player.connected) then - game.remove_offline_players({ player }) + game.remove_offline_players({ player }) -- SOMEHOW RELATED TO THE DESYNCS? (This plus cheat command for other planets.) -- Otherwise, make sure to re-init them! else if (remove_player) then - log("ERROR! RemoveOrResetPlayer - Player not removed as they are still connected: " .. player.name) + log("ERROR! RemoveOrResetPlayer - Player not removed as they are still connected: " .. player_name) end SeparateSpawnsInitPlayer(player.index --[[@as string]]) - SendBroadcastMsg({"oarc-player-was-reset-notify", player.name}) + SendBroadcastMsg({"oarc-player-was-reset-notify", player_name}) end -- Refresh the shared spawn spawn gui for all players @@ -865,7 +946,22 @@ function FindPrimaryUniqueSpawn(player_name) return nil end ----Find the primary home spawn of a player, if one exists. It could be they joined a shared spawn. +---Returns all spawns (primary and secondary) where this player is the host +---@param player_name string +---@return OarcUniqueSpawn[] +function FindAllUniqueSpawnsWherePlayerIsTheHost(player_name) + local found_spawns = {} + for _,spawns in pairs(storage.unique_spawns) do + for _, spawn in pairs(spawns) do + if (spawn.host_name == player_name) then + table.insert(found_spawns, spawn) + end + end + end + return found_spawns +end + +---Find the primary home spawn of a player, if one exists. This will return a shared spawn if they joined one. ---@param player_name string ---@return OarcUniqueSpawn? function FindPlayerHomeSpawn(player_name) @@ -878,6 +974,15 @@ function FindPlayerHomeSpawn(player_name) end end +---Get the spawn choices from whoever is the host of the spawn the player joined, even if they are the host. +---@param player_name string +---@return OarcSpawnChoices? +function GetPrimarySpawnChoices(player_name) + local primary_spawn = FindPlayerHomeSpawn(player_name) + if (primary_spawn == nil) then return nil end + return storage.spawn_choices[primary_spawn.host_name] +end + ---Find the spawn of a player, if one exists, on a specific surface. ---@param player_name string ---@param surface_name string @@ -904,24 +1009,39 @@ function FindSecondaryUniqueSpawns(player_name) return secondary_spawns end +---Searches through the delayed spawns to see if there is one for this player. +---@param player_name string +---@param surface_name string +---@return OarcDelayedSpawn? +function FindDelayedSpawn(player_name, surface_name) + for _,delayed_spawn in pairs(storage.delayed_spawns) do + if (delayed_spawn.surface_name == surface_name) then + if (delayed_spawn.host_name == player_name) or (TableContains(delayed_spawn.joiners, player_name)) then + return delayed_spawn + end + end + end +end + ---Cleans up a player's unique spawn point, if safe to do so. ---@param player_name string +---@param unique_spawn OarcUniqueSpawn? ---@return nil -function UniqueSpawnCleanupRemove(player_name) +function UniqueSpawnCleanupRemove(player_name, unique_spawn) - -- Assumes we only remove the one primary unique spawn per player. - local primary_spawn = FindPrimaryUniqueSpawn(player_name) - if (primary_spawn == nil) then return end -- Safety - log("UniqueSpawnCleanupRemove - " .. player_name) + if (unique_spawn == nil) then return end -- Safety + log("UniqueSpawnCleanupRemove - " .. player_name .. " on surface: " .. unique_spawn.surface_name) - local total_spawn_width = storage.ocfg.spawn_general.spawn_radius_tiles + + local spawn_config = storage.ocfg.surfaces_config[unique_spawn.surface_name].spawn_config + + local total_spawn_width = (storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier) + storage.ocfg.spawn_general.moat_width_tiles -- Check if it was near someone else's base. (Really just buddy base is possible I think?) nearOtherSpawn = false - for player_index, spawn in pairs(storage.unique_spawns[primary_spawn.surface_name]) do + for player_index, spawn in pairs(storage.unique_spawns[unique_spawn.surface_name]) do if ((player_index ~= player_name) and - (util.distance(primary_spawn.position, spawn.position) < (total_spawn_width * 3))) then + (util.distance(unique_spawn.position, spawn.position) < (total_spawn_width * 3))) then log("Won't remove base as it's close to another spawn: " .. player_index) nearOtherSpawn = true end @@ -929,37 +1049,46 @@ function UniqueSpawnCleanupRemove(player_name) -- TODO: Possibly limit this based on playtime? If player is on for a long time then don't remove it? -- Use regrowth mod to cleanup the area. - local spawn_position = primary_spawn.position + local spawn_position = unique_spawn.position if (storage.ocfg.regrowth.enable_abandoned_base_cleanup and (not nearOtherSpawn)) then - log("Removing base: " .. spawn_position.x .. "," .. spawn_position.y .. " on surface: " .. primary_spawn.surface_name) + log("Removing base: " .. spawn_position.x .. "," .. spawn_position.y .. " on surface: " .. unique_spawn.surface_name) -- Clear an area around the spawn that SHOULD not include any other bases. local clear_radius = storage.ocfg.gameplay.minimum_distance_to_existing_chunks - 2 -- Bring in a bit for safety. - RegrowthMarkAreaForRemoval(primary_spawn.surface_name, spawn_position, clear_radius) + RegrowthMarkAreaForRemoval(unique_spawn.surface_name, spawn_position, clear_radius) TriggerCleanup() -- Trigger event - script.raise_event("oarc-mod-on-spawn-remove-request", {spawn_data = primary_spawn}) + script.raise_event("oarc-mod-on-spawn-remove-request", {spawn_data = unique_spawn}) end - storage.unique_spawns[primary_spawn.surface_name][player_name] = nil + -- Remove the spawn point from the global table. + storage.unique_spawns[unique_spawn.surface_name][player_name] = nil end ---Cleans up all references to a player in the global tables. ---@param player_name string ---@return nil function CleanupPlayerGlobals(player_name) + -- Clear the buddy pair IF one exists if (storage.buddy_pairs[player_name] ~= nil) then - local buddyName = storage.buddy_pairs[player_name] + local buddy_name = storage.buddy_pairs[player_name] storage.buddy_pairs[player_name] = nil - storage.buddy_pairs[buddyName] = nil + storage.buddy_pairs[buddy_name] = nil + + -- Nil the buddy from any of the unique spawn entries + for _,spawns in pairs(storage.unique_spawns) do + if (spawns[buddy_name] ~= nil) then + spawns[buddy_name].buddy_name = nil + end + end end -- Transfer or remove a shared spawn if player is owner local unique_spawn = FindPrimaryUniqueSpawn(player_name) if (unique_spawn ~= nil and #unique_spawn.joiners > 0) then local new_owner_name = table.remove(unique_spawn.joiners) -- Get 1 to use as new owner. - TransferOwnershipOfSharedSpawn(unique_spawn, new_owner_name) + TransferOwnershipOfAllSpawns(unique_spawn, new_owner_name) SendBroadcastMsg( {"oarc-host-left-new-host", player_name, new_owner_name}) end @@ -979,9 +1108,9 @@ function CleanupPlayerGlobals(player_name) storage.player_respawns[player_name] = nil end - -- Remove them from the delayed spawn queue if they are in it + -- Remove them from the delayed spawn queue if they are still the host for index, delayedSpawn in pairs(storage.delayed_spawns --[[@as OarcDelayedSpawnsTable]]) do - if (player_name == delayedSpawn.playerName) then + if (player_name == delayedSpawn.host_name) then storage.delayed_spawns[index] = nil log("Removing player from delayed spawn queue: " .. player_name) break @@ -997,31 +1126,39 @@ function CleanupPlayerGlobals(player_name) end ---Transfers ownership of a shared spawn to another player. ----@param spawn OarcUniqueSpawn +---@param primary_spawn OarcUniqueSpawn ---@param new_host_name string ---@return nil -function TransferOwnershipOfSharedSpawn(spawn, new_host_name) - - -- Create a new unique for the new owner based on the old one. - storage.unique_spawns[spawn.surface_name][new_host_name] = { - position = spawn.position, - surface_name = spawn.surface_name, - primary = spawn.primary, - moat = spawn.moat, - host_name = new_host_name, - joiners = spawn.joiners, - join_queue = {}, - open_access = false, - buddy_name = spawn.buddy_name - } +function TransferOwnershipOfAllSpawns(primary_spawn, new_host_name) + + -- Transfer every primary AND secondary spawn. + for surface_name, unique_spawns in pairs(storage.unique_spawns) do + for host_name, unique_spawn in pairs(unique_spawns) do + if (host_name == primary_spawn.host_name) then + + -- Create a new unique for the new owner based on the old one. + storage.unique_spawns[surface_name][new_host_name] = table.deepcopy(unique_spawn) + -- local new_spawn = table.deepcopy(unique_spawn) + storage.unique_spawns[surface_name][new_host_name].host_name = new_host_name + storage.unique_spawns[surface_name][new_host_name].joiners = table.deepcopy(primary_spawn.joiners) -- Copy the old joiner list, with the new host already removed. + + -- Update the matching buddy spawn if it exists. + if (primary_spawn.buddy_name ~= nil) then + storage.unique_spawns[surface_name][unique_spawn.buddy_name].buddy_name = new_host_name + end - -- Update the matching buddy spawn if it exists. - if (spawn.buddy_name ~= nil) then - storage.unique_spawns[spawn.surface_name][spawn.buddy_name].buddy_name = new_host_name + -- Remove the old spawn + storage.unique_spawns[surface_name][host_name] = nil + end + end end - -- Delete the old one - storage.unique_spawns[spawn.surface_name][spawn.host_name] = nil + -- Transfer any delayed_spawns + for index, delayed_spawn in pairs(storage.delayed_spawns) do + if (delayed_spawn.host_name == primary_spawn.host_name) then + storage.delayed_spawns[index].host_name = new_host_name + end + end game.players[new_host_name].print({ "oarc-new-owner-msg" }) end @@ -1034,6 +1171,37 @@ end --]] +---Queue a player whose character is nil when being sent to a new spawn. +---A separate on_tick function will read this queue and send the player to the spawn point when they have a character again. +---@param player_name string +---@param surface_name string +---@param first_spawn boolean +---@return nil +function QueueNilCharacterForNewSpawnTeleport(player_name, surface_name, first_spawn) + if (storage.nil_character_teleport_queue[player_name] == nil) then + storage.nil_character_teleport_queue[player_name] = { surface_name = surface_name, first_spawn = first_spawn } + end +end + +---On tick function to process the nil character teleport queue. +---@return nil +function OnTickNilCharacterTeleportQueue() + for player_name, data in pairs(storage.nil_character_teleport_queue) do + local player = game.players[player_name] + + -- If player is nil, remove them from the queue. + if (player == nil) then + storage.nil_character_teleport_queue[player_name] = nil + + -- Else if they have a character, send them to the spawn point. + -- And hope to high heaven this doesn't recurse infinitely. + elseif (player.character ~= nil) then + storage.nil_character_teleport_queue[player_name] = nil + SendPlayerToNewSpawn(player_name, data.surface_name, data.first_spawn) + end + end +end + ---Finds and removes a player from a shared spawn join queue, and refreshes the host's GUI. ---@param player_name string ---@return boolean @@ -1198,6 +1366,11 @@ function IsSharedSpawnOpen(surface_name, owner_name) return false end + -- Currently don't support directly joining secondary spawns! + if (not spawn.primary) then + return false + end + if (game.players[owner_name] == nil) or not (game.players[owner_name].connected) then return false end @@ -1217,6 +1390,54 @@ function IsSharedSpawnFull(surface_name, owner_name) return (GetOnlinePlayersAtSharedSpawn(surface_name, owner_name) >= storage.ocfg.gameplay.number_of_players_per_shared_spawn) end +---Creates a new spawn, this triggers the chunk generation and will provide an event when done. +---@param host_name string +---@param surface_name string -- Might be different from spawn_choices if it is a secondary spawn. +---@param spawn_position MapPosition +---@param spawn_choices OarcSpawnChoices +---@param primary boolean +---@return OarcDelayedSpawn +function GenerateNewSpawn(host_name, surface_name, spawn_position, spawn_choices, primary) + + -- Double check... + if ((spawn_position.x == 0) and (spawn_position.y == 0)) then + error("Invalid spawn position for GenerateNewSpawn: " .. host_name .. " on surface: " .. spawn_choices.surface_name) + end + + local unique_spawn = nil + if primary then + unique_spawn = InitPrimarySpawnGlobals(host_name, spawn_position, spawn_choices) + else + local primary_spawn = FindPrimaryUniqueSpawn(host_name) + if (primary_spawn == nil) then + error("ERROR - GenerateNewSpawn - No primary spawn found for: " .. host_name) + end + unique_spawn = InitSecondarySpawnGlobals(primary_spawn, surface_name, spawn_position) + end + + return QueueNewSpawnGeneration(unique_spawn) +end + +---Queues a player who is waiting on their spawn to be generated. +---@param player_name string +---@param delayed_spawn OarcDelayedSpawn +---@return nil +function QueuePlayerForSpawn(player_name, delayed_spawn) + + SetPlayerRespawn(player_name, delayed_spawn.surface_name, delayed_spawn.position, true) + + game.players[player_name].print({ "oarc-generating-spawn-please-wait" }) + + local ticks_remaining = delayed_spawn.delayed_tick - game.tick + local seconds_remaining = math.ceil(ticks_remaining / TICKS_PER_SECOND) + + HideOarcGui(game.players[player_name]) + DisplayPleaseWaitForSpawnDialog(game.players[player_name], seconds_remaining, game.surfaces[delayed_spawn.surface_name], delayed_spawn.position) + + table.insert(delayed_spawn.waiting_players, player_name) + log("QueuePlayerForSpawn - " .. player_name .. " - " .. delayed_spawn.host_name) +end + ---Sets the custom spawn point for a player. They can have one per surface. ---@param player_name string ---@param surface_name string @@ -1237,113 +1458,140 @@ function SetPlayerRespawn(player_name, surface_name, position, reset_cooldown) end end ----Creates the storage.unique_spawns entries for a new spawn area. ----@param player_name string ----@param surface_name string +---Creates the storage.unique_spawns entries for a new PRIMARY spawn area. +---@param host_name string ---@param spawn_position MapPosition ----@param moat_enabled boolean ----@param primary boolean ----@param buddy_name string? ----@return nil -function InitUniqueSpawnGlobals(player_name, surface_name, spawn_position, moat_enabled, primary, buddy_name) +---@param spawn_choices OarcSpawnChoices +---@return OarcUniqueSpawn +function InitPrimarySpawnGlobals(host_name, spawn_position, spawn_choices) ---@type OarcUniqueSpawn local new_unique_spawn = { - surface_name = surface_name, + surface_name = spawn_choices.surface_name, position = spawn_position, - moat = moat_enabled, - primary = primary, - host_name = player_name, + moat = spawn_choices.moat, + primary = true, + host_name = host_name, joiners = {}, join_queue = {}, open_access = false, - buddy_name = buddy_name + buddy_name = spawn_choices.buddy, + generated = false } - if storage.unique_spawns[surface_name] == nil then - storage.unique_spawns[surface_name] = {} + if storage.unique_spawns[spawn_choices.surface_name] == nil then + storage.unique_spawns[spawn_choices.surface_name] = {} end - storage.unique_spawns[surface_name][player_name] = new_unique_spawn + storage.unique_spawns[spawn_choices.surface_name][host_name] = new_unique_spawn + + return new_unique_spawn end ----Queue a player for a delayed spawn. This will generate the spawn area and move the player there when ready. ----@param player_name string ----@param surface string +---Creates the storage.unique_spawns entries for a new SECONDARY spawn area. +---@param unique_spawn OarcUniqueSpawn +---@param surface_name string ---@param spawn_position MapPosition ----@param moat_enabled boolean ----@param primary boolean ----@param buddy_name string? ----@return nil -function QueuePlayerForDelayedSpawn(player_name, surface, spawn_position, moat_enabled, primary, buddy_name) - -- If we get a valid spawn point, setup the area - if ((spawn_position.x == 0) and (spawn_position.y == 0)) then - error("Invalid spawn position for player: " .. player_name .. " on surface: " .. surface) +---@return OarcUniqueSpawn +function InitSecondarySpawnGlobals(unique_spawn, surface_name, spawn_position) + + local new_unique_spawn = table.deepcopy(unique_spawn) + new_unique_spawn.surface_name = surface_name + new_unique_spawn.position = spawn_position + new_unique_spawn.primary = false + new_unique_spawn.generated = false + + if storage.unique_spawns[surface_name] == nil then + storage.unique_spawns[surface_name] = {} end - InitUniqueSpawnGlobals(player_name, surface, spawn_position, moat_enabled, primary, buddy_name) + storage.unique_spawns[surface_name][unique_spawn.host_name] = new_unique_spawn + + return new_unique_spawn +end + +---Starts the generation of a new unique spawn. This will generate an event when done. +---@param unique_spawn OarcUniqueSpawn +---@return OarcDelayedSpawn +function QueueNewSpawnGeneration(unique_spawn) -- Add a 1 chunk buffer to be safe local total_spawn_width = storage.ocfg.spawn_general.spawn_radius_tiles + storage.ocfg.spawn_general.moat_width_tiles local spawn_chunk_radius = math.ceil(total_spawn_width / CHUNK_SIZE) + 1 + + -- This is just a rough estimate of worst case chunk generation time. + -- If we hit this timeout, usually it means something has gone wrong. local delay_spawn_seconds = 5 * spawn_chunk_radius - game.players[player_name].print({ "oarc-generating-spawn-please-wait" }) - game.surfaces[surface].request_to_generate_chunks(spawn_position, spawn_chunk_radius) + -- Trigger the chunk generation + game.surfaces[unique_spawn.surface_name].request_to_generate_chunks(unique_spawn.position, spawn_chunk_radius) - local final_chunk = GetChunkPosFromTilePos(spawn_position) + local final_chunk = GetChunkPosFromTilePos(unique_spawn.position) final_chunk.x = final_chunk.x + spawn_chunk_radius final_chunk.y = final_chunk.y + spawn_chunk_radius ---@type OarcDelayedSpawn - local delayed_spawn = { - playerName = player_name, - surface = surface, - position = spawn_position, - moat = moat_enabled, - delayedTick = game.tick + delay_spawn_seconds * TICKS_PER_SECOND, + local delayed_spawn = { + + -- I do this explicitly so that I get the LUA warnings if I miss a field! + -- I know I could use table.deepcopy, but this is INTENTIONAL! + surface_name = unique_spawn.surface_name, + position = unique_spawn.position, + moat = unique_spawn.moat, + primary = unique_spawn.primary, + host_name = unique_spawn.host_name, + joiners = unique_spawn.joiners, + join_queue = unique_spawn.join_queue, + open_access = unique_spawn.open_access, + buddy_name = unique_spawn.buddy_name, + + -- This is the extra data I need for the delayed spawn + delayed_tick = game.tick + delay_spawn_seconds * TICKS_PER_SECOND, final_chunk_generated = final_chunk, - primary = primary + waiting_players = {} } table.insert(storage.delayed_spawns, delayed_spawn) - HideOarcGui(game.players[player_name]) - DisplayPleaseWaitForSpawnDialog(game.players[player_name], delay_spawn_seconds, game.surfaces[surface], spawn_position) - - RegrowthMarkAreaSafeGivenTilePos(surface, spawn_position, - math.ceil(storage.ocfg.spawn_general.spawn_radius_tiles / CHUNK_SIZE), true) + RegrowthMarkAreaSafeGivenTilePos(unique_spawn.surface_name, unique_spawn.position, spawn_chunk_radius, true) -- Chart the area to be able to display the minimap while the player waits. - ChartArea(game.players[player_name].force, + ChartArea(game.players[unique_spawn.host_name].force, delayed_spawn.position, spawn_chunk_radius, - surface - ) + unique_spawn.surface_name) + + return delayed_spawn end ---Creates and sends a player to a new secondary spawn, temporarily placing them in the holding pen. ---@param player LuaPlayer ----@param surface LuaSurface +---@param surface_name string ---@return nil -function SecondarySpawn(player, surface) +function SecondarySpawn(player, surface_name) + + local player_name = player.name + + -- Get their home spawn first: + local primary_spawn = FindPlayerHomeSpawn(player_name) + local host_name = primary_spawn.host_name - -- Ensure we still have their previous spawn choices - local spawn_choices = storage.spawn_choices[player.name] + -- Ensure we still have the previous spawn choices (theirs or the host's) + local spawn_choices = GetPrimarySpawnChoices(host_name) if (spawn_choices == nil) then - log("ERROR - SecondarySpawn - No spawn choices for player: " .. player.name) + log("ERROR - SecondarySpawn - No spawn choices for player: " .. host_name) return end - -- Confirm there is no existing spawn point for this player on this surface - if (storage.unique_spawns[surface.name] ~= nil and storage.unique_spawns[surface.name][player.name] ~= nil) then - log("ERROR - SecondarySpawn - Player already has a spawn point on this surface: " .. player.name) + -- Confirm there is no existing spawn point for this host on this surface + if (storage.unique_spawns[surface_name] ~= nil and storage.unique_spawns[surface_name][host_name] ~= nil) then + log("ERROR - SecondarySpawn - Host already has a spawn point on this surface: " .. host_name .. " on surface: " .. surface_name) return end -- Find a new spawn point - local spawn_position = FindUngeneratedCoordinates(surface, spawn_choices.distance, 3) + local spawn_position = FindUngeneratedCoordinates(surface_name, spawn_choices.distance, 3) -- If that fails, just throw a warning and don't spawn them. They can try again. if ((spawn_position.x == 0) and (spawn_position.y == 0)) then player.print({ "oarc-no-ungenerated-land-error" }) @@ -1351,35 +1599,31 @@ function SecondarySpawn(player, surface) end -- Add new spawn point for the new surface - SetPlayerRespawn(player.name, surface.name, spawn_position, false) -- Do not reset cooldown - QueuePlayerForDelayedSpawn(player.name, surface.name, spawn_position, spawn_choices.moat, false, nil) + local delayed_spawn = GenerateNewSpawn(host_name, surface_name, spawn_position, spawn_choices, false) + QueuePlayerForSpawn(player_name, delayed_spawn) - -- TODO: Temporary for space age, buddies get a single spawn point for secondary spawns. + -- Handle special buddy spawns: if (spawn_choices.buddy) then - SetPlayerRespawn(spawn_choices.buddy, surface.name, spawn_position, false) + local buddy_position = GetBuddySpawnPosition(spawn_position, surface_name, spawn_choices.moat) + local buddy_choices = storage.spawn_choices[spawn_choices.buddy] - -- Add buddy to the shared spawn? - storage.unique_spawns[surface.name][player.name].joiners = { spawn_choices.buddy } + GenerateNewSpawn(spawn_choices.buddy, surface_name, buddy_position, buddy_choices, false) + SetPlayerRespawn(spawn_choices.buddy, surface_name, buddy_position, false) - log("Buddy secondary spawn NOT IMPLEMENTED YET!") - end + -- Make sure host and joiners all have their new respawn position set for this surface. + elseif (#storage.unique_spawns[surface_name][host_name].joiners > 0) then + SetPlayerRespawn(host_name, surface_name, spawn_position, false) - -- -- If it is a buddy spawn, we need to setup both areas TOGETHER - -- if spawn_choices.buddy then - -- -- The x_offset must be big enough to ensure the spawns DO NOT overlap! - -- local x_offset = (storage.ocfg.spawn_general.spawn_radius_tiles * 2) - -- if (spawn_choices.moat) then - -- x_offset = x_offset + 10 - -- end - -- local buddy_spawn_position = { x = spawn_position.x + x_offset, y = spawn_position.y } - -- SetPlayerRespawn(spawn_choices.buddy, spawn_choices.surface, buddy_spawn_position, true) - -- end + for _,joiner_name in pairs(storage.unique_spawns[surface_name][host_name].joiners) do + SetPlayerRespawn(joiner_name, surface_name, spawn_position, false) + end + end -- Send them to the holding pen SafeTeleport(player, game.surfaces[HOLDING_PEN_SURFACE_NAME], {x=0,y=0}) -- Announce - SendBroadcastMsg({"", { "oarc-player-new-secondary", player.name, surface.name }, " ", GetGPStext(surface.name, spawn_position)}) + SendBroadcastMsg({"", { "oarc-player-new-secondary", player_name, surface_name }, " ", GetGPStext(surface_name, spawn_position)}) end -- Check a table to see if there are any players waiting to spawn @@ -1392,14 +1636,24 @@ function DelayedSpawnOnTick() -- I think this loop removes from the back of the table to the front?? for i = #storage.delayed_spawns, 1, -1 do - delayedSpawn = storage.delayed_spawns[i] --[[@as OarcDelayedSpawn]] + delayed_spawn = storage.delayed_spawns[i] --[[@as OarcDelayedSpawn]] - local surface = game.surfaces[delayedSpawn.surface] + local surface = game.surfaces[delayed_spawn.surface_name] - if ((delayedSpawn.delayedTick < game.tick) or surface.is_chunk_generated(delayedSpawn.final_chunk_generated) ) then - if (game.players[delayedSpawn.playerName] ~= nil) then - SendPlayerToNewSpawnAndCreateIt(delayedSpawn) + if ((delayed_spawn.delayed_tick < game.tick) or surface.is_chunk_generated(delayed_spawn.final_chunk_generated) ) then + log("DelayedSpawnOnTick - Generating spawn for: " .. delayed_spawn.host_name) + + GenerateFinalSpawnPieces(delayed_spawn) + storage.unique_spawns[delayed_spawn.surface_name][delayed_spawn.host_name].generated = true + + -- For each player waiting to spawn, send them to their new spawn point. + for _,player_name in pairs(delayed_spawn.waiting_players) do + local player = game.players[player_name] + if (player ~= nil) then + SendPlayerToNewSpawn(player_name, delayed_spawn.surface_name, delayed_spawn.primary) + end end + table.remove(storage.delayed_spawns, i) end end @@ -1412,13 +1666,19 @@ end ---@param player LuaPlayer ---@param first_spawn boolean ---@return nil -function SendPlayerToSpawn(surface_name, player, first_spawn) +function TeleportPlayerToRespawnPoint(surface_name, player, first_spawn) local spawn = storage.player_respawns[player.name][surface_name] if (spawn == nil) then log("ERROR - SendPlayerToSpawn - No spawn point for player: " .. player.name .. " on surface: " .. surface_name .. " first_spawn: " .. tostring(first_spawn)) return end + + -- As a temporary measure to make sure teleport works in the case that the player is in a moving cargo-pod, we first + -- teleport to the holding pen surface since there is no way to force them out of the cargo-pod that I know of. + if player.driving then + SafeTeleport(player, game.surfaces[HOLDING_PEN_SURFACE_NAME], {x=0,y=0}) + end SafeTeleport(player, game.surfaces[surface_name], spawn.position) if first_spawn then @@ -1431,7 +1691,7 @@ end ---@return boolean function PlayerHasDelayedSpawn(player_name) for _,delayedSpawn in pairs(storage.delayed_spawns) do - if (delayedSpawn.playerName == player_name) then + if (delayedSpawn.host_name == player_name) then return true end end @@ -1540,6 +1800,7 @@ SPAWN_TEAM_CHOICE = { ---@field join_queue string[] List of players waiting to join this spawn. ---@field joiners string[] List of players who have joined this spawn NOT including the host. ---@field buddy_name string? The other buddy player name if this is a buddy spawn. +---@field generated boolean Whether the spawn has finished being generated or not. ---Table of [OarcUniqueSpawnClass](lua://OarcUniqueSpawnClass) indexed first by surface name and then by player name. ---@alias OarcUniqueSpawnsTable table> @@ -1551,26 +1812,22 @@ SPAWN_TEAM_CHOICE = { ---Temporary data used when spawning a player. Player needs to wait while the area is prepared. ---Temporary data used when spawning a player. Player needs to wait while the area is prepared. ----@class OarcDelayedSpawn ----@field surface string The surface on which the player will spawn. ----@field playerName string The name of the player. ----@field position MapPosition The position where the player will spawn. ----@field moat boolean Whether the spawn has a moat or not. ----@field delayedTick number The game tick when the spawn will be ready. +---@class OarcDelayedSpawn: OarcUniqueSpawn +---@field delayed_tick number The game tick when the spawn will be ready. ---@field final_chunk_generated ChunkPosition The final chunk position that needs to be generated. ----@field primary boolean Whether this is the primary spawn point for a player, this is the first surface they spawn on. All other spawns are secondary. ----Table of [OarcDelayedSpawn](lua://OarcDelayedSpawn) indexed by player name. ----@alias OarcDelayedSpawnsTable table +---@field waiting_players string[] List of players waiting to join this spawn once it is done generating. +---Array of [OarcDelayedSpawn](lua://OarcDelayedSpawn). +---@alias OarcDelayedSpawnsTable OarcDelayedSpawn[] ---This contains the spawn choices for a player in the spawn menu. ---Class representing the spawn choices for a player in the spawn menu. ---@class OarcSpawnChoices ----@field surface string The surface on which the player wants to spawn. +---@field surface_name string The surface on which the player wants to spawn. ---@field team SpawnTeamChoice The team choice for the player. Main team or own team. ---@field moat boolean Whether the player wants a moat around their spawn. ---@field buddy string? The buddy player name if the player wants to spawn with a buddy. ---@field distance integer The distance from the center of the map where the player wants to spawn. ----@field host string? The host player name if the player wants to join a shared spawn. +---@field host_name string? The host player name if the player wants to join a shared spawn. ---@field buddy_team boolean Whether the player wants to join a buddy's team. This means both players will be on the same team. ---Table of [OarcSpawnChoices](lua://OarcSpawnChoices) indexed by player name. @@ -1578,3 +1835,6 @@ SPAWN_TEAM_CHOICE = { ---Primary means a player can spawn for the first time on this surface, secondary they can land here and also receive a custom spawn area. ---@alias OarcSurfaceSpawnSetting { primary: boolean, secondary: boolean} + +---Entry for a nil_character_teleport_queue +---@alias OarcNilCharacterTeleportQueueEntry { surface_name: string, first_spawn: boolean } diff --git a/lib/separate_spawns_guis.lua b/lib/separate_spawns_guis.lua index 0aec525..06e7936 100644 --- a/lib/separate_spawns_guis.lua +++ b/lib/separate_spawns_guis.lua @@ -501,8 +501,6 @@ function CreateBuddySpawnFrame(parent_flow, player, enable_buddy_spawn, enable_s end end - log("Creating buddy spawn frame for: " .. player.name) - AddLabel(buddy_spawn_frame, nil, { "oarc-spawn-menu-buddy-header" }, my_label_header_style) if not enable_buddy_spawn then @@ -580,7 +578,6 @@ end ---@param dropdown LuaGuiElement The buddy dropdown element ---@return nil function RefreshBuddyList(player, dropdown) - log("Refreshing buddy list for: " .. player.name) dropdown.items = GetOtherPlayersInSpawnMenu(player) end @@ -612,9 +609,9 @@ function DisplaySpawnOptions(player) end ---@type OarcSpawnChoices local spawn_choices_entry = { - surface = storage.ocfg.gameplay.default_surface, + surface_name = storage.ocfg.gameplay.default_surface, team = default_team, - moat = false, + moat = false, --storage.ocfg.gameplay.allow_moats_around_spawns TODO: Change default buddy = nil, distance = storage.ocfg.gameplay.near_spawn_distance, host = nil, @@ -751,7 +748,7 @@ function RequestToJoinSharedSpawn(player) player.gui.screen.spawn_opts.destroy() end - local host_name = storage.spawn_choices[player.name].host + local host_name = storage.spawn_choices[player.name].host_name if (host_name == nil) then player.print({ "oarc-no-shared-spawn-selected" }) return end -- Clear the spawn options gui @@ -782,7 +779,7 @@ end ---@return nil function CancelSharedSpawnRequest(player) - local host_name = storage.spawn_choices[player.name].host + local host_name = storage.spawn_choices[player.name].host_name if (host_name ~= nil) and (game.players[host_name] ~= nil) then game.players[host_name].print({ "oarc-player-cancel-join-request", player.name }) end @@ -831,7 +828,7 @@ function SpawnOptsSelectionChanged(event) if (tags.setting == "surface_select") then local index = event.element.selected_index local surface_name = event.element.get_item(index) --[[@as string]] - storage.spawn_choices[player.name].surface = surface_name + storage.spawn_choices[player.name].surface_name = surface_name log("GUI DEBUG Selected surface: " .. surface_name) elseif (tags.setting == "shared_spawn_select") then @@ -863,13 +860,13 @@ function SharedSpawnSelect(gui_element, player) if (primary_spawn and IsSharedSpawnOpen(primary_spawn.surface_name, host_name) and not IsSharedSpawnFull(primary_spawn.surface_name, host_name)) then - storage.spawn_choices[player.name].host = host_name + storage.spawn_choices[player.name].host_name = host_name button.enabled = true button.caption = { "oarc-join-shared-button-enable", host_name, primary_spawn.surface_name } button.style = "green_button" else player.print({ "oarc-invalid-host-shared-spawn" }) - storage.spawn_choices[player.name].host = nil + storage.spawn_choices[player.name].host_name = nil gui_element.selected_index = 0 button.enabled = false button.caption = { "oarc-join-shared-button-disable" } @@ -877,7 +874,7 @@ function SharedSpawnSelect(gui_element, player) end else - storage.spawn_choices[player.name].host = nil + storage.spawn_choices[player.name].host_name = nil end end @@ -891,14 +888,11 @@ function PrimarySpawnRequest(player) if (spawn_choices == nil) then error("ERROR! No spawn choices found for player!") return end -- N/A for solo spawns so clear these! - storage.spawn_choices[player.name].host = nil + storage.spawn_choices[player.name].host_name = nil storage.spawn_choices[player.name].buddy = nil - -- Cache some useful variables - local surface = game.surfaces[spawn_choices.surface] - -- Find coordinates of a good place to spawn - local spawn_position = FindUngeneratedCoordinates(surface, spawn_choices.distance, 3) + local spawn_position = FindUngeneratedCoordinates(spawn_choices.surface_name, spawn_choices.distance, 3) -- If that fails, just throw a warning and don't spawn them. They can try again. if ((spawn_position.x == 0) and (spawn_position.y == 0)) then @@ -911,12 +905,11 @@ function PrimarySpawnRequest(player) CreatePlayerCustomForce(player) end - -- Create that player's spawn in the global vars - SetPlayerRespawn(player.name, spawn_choices.surface, spawn_position, true) + -- Queue spawn generation and the player. + local delayed_spawn = GenerateNewSpawn(player.name, spawn_choices.surface_name, spawn_position, spawn_choices, true) + QueuePlayerForSpawn(player.name, delayed_spawn) - -- Send the player there - QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, spawn_position, spawn_choices.moat, true, nil) - SendBroadcastMsg({"", { "oarc-player-is-joining", player.name, spawn_choices.surface }, " ", GetGPStext(spawn_choices.surface, spawn_position)}) + SendBroadcastMsg({"", { "oarc-player-is-joining", player.name, spawn_choices.surface_name }, " ", GetGPStext(spawn_choices.surface_name, spawn_position)}) -- Unlock spawn control gui tab SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_TAB_NAME, true) @@ -1088,7 +1081,7 @@ function DisplayBuddySpawnRequestMenu(player, requesting_buddy_name) end ---@type LocalisedString - local surfaceText = { "oarc-buddy-txt-surface", spawn_choices.surface} + local surfaceText = { "oarc-buddy-txt-surface", spawn_choices.surface_name} ---@type LocalisedString local distText = { "oarc-buddy-txt-distance", spawn_choices.distance} @@ -1140,15 +1133,16 @@ function AcceptBuddyRequest(player, requesting_buddy_name) ---@type OarcSpawnChoices local spawn_choices = storage.spawn_choices[requesting_buddy_name] local requesting_buddy = game.players[requesting_buddy_name] - local surface = game.surfaces[spawn_choices.surface] + local surface = game.surfaces[spawn_choices.surface_name] -- Copy the buddy's spawn choices to the accepting player - spawn_choices.host = nil -- N/A for buddy spawns so clear it. + spawn_choices.host_name = nil -- N/A for buddy spawns so clear it. storage.spawn_choices[player.name] = table.deepcopy(spawn_choices) storage.spawn_choices[player.name].buddy = requesting_buddy_name + local buddy_choices = storage.spawn_choices[player.name] -- Find coordinates of a good place to spawn - local spawn_position = FindUngeneratedCoordinates(surface, spawn_choices.distance, 3) + local spawn_position = FindUngeneratedCoordinates(spawn_choices.surface_name, spawn_choices.distance, 3) -- If that fails, just throw a warning and don't spawn them. They can try again. if ((spawn_position.x == 0) and (spawn_position.y == 0)) then @@ -1175,20 +1169,20 @@ function AcceptBuddyRequest(player, requesting_buddy_name) end -- Create that spawn in the global vars - local buddySpawn = { x = 0, y = 0 } - -- The x_offset must be big enough to ensure the spawns DO NOT overlap! - local x_offset = (storage.ocfg.spawn_general.spawn_radius_tiles * 2) - if (spawn_choices.moat) then - x_offset = x_offset + 10 - end - buddySpawn = { x = spawn_position.x + x_offset, y = spawn_position.y } - SetPlayerRespawn(player.name, spawn_choices.surface, buddySpawn, true) - SetPlayerRespawn(requesting_buddy_name, spawn_choices.surface, spawn_position, true) - - -- Send the player there (ORDER MATTERS! Otherwise sometimes chunks don't generate properly!) - QueuePlayerForDelayedSpawn(requesting_buddy_name, spawn_choices.surface, spawn_position, spawn_choices.moat, true, player.name) - QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, buddySpawn, spawn_choices.moat, true, requesting_buddy_name) - SendBroadcastMsg({"", {"oarc-buddies-are-joining", requesting_buddy_name, player.name, spawn_choices.surface}, " ", GetGPStext(spawn_choices.surface, spawn_position)}) + local buddy_position = GetBuddySpawnPosition(spawn_position, spawn_choices.surface_name, spawn_choices.moat) + + + -- Queue spawn generation for the requesting buddy FIRST. (left) + local delayed_spawn = GenerateNewSpawn(requesting_buddy_name, spawn_choices.surface_name, spawn_position, spawn_choices, true) + QueuePlayerForSpawn(requesting_buddy_name, delayed_spawn) + + -- ORDER MATTERS! Otherwise sometimes chunks don't generate properly! + -- Queue spawn generation for the accepting buddy SECOND. (right) + local delayed_spawn = GenerateNewSpawn(player.name, buddy_choices.surface_name, buddy_position, buddy_choices, true) + QueuePlayerForSpawn(player.name, delayed_spawn) + + + SendBroadcastMsg({"", {"oarc-buddies-are-joining", requesting_buddy_name, player.name, spawn_choices.surface_name}, " ", GetGPStext(spawn_choices.surface_name, spawn_position)}) -- Unlock spawn control gui tab SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_TAB_NAME, true) diff --git a/lib/spawn_area_generation.lua b/lib/spawn_area_generation.lua new file mode 100644 index 0000000..980257e --- /dev/null +++ b/lib/spawn_area_generation.lua @@ -0,0 +1,391 @@ +-- -------------------------------------------------------------------------------- +-- -- Resource patch and starting area generation +-- -------------------------------------------------------------------------------- + +---Circle spawn shape (handles land, trees and moat) +---@param surface LuaSurface +---@param unique_spawn OarcUniqueSpawn +---@param chunk_area BoundingBox +---@return nil +function CreateCropCircle(surface, unique_spawn, chunk_area) + -------------------------------------------- + local spawn_general = storage.ocfg.spawn_general + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + + local spawn_pos = unique_spawn.position + local tile_radius = spawn_general.spawn_radius_tiles * spawn_config.radius_modifier + + local fill_tile = "landfill" + if spawn_general.force_tiles then + fill_tile = spawn_config.fill_tile + end + + local moat = unique_spawn.moat + local bridge = storage.ocfg.gameplay.enable_moat_bridging + + local liquid_tile = spawn_config.liquid_tile + local fish_enabled = (liquid_tile == "water") + + local moat_width = storage.ocfg.spawn_general.moat_width_tiles + local tree_width = storage.ocfg.spawn_general.tree_width_tiles + -------------------------------------------- + + local tile_radius_sqr = tile_radius ^ 2 + local moat_radius_sqr = ((tile_radius + moat_width) ^ 2) + local tree_radius_sqr_inner = ((tile_radius - 1 - tree_width) ^ 2) -- 1 less to make sure trees are inside the spawn area + local tree_radius_sqr_outer = ((tile_radius - 1) ^ 2) + + + local dirtTiles = {} + for i = chunk_area.left_top.x, chunk_area.right_bottom.x, 1 do + for j = chunk_area.left_top.y, chunk_area.right_bottom.y, 1 do + -- This ( X^2 + Y^2 ) is used to calculate if something is inside a circle area. + -- We avoid using sqrt for performance reasons. + local distSqr = math.floor((spawn_pos.x - i) ^ 2 + (spawn_pos.y - j) ^ 2) + + -- Fill in all unexpected water (or force grass) + if (distSqr <= tile_radius_sqr) then + if (surface.get_tile(i, j).collides_with("water_tile") or + storage.ocfg.spawn_general.force_tiles) then + table.insert(dirtTiles, { name = fill_tile, position = { i, j } }) + end + end + + -- Fill moat with water. + if (moat) then + if (bridge and ((j == spawn_pos.y - 1) or (j == spawn_pos.y) or (j == spawn_pos.y + 1))) then + -- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating + -- 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 = liquid_tile, position = { i, j } }) + + --5% chance of fish in water + if fish_enabled and (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!) + local tree_entity = spawn_config.tree_entity + if (tree_entity == nil) then return end + + for i = chunk_area.left_top.x, chunk_area.right_bottom.x, 1 do + for j = chunk_area.left_top.y, chunk_area.right_bottom.y, 1 do + local distSqr = math.floor((spawn_pos.x - i) ^ 2 + (spawn_pos.y - j) ^ 2) + if ((distSqr < tree_radius_sqr_outer) and (distSqr > tree_radius_sqr_inner)) then + local pos = surface.find_non_colliding_position(tree_entity, { i, j }, 2, 0.5) + if (pos ~= nil) then + surface.create_entity({ name = tree_entity, position = pos }) + end + end + end + end +end + +---Octagon spawn shape (handles land, trees and moat) (Curtesy of jvmguy) +---@param surface LuaSurface +---@param unique_spawn OarcUniqueSpawn +---@param chunk_area BoundingBox +---@return nil +function CreateCropOctagon(surface, unique_spawn, chunk_area) + -------------------------------------------- + local spawn_general = storage.ocfg.spawn_general + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + + local spawn_pos = unique_spawn.position + local tile_radius = spawn_general.spawn_radius_tiles * spawn_config.radius_modifier + + local fill_tile = "landfill" + if spawn_general.force_tiles then + fill_tile = spawn_config.fill_tile + end + + local moat = unique_spawn.moat + local bridge = storage.ocfg.gameplay.enable_moat_bridging + + local liquid_tile = spawn_config.liquid_tile + local fish_enabled = (liquid_tile == "water") + + local moat_width = storage.ocfg.spawn_general.moat_width_tiles + local tree_width = storage.ocfg.spawn_general.tree_width_tiles + -------------------------------------------- + + local moat_width_outer = tile_radius + moat_width + local tree_distance_inner = tile_radius - tree_width + + local dirtTiles = {} + for i = chunk_area.left_top.x, chunk_area.right_bottom.x, 1 do + for j = chunk_area.left_top.y, chunk_area.right_bottom.y, 1 do + local distVar1 = math.floor(math.max(math.abs(spawn_pos.x - i), math.abs(spawn_pos.y - j))) + local distVar2 = math.floor(math.abs(spawn_pos.x - i) + math.abs(spawn_pos.y - j)) + local distVar = math.max(distVar1, distVar2 * 0.707); + + -- Fill in all unexpected water (or force grass) + if (distVar <= tile_radius) then + if (surface.get_tile(i, j).collides_with("water_tile") or + storage.ocfg.spawn_general.force_tiles) then + table.insert(dirtTiles, { name = fill_tile, position = { i, j } }) + end + end + + -- Fill moat with water + if (moat) then + if (bridge and ((j == spawn_pos.y - 1) or (j == spawn_pos.y) or (j == spawn_pos.y + 1))) then + -- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating + -- land connections if the spawn is on or near land. + elseif ((distVar > tile_radius) and (distVar <= moat_width_outer)) then + table.insert(dirtTiles, { name = liquid_tile, position = { i, j } }) + + --5% chance of fish in water + if fish_enabled and (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!) + local tree_entity = spawn_config.tree_entity + if (tree_entity == nil) then return end + + --Create trees (needs to be done after setting tiles!) + for i = chunk_area.left_top.x, chunk_area.right_bottom.x, 1 do + for j = chunk_area.left_top.y, chunk_area.right_bottom.y, 1 do + local distVar1 = math.floor(math.max(math.abs(spawn_pos.x - i), math.abs(spawn_pos.y - j))) + local distVar2 = math.floor(math.abs(spawn_pos.x - i) + math.abs(spawn_pos.y - j)) + local distVar = math.max(distVar1, distVar2 * 0.707); + + if ((distVar < tile_radius) and (distVar >= tree_distance_inner)) then + local pos = surface.find_non_colliding_position(tree_entity, { i, j }, 2, 0.5) + if (pos ~= nil) then + surface.create_entity({ name = tree_entity, position = pos }) + end + end + end + end +end + +---Square spawn shape (handles land, trees and moat) +---@param surface LuaSurface +---@param unique_spawn OarcUniqueSpawn +---@param chunk_area BoundingBox +---@return nil +function CreateCropSquare(surface, unique_spawn, chunk_area) + -------------------------------------------- + local spawn_general = storage.ocfg.spawn_general + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + + local spawn_pos = unique_spawn.position + local tile_radius = spawn_general.spawn_radius_tiles * spawn_config.radius_modifier + + local fill_tile = "landfill" + if spawn_general.force_tiles then + fill_tile = spawn_config.fill_tile + end + + local moat = unique_spawn.moat + local bridge = storage.ocfg.gameplay.enable_moat_bridging + + local liquid_tile = spawn_config.liquid_tile + local fish_enabled = (liquid_tile == "water") + + local moat_width = storage.ocfg.spawn_general.moat_width_tiles + local tree_width = storage.ocfg.spawn_general.tree_width_tiles + -------------------------------------------- + + local moat_width_outer = tile_radius + moat_width + local tree_distance_inner = tile_radius - tree_width + + local dirtTiles = {} + for i = chunk_area.left_top.x, chunk_area.right_bottom.x, 1 do + for j = chunk_area.left_top.y, chunk_area.right_bottom.y, 1 do + -- Max distance from center (either x or y) + local max_distance = math.max(math.abs(spawn_pos.x - i), math.abs(spawn_pos.y - j)) + + -- Fill in all unexpected water (or force grass) + if (max_distance <= tile_radius) then + if (surface.get_tile(i, j).collides_with("water_tile") or + storage.ocfg.spawn_general.force_tiles) then + table.insert(dirtTiles, { name = fill_tile, position = { i, j } }) + end + end + + -- Fill moat with water + if (moat) then + if (bridge and ((j == spawn_pos.y - 1) or (j == spawn_pos.y) or (j == spawn_pos.y + 1))) then + -- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating + -- land connections if the spawn is on or near land. + elseif ((max_distance > tile_radius) and (max_distance <= moat_width_outer)) then + table.insert(dirtTiles, { name = liquid_tile, position = { i, j } }) + + --5% chance of fish in water + if fish_enabled and (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!) + local tree_entity = spawn_config.tree_entity + if (tree_entity == nil) then return end + + --Create trees (needs to be done after setting tiles!) + for i = chunk_area.left_top.x, chunk_area.right_bottom.x, 1 do + for j = chunk_area.left_top.y, chunk_area.right_bottom.y, 1 do + local max_distance = math.max(math.abs(spawn_pos.x - i), math.abs(spawn_pos.y - j)) + if ((max_distance < tile_radius) and (max_distance >= tree_distance_inner)) then + local pos = surface.find_non_colliding_position(tree_entity, { i, j }, 2, 0.5) + if (pos ~= nil) then + surface.create_entity({ name = tree_entity, position = pos }) + end + end + end + end +end + +---Add a circle of water +---@param surface LuaSurface +---@param centerPos MapPosition +---@param chunkArea BoundingBox +---@param tileRadius number +---@param moatTile string +---@param bridge boolean +---@param shape SpawnShapeChoice +---@return nil +function CreateMoat(surface, centerPos, chunkArea, tileRadius, moatTile, bridge, shape) + local tileRadSqr = tileRadius ^ 2 + + local 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 + if (bridge and ((j == centerPos.y - 1) or (j == centerPos.y) or (j == centerPos.y + 1))) then + -- This will leave the tiles "as is" on the left and right of the spawn which has the effect of creating + -- land connections if the spawn is on or near land. + else + -- This ( X^2 + Y^2 ) is used to calculate if something + -- is inside a circle area. + local distVar = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2) + + -- Create a circle of water + if ((distVar < tileRadSqr + (1500 * storage.ocfg.spawn_general.moat_width_tiles)) and + (distVar > tileRadSqr)) then + table.insert(tiles, { name = moatTile, position = { i, j } }) + end + end + end + end + + surface.set_tiles(tiles) +end + +-- Create a horizontal line of tiles (typically used for water) +---@param surface LuaSurface +---@param leftPos TilePosition +---@param length integer +---@param tile_name string +---@return nil +function CreateTileStrip(surface, leftPos, length, tile_name) + local waterTiles = {} + for i = 0, length - 1, 1 do + table.insert(waterTiles, { name = tile_name, position = { leftPos.x + i, leftPos.y } }) + end + surface.set_tiles(waterTiles) +end + +--- Function to generate a resource patch, of a certain size/amount at a pos. +---@param surface LuaSurface +---@param resourceName string +---@param diameter integer +---@param position TilePosition +---@param amount integer +function GenerateResourcePatch(surface, resourceName, diameter, position, amount) + local midPoint = math.floor(diameter / 2) + if (diameter == 0) then + return + end + + -- Right now only 2 shapes are supported. Circle and Square. + local square_shape = (storage.ocfg.spawn_general.resources_shape == RESOURCES_SHAPE_CHOICE_SQUARE) + + for y = -midPoint, midPoint do + for x = -midPoint, midPoint do + -- Either it's a square, or it's a circle so we check if it's inside the circle. + if (square_shape or ((x) ^ 2 + (y) ^ 2 < midPoint ^ 2)) then + surface.create_entity({ + name = resourceName, + amount = amount, + position = { position.x + x, position.y + y } + }) + end + end + end +end + +--- Function to generate a resource patch, of a certain size/amount at a pos. +---@param surface LuaSurface +---@param position MapPosition +---@return nil +function PlaceRandomEntities(surface, position) + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + local random_entities = spawn_config.random_entities + if (random_entities == nil) then return end + + local tree_width = storage.ocfg.spawn_general.tree_width_tiles + local radius = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier - tree_width + + --Iterate through the random entities and place them + for _, entry in pairs(random_entities) do + local entity_name = entry.name + + for i = 1, entry.count do + local random_pos = GetRandomPointWithinCircle(radius, position) + local open_pos = surface.find_non_colliding_position(entity_name, random_pos, tree_width, 0.5) + + if (open_pos ~= nil) then + surface.create_entity({ + name = entity_name, + position = open_pos + }) + end + end + end +end + +--- Randomly place lightning attractors specific for Fulgora. This should space them out so they don't overlap too much. +---@param surface LuaSurface +---@param position MapPosition +---@return nil +function PlaceFulgoranLightningAttractors(surface, position, count) + local spawn_config = storage.ocfg.surfaces_config[surface.name].spawn_config + local radius = storage.ocfg.spawn_general.spawn_radius_tiles * spawn_config.radius_modifier + + -- HARDCODED FOR NOW + local ATTRACTOR_NAME = "fulgoran-ruin-attractor" + local ATTRACTOR_RADIUS = 20 + + --Iterate through and place them and use the largest available entity + for i = 1, count do + local random_pos = GetRandomPointWithinCircle(radius, position) + local open_pos = surface.find_non_colliding_position("crash-site-spaceship", random_pos, 1, 0.5) + + if (open_pos ~= nil) then + surface.create_entity({ + name = ATTRACTOR_NAME, + position = open_pos, + force = "player" -- Same as native game + }) + end + end +end diff --git a/locale/de/locale.cfg b/locale/de/locale.cfg index b9fcacf..c31e780 100644 --- a/locale/de/locale.cfg +++ b/locale/de/locale.cfg @@ -329,13 +329,13 @@ oarc-fluid-count=Anzahl oarc-fluid-amount=Menge oarc-misc-config=Sonstiges-Konfiguration -oarc-water=Wasser -oarc-water-length=Wasserlänge -oarc-water-length-tooltip=Dies ist die Länge in Kacheln des Wasserstreifens um den Spawn-Bereich. -oarc-water-x-offset=Wasser X-Versatz -oarc-water-x-offset-tooltip=Dies ist der X-Versatz in Kacheln, gemessen vom Norden des Spawn-Bereichs. -oarc-water-y-offset=Wasser Y-Versatz -oarc-water-y-offset-tooltip=Dies ist der Y-Versatz in Kacheln, gemessen vom Norden des Spawn-Bereichs. +oarc-liquid=Wasser +oarc-liquid-length=Wasserlänge +oarc-liquid-length-tooltip=Dies ist die Länge in Kacheln des Wasserstreifens um den Spawn-Bereich. +oarc-liquid-x-offset=Wasser X-Versatz +oarc-liquid-x-offset-tooltip=Dies ist der X-Versatz in Kacheln, gemessen vom Norden des Spawn-Bereichs. +oarc-liquid-y-offset=Wasser Y-Versatz +oarc-liquid-y-offset-tooltip=Dies ist der Y-Versatz in Kacheln, gemessen vom Norden des Spawn-Bereichs. oarc-shared-chest=Gemeinsame Truhe oarc-chest-x-offset=Truhe X-Versatz diff --git a/locale/en/locale-mod-settings.cfg b/locale/en/locale-mod-settings.cfg index 32b8aa6..8147cf4 100644 --- a/locale/en/locale-mod-settings.cfg +++ b/locale/en/locale-mod-settings.cfg @@ -2,7 +2,7 @@ oarc-mod=This is a multiplayer mod that allows every player to create their own spawn point when they join the game. There are a lot of helpful features to ensure that new players can join at anytime in the game or even join other player's spawn areas.\n\n[color=red][font=default-bold]Please check out the github page and discord for more information and support.[/font][/color]\n\n[font=default-small]This USED to be available as a scenario, but now is only provided as a mod. To start a new game with this mod, just use the default freeplay scenario. The scenario included in this mod only provides a template to overwrite the default freeplay scenario. It also provides a way for experienced server hosts to configure settings from a file instead of through the usual mod settings. Please read the control.lua file inside the scenario for notes.[/font] [mod-setting-name] -oarc-mod-default-allow-spawning-on-other-surfaces=Default to allow spawns on other surfaces +oarc-mod-default-enable-secondary-spawns-on-other-surfaces=Enable secondary spawns by default oarc-mod-linked-chest-size=Sharing chest capacity oarc-mod-welcome-msg-title=Welcome message title @@ -30,7 +30,6 @@ oarc-mod-enable-friendly-fire=Enable friendly fire oarc-mod-main-force-name=Main force name oarc-mod-default-surface=Default starting surface -oarc-mod-enable-secondary-spawns=Enable secondary spawns oarc-mod-scale-resources-around-spawns=Scale resources around spawns oarc-mod-modified-enemy-spawning=Scale enemies around spawns @@ -51,7 +50,7 @@ oarc-mod-spawn-general-radius-tiles=Spawn area radius oarc-mod-spawn-general-moat-width-tiles=Spawn moat width oarc-mod-spawn-general-tree-width-tiles=Spawn tree ring width oarc-mod-spawn-general-enable-resources-circle-shape=Spawn resource deposits shape -oarc-mod-spawn-general-enable-force-grass=Force spawn area grass +oarc-mod-spawn-general-enable-force-tiles=Fill spawn area tiles oarc-mod-spawn-general-shape=Spawn area shape oarc-mod-resource-placement-enabled=Starting resource auto placement @@ -65,7 +64,7 @@ oarc-mod-resource-placement-size-multiplier=Starting resource size multiplier oarc-mod-resource-placement-amount-multiplier=Starting resource amount multiplier [mod-setting-description] -oarc-mod-default-allow-spawning-on-other-surfaces=This controls the default starting setting for whether to allow spawning on other surfaces. If enabled, by default all other surfaces will be available for players to spawn on. [color=red]If you have other mods installed that add additional surfaces, I recommend leaving this disabled. Regardless of this setting, you can configure which surfaces allow spawning using the in game settings menu.[/color] +oarc-mod-default-enable-secondary-spawns-on-other-surfaces=[color=red]SECONDARY SPAWNS ARE STILL EXPERIMENTAL AND NOT FULLY SUPPORTED!!!![/color] This controls the default behavior for whether other planets/surfaces allow secondary spawns. If enabled, by default all other surfaces will generate secondary spawns when players land on them. Secondary spawns mean that every player who starts their own base, gets their own landing site on the other planets/surfaces. [color=red]If you have other mods installed that add additional surfaces, I recommend leaving this disabled. Regardless of this setting, you can configure which surfaces allow spawning using the in game settings menu.[/color] oarc-mod-linked-chest-size=This is the size of the shared chest that players can use to share items with other players. This is only meaningful if the shared chest feature is enabled. oarc-mod-welcome-msg-title=This is the title of the welcome message that will be displayed to players when they join the game. @@ -93,7 +92,6 @@ oarc-mod-enable-friendly-fire=Enables friendly fire. So you can shoot your chest oarc-mod-main-force-name=The name of the main force. This is the default team that is created when the game starts. oarc-mod-default-surface=The default surface that players will spawn on if they join the main team or if spawning on other surfaces is not enabled. -oarc-mod-enable-secondary-spawns=Enabling this will provide players with a secondary spawn point when they first move to a new surface/planet. This is only applicable if the other surface is enabled for separate spawns. oarc-mod-scale-resources-around-spawns=This scales resources around every spawn area so far away spawns aren't immediately next to very rich deposits. oarc-mod-modified-enemy-spawning=This scales the enemy spawning globally based on the allowed spawn distances to avoid every spawn being surrounded by behemoth worms. @@ -114,7 +112,7 @@ oarc-mod-spawn-general-radius-tiles=This is the radius of the spawn area in tile oarc-mod-spawn-general-moat-width-tiles=This is the width of the moat around the spawn area in tiles, if a moat is enabled and selected at spawn time. oarc-mod-spawn-general-tree-width-tiles=This is the width of the tree ring around the spawn area in tiles. It guarantees some trees near to spawn. oarc-mod-spawn-general-enable-resources-circle-shape=This is the shape of the starting area resource deposits. -oarc-mod-spawn-general-enable-force-grass=Enabling this will make the entire spawn area pure grass. Disabling will use landfill as needed instead. +oarc-mod-spawn-general-enable-force-tiles=Enabling this will fill the entire spawn area with a specific tile (grass on Nauvis). Otherwise, landfill will be used as needed to fill in any gaps instead. oarc-mod-spawn-general-shape=This is the shape of the spawn area. oarc-mod-resource-placement-enabled=You should leave this enabled unless you are manually specifying resource placements in the custom scenario! diff --git a/locale/en/locale.cfg b/locale/en/locale.cfg index d7338a1..7dc22f6 100644 --- a/locale/en/locale.cfg +++ b/locale/en/locale.cfg @@ -241,7 +241,7 @@ oarc-settings-tab-description=This tab contains the same mod settings in the mod 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=Enable or Disable Surface Features 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. -oarc-settings-tab-surface-secondary-checkbox-tooltip=Enabling this will give players custom secondary spawns when they first travel to this surface. [color=red]This feature is currently a work in progress.[/color] +oarc-settings-tab-surface-secondary-checkbox-tooltip=Enabling this will give players custom secondary spawns when they first travel to this surface. [color=red]This feature is currently a work in progress. If you enable this AFTER landing on a new planet, the next time you travel in a rocket you will be sent to a new spawn![/color] oarc-settings-tab-surface-regrowth-checkbox-tooltip=Enabling this will allow the regrowth and world eater features to work on this surface, if those are enabled. oarc-settings-tab-surface-column-header=Surface oarc-settings-tab-surface-spawning-enabled=Home @@ -341,13 +341,13 @@ oarc-fluid-count=Count oarc-fluid-amount=Amount oarc-misc-config=Misc Config -oarc-water=Water -oarc-water-length=Water Length -oarc-water-length-tooltip=This is the length in tiles of the water strip around the spawn area. -oarc-water-x-offset=Water X Offset -oarc-water-x-offset-tooltip=This is the x_offset in tiles, from the north of the spawn area. -oarc-water-y-offset=Water Y Offset -oarc-water-y-offset-tooltip=This is the y_offset in tiles, from the north of the spawn area. +oarc-liquid=Liquid +oarc-liquid-length=Liquid Length +oarc-liquid-length-tooltip=This is the length in tiles of the liquid strip in the spawn area. Only placed if there is no moat. +oarc-liquid-x-offset=Liquid X Offset +oarc-liquid-x-offset-tooltip=This is the x_offset in tiles, from the north of the spawn area. +oarc-liquid-y-offset=Liquid Y Offset +oarc-liquid-y-offset-tooltip=This is the y_offset in tiles, from the north of the spawn area. oarc-shared-chest=Shared Chest oarc-chest-x-offset=Chest X Offset @@ -370,8 +370,9 @@ oarc-coins-available=Coins Available: __1__ [item=coin] oarc-broke-message=You're broke! Go kill some enemies or beg for change... oarc-character-invalid=Your character needs to be valid to use this! Make sure you are not inside a vehicle or dead! -oarc-teleport-fail=Could not find a safe place to teleport the cargo pod! -oarc-teleport-success=Teleported cargo pod to you! +oarc-teleport-cargo-pod-fail=Could not find a safe place to teleport the cargo pod near you! +oarc-teleport-cargo-pod-success=Teleported cargo pod to you! +oarc-no-cargo-pods=No cargo pods found! oarc-cleanup-30sec-warning=Map cleanup in 30 seconds... Unused and old map chunks will be deleted! oarc-cleanup-force-30sec-warning=Map cleanup (forced) in 30 seconds... Unused and old map chunks will be deleted! @@ -386,13 +387,20 @@ oarc-auto-decon-coins-tooltip=Automatically marks coins dropped by enemies for d oarc-player-self-reset-caption=Player Self Reset oarc-player-self-reset-tooltip=Allow players to reset themselves in the spawn controls. oarc-scale-spawner-damage-caption=Scale Spawner Damage -oarc-scale-spawner-damage-tooltip=Scales damage done to spawners with evolution factor and distance to cloest spawn point. This helps compensate for spawner health scaling at a high evolution factor. +oarc-scale-spawner-damage-tooltip=Scales damage done to spawners with evolution factor and distance to closest spawn point. This helps compensate for spawner health scaling at a high evolution factor. oarc-teams-both-disabled-msg=Invalid setting! Both main force and separate teams are disabled! Enabling main force. oarc-spawn-distance-invalid-msg=Invalid setting! Near spawn min distance is greater than or equal to near spawn max distance! oarc-world-eater-invalid-msg=Invalid setting! World eater is enabled but regrowth is not! Disabling world eater. oarc-default-surface-invalid-msg=Invalid setting! Default surface does not exist! Setting to nauvis. +oarc-research-finished=Team __1__ has finished researching __2__! + +oarc-command-dude-wheres-my-cargo-pod=This will attempt to teleport any cargo-pods from the map center to you if they are on the same force as you. This is meant to be a temporary workaround until I find a way to send cargo pods directly to a new spawn. +oarc-command-reroll-spawn=This will reroll your spawn (on whichever surface you are currently on). This is useful if you are stuck in a bad spot or want to try a different location. [color=red]Spam this at your own peril![/color] +oarc-no-reroll-buddy-spawn=I haven't implemethed spawn rerolls for buddy spawns yet! You can try leaving and rejoining the game to get a new buddy spawn. +oarc-spawn-rerolled=__1__ has rerolled their spawn point! + [entity-name] oarc-linked-chest=OARC Linked Chest oarc-linked-power=OARC Linked Power diff --git a/locale/zh-CN/locale-mod-settings.cfg b/locale/zh-CN/locale-mod-settings.cfg index aab53f0..fc6f38e 100644 --- a/locale/zh-CN/locale-mod-settings.cfg +++ b/locale/zh-CN/locale-mod-settings.cfg @@ -2,7 +2,7 @@ oarc-mod=这是一个多人模组,允许每个玩家在加入游戏时创建自己的出生岛。这个模组提供了许多实用功能,确保新玩家可以随时加入游戏,甚至可以加入其他玩家的出生区域。\n\n[color=red][font=default-bold]请查看GitHub页面和Discord、群 289362487 获取更多信息和支持。[/font][/color]\n\n[font=default-small]这个模组曾经以场景的形式提供,现在仅作为模组提供。要使用此模组开始新游戏,只需使用标准模式即可。模组中包含的场景仅提供一个覆盖默认自由游戏场景的模板。它还为经验丰富的服务器老司机管理员提供了通过文件而非常规模组设置来配置设置的方法。请阅读场景中的control.lua文件以获取更多说明。[/font] [mod-setting-name] -oarc-mod-default-allow-spawning-on-other-surfaces=默认允许在其他空间(星球)出生 +oarc-mod-default-enable-secondary-spawns-on-other-surfaces=默认允许在其他空间(星球)出生 oarc-mod-linked-chest-size=共享箱子容量 oarc-mod-welcome-msg-title=欢迎消息标题 @@ -30,7 +30,6 @@ oarc-mod-enable-friendly-fire=启用友军伤害 oarc-mod-main-force-name=主队伍名称 oarc-mod-default-surface=默认起始空间(星球) -oarc-mod-enable-secondary-spawns=启用次要基地 oarc-mod-scale-resources-around-spawns=根据基地缩放资源 oarc-mod-modified-enemy-spawning=根据基地缩放敌人 @@ -51,7 +50,7 @@ oarc-mod-spawn-general-radius-tiles=基地半径 oarc-mod-spawn-general-moat-width-tiles=基地护城河宽度 oarc-mod-spawn-general-tree-width-tiles=基地树木环宽度 oarc-mod-spawn-general-enable-resources-circle-shape=出生资源存放形状 -oarc-mod-spawn-general-enable-force-grass=强制基地为草地 +oarc-mod-spawn-general-enable-force-tiles=强制基地为草地 oarc-mod-spawn-general-shape=基地形状 oarc-mod-resource-placement-enabled=起始资源自动放置 @@ -65,7 +64,7 @@ oarc-mod-resource-placement-size-multiplier=起始资源大小倍数 oarc-mod-resource-placement-amount-multiplier=起始资源数量倍数 [mod-setting-description] -oarc-mod-default-allow-spawning-on-other-surfaces=这控制了是否允许在其他空间(星球)出生的默认起始设置。如果启用,默认情况下所有其他空间(星球)都将可供玩家出生。[color=red]如果您安装了添加额外空间(星球)的其他模组,我建议禁用此选项。无论此设置如何,您都可以通过游戏内设置菜单配置哪些空间(星球)允许出生。[/color] +oarc-mod-default-enable-secondary-spawns-on-other-surfaces=这控制了是否允许在其他空间(星球)出生的默认起始设置。如果启用,默认情况下所有其他空间(星球)都将可供玩家出生。[color=red]如果您安装了添加额外空间(星球)的其他模组,我建议禁用此选项。无论此设置如何,您都可以通过游戏内设置菜单配置哪些空间(星球)允许出生。[/color] oarc-mod-linked-chest-size=这是玩家可用于与其他玩家共享物品的共享箱子大小。仅在启用共享箱子功能时有意义。 oarc-mod-welcome-msg-title=这是玩家加入游戏时将显示的欢迎消息标题。 oarc-mod-welcome-msg=这是玩家加入游戏时和信息面板中将显示的欢迎消息。[color=red]留一个空格以禁用。[/color] @@ -88,7 +87,6 @@ oarc-mod-number-of-players-per-shared-spawn=可以加入共享基地的玩家数 oarc-mod-enable-friendly-fire=启用友军伤害。这样您可以射击自己的箱子(或用火车碾压朋友)。这允许您对自己的队伍造成伤害。 oarc-mod-main-force-name=主队伍的名称。这是游戏开始时创建的默认队伍。 oarc-mod-default-surface=如果玩家加入主队伍或不允许在其他空间(星球)出生,他们将在此默认空间(星球)出生。 -oarc-mod-enable-secondary-spawns=启用此选项将在玩家首次移动到新空间/星球时提供次要基地。仅在允许其他空间(星球)单独出生时适用。 oarc-mod-scale-resources-around-spawns=这会根据每个出生区域缩放资源,以避免远处的基地紧邻非常丰富的矿藏。 oarc-mod-modified-enemy-spawning=根据允许的出生距离全局缩放敌人生成,以避免每个基地都被巨型蠕虫包围。 oarc-mod-minimum-online-time=玩家在离开前必须在线的最短时间,否则他们的出生区域将被清理。 @@ -104,7 +102,7 @@ oarc-mod-spawn-general-radius-tiles=这是出生区域的半径(以图块为 oarc-mod-spawn-general-moat-width-tiles=如果启用并在出生时选择了护城河,这是出生区域周围护城河的宽度(以图块为单位)。 oarc-mod-spawn-general-tree-width-tiles=这是出生区域周围树木环的宽度(以图块为单位)。它保证基地附近有一些树木。 oarc-mod-spawn-general-enable-resources-circle-shape=这是起始区域资源存放的形状。 -oarc-mod-spawn-general-enable-force-grass=启用此选项将使整个出生区域变成纯草地。禁用将根据需要使用填海造地。 +oarc-mod-spawn-general-enable-force-tiles=启用此选项将使整个出生区域变成纯草地。禁用将根据需要使用填海造地。 oarc-mod-spawn-general-shape=这是出生区域的形状。 oarc-mod-resource-placement-enabled=除非您在自定义场景中手动指定资源放置,否则应保持此项启用! oarc-mod-resource-placement-distance-to-edge=这是资源放置距离出生区域边缘的距离。仅适用于圆形/八边形形状的基地。 diff --git a/locale/zh-CN/locale.cfg b/locale/zh-CN/locale.cfg index f16a161..120548a 100644 --- a/locale/zh-CN/locale.cfg +++ b/locale/zh-CN/locale.cfg @@ -343,13 +343,13 @@ oarc-fluid-amount=量 oarc-misc-config=杂项配置 -oarc-water=水 -oarc-water-length=水的长度 -oarc-water-length-tooltip=这是基地周围水带的长度(单位:格)。 -oarc-water-x-offset=水的 X 偏移 -oarc-water-x-offset-tooltip=这是从基地北部的 x 轴偏移(单位:格)。 -oarc-water-y-offset=水的 Y 偏移 -oarc-water-y-offset-tooltip=这是从基地北部的 y 轴偏移(单位:格)。 +oarc-liquid=水 +oarc-liquid-length=水的长度 +oarc-liquid-length-tooltip=这是基地周围水带的长度(单位:格)。 +oarc-liquid-x-offset=水的 X 偏移 +oarc-liquid-x-offset-tooltip=这是从基地北部的 x 轴偏移(单位:格)。 +oarc-liquid-y-offset=水的 Y 偏移 +oarc-liquid-y-offset-tooltip=这是从基地北部的 y 轴偏移(单位:格)。 oarc-shared-chest=共享箱 oarc-chest-x-offset=箱的 X 偏移 @@ -372,8 +372,8 @@ oarc-coins-available=可用金币:__1__ [item=coin] oarc-broke-message=你破产了!去杀些敌人或讨些零钱吧…… oarc-character-invalid=要使用此功能,您的角色必须有效!确保您不在任何载具内或处于死亡状态! -oarc-teleport-fail=找不到安全的地方来传送货舱! -oarc-teleport-success=货舱已传送到您身边! +oarc-teleport-cargo-pod-fail=找不到安全的地方来传送货舱! +oarc-teleport-cargo-pod-success=货舱已传送到您身边! oarc-cleanup-30sec-warning=地图清理将在30秒后开始... 未使用和过期的地图区块将被删除! oarc-cleanup-force-30sec-warning=地图强制清理将在30秒后开始... 未使用和过期的地图区块将被删除! diff --git a/migrations/oarc-mod-V2.1.10.lua b/migrations/oarc-mod-V2.1.10.lua new file mode 100644 index 0000000..6872d5a --- /dev/null +++ b/migrations/oarc-mod-V2.1.10.lua @@ -0,0 +1,61 @@ +-- Migrate the force_grass to force_tiles new setting! +if storage.ocfg.spawn_general.force_tiles == nil then + storage.ocfg.spawn_general.force_tiles = true + log("Updating spawn_general config with new 'force_tiles' setting.") +end + +-- Make sure fulgora config is set up if it is missing. +if script.active_mods["space-age"] ~= nil then + if (storage.ocfg.surfaces_config["fulgora"] == nil) or + (storage.ocfg.surfaces_config["fulgora"].spawn_config.fill_tile == nil) then + storage.ocfg.surfaces_config["fulgora"] = + { + spawn_config = FULGORA_SPAWN_CONFIG, + starting_items = FULGORA_STARTER_ITEMS + } + log("Updating fulgora config with new spawn_config and starting_items.") + end +end + +-- Refresh the Nauvis config if it is missing the new settings: +if storage.ocfg.surfaces_config["nauvis"].spawn_config.fill_tile == nil then + storage.ocfg.surfaces_config["nauvis"] = { + spawn_config = NAUVIS_SPAWN_CONFIG, + starting_items = NAUVIS_STARTER_ITEMS + } + log("Updating nauvis config with new spawn_config and starting_items.") +end + +-- New startup setting that is also cached in storage. +if storage.ocfg.gameplay.default_enable_secondary_spawns_on_other_surfaces == nil then + storage.ocfg.gameplay.default_enable_secondary_spawns_on_other_surfaces = false + log("Updating gameplay config with new default_enable_secondary_spawns_on_other_surfaces setting.") +end + +--Make sure new planets get init'd. No harm in running this multiple times. +SeparateSpawnsInitPlanets() + +-- Block spam. Highhly requested. +game.technology_notifications_enabled = false +log("Disabling technology notifications.") + +-- New global teleport queue for nil characters. +if storage.nil_character_teleport_queue == nil then + storage.nil_character_teleport_queue = {} +end + +-- Make sure all existing spawns have the generated status set. +for surface_index, spawns in pairs(storage.unique_spawns) do + for player_index, spawn in pairs(spawns) do + if spawn.generated == nil then + spawn.generated = true + end + end +end + +-- Make sure any existing players current surface is tracked +for _, player in pairs(game.players) do + if player.character then + storage.player_surfaces[player.name] = player.character.surface.name + end +end \ No newline at end of file diff --git a/settings.lua b/settings.lua index d16dc11..cd5ecc5 100644 --- a/settings.lua +++ b/settings.lua @@ -1,7 +1,7 @@ data:extend({ { type = "bool-setting", - name = "oarc-mod-default-allow-spawning-on-other-surfaces", + name = "oarc-mod-default-enable-secondary-spawns-on-other-surfaces", setting_type = "startup", default_value = false, order = "a1" @@ -177,13 +177,6 @@ data:extend({ default_value = "nauvis", order = "e2" }, - { - type = "bool-setting", - name = "oarc-mod-enable-secondary-spawns", - setting_type = "runtime-global", - default_value = false, - order = "e3" - }, { type = "bool-setting", @@ -328,9 +321,9 @@ data:extend({ }, { type = "bool-setting", - name = "oarc-mod-spawn-general-enable-force-grass", + name = "oarc-mod-spawn-general-enable-force-tiles", setting_type = "runtime-global", - default_value = false, + default_value = true, order = "h5" }, { type = "string-setting",