From bd337e17e9024f285b494b5136b57a79d7817faf Mon Sep 17 00:00:00 2001 From: Oarcinae Date: Sat, 10 Aug 2024 19:29:50 -0400 Subject: [PATCH] Saving progress. Copy in old regrowth_map file. Halfway through updating the separate_spawns_guis. --- scenarios/OARC/config.lua | 2 + scenarios/OARC/control.lua | 15 + scenarios/OARC/lib/config_parser.lua | 38 +- scenarios/OARC/lib/oarc_utils.lua | 473 ++++++++-------- scenarios/OARC/lib/regrowth_map.lua | 385 +++++++++++++ scenarios/OARC/lib/separate_spawns.lua | 576 ++++++++++++-------- scenarios/OARC/lib/separate_spawns_guis.lua | 157 +++--- 7 files changed, 1101 insertions(+), 545 deletions(-) create mode 100644 scenarios/OARC/lib/regrowth_map.lua diff --git a/scenarios/OARC/config.lua b/scenarios/OARC/config.lua index 254ab55..33cdd70 100644 --- a/scenarios/OARC/config.lua +++ b/scenarios/OARC/config.lua @@ -259,6 +259,8 @@ OCFG = { spawn_config = { general = { + + ---TODO: Rename land_area_tiles to spawn_circle_size or something more descriptive. -- THIS IS WHAT SETS THE SPAWN CIRCLE SIZE! -- Create a circle of land area for the spawn -- If you make this much bigger than a few chunks, good luck. diff --git a/scenarios/OARC/control.lua b/scenarios/OARC/control.lua index 970db59..ca5eb3e 100644 --- a/scenarios/OARC/control.lua +++ b/scenarios/OARC/control.lua @@ -102,6 +102,21 @@ script.on_event(defines.events.on_chunk_generated, function(event) SeparateSpawnsGenerateChunk(event) end) +---------------------------------------- +-- On Entity Spawned and On Biter Base Built +-- This is where I modify biter spawning based on location and other factors. +---------------------------------------- +script.on_event(defines.events.on_entity_spawned, function(event) + if (global.ocfg.gameplay.oarc_modified_enemy_spawning) then + ModifyEnemySpawnsNearPlayerStartingAreas(event) + end +end) +script.on_event(defines.events.on_biter_base_built, function(event) + if (global.ocfg.gameplay.oarc_modified_enemy_spawning) then + ModifyEnemySpawnsNearPlayerStartingAreas(event) + end +end) + ---------------------------------------- -- Gui Events ---------------------------------------- diff --git a/scenarios/OARC/lib/config_parser.lua b/scenarios/OARC/lib/config_parser.lua index 111bf45..f6f0639 100644 --- a/scenarios/OARC/lib/config_parser.lua +++ b/scenarios/OARC/lib/config_parser.lua @@ -35,10 +35,10 @@ function ReadModSettings() log("Copying mod settings to OCFG table...") -- Copy in the startup settings from the mod settings. - global.ocfg.mod_overlap.enable_main_force = settings.startup["oarc-mod-enable-main-force"].value - global.ocfg.mod_overlap.enable_separate_teams = settings.startup["oarc-mod-enable-separate-teams"].value - global.ocfg.mod_overlap.enable_spawning_on_other_surfaces = settings.startup["oarc-mod-enable-spawning-on-other-surfaces"].value - global.ocfg.mod_overlap.enable_buddy_spawn = settings.startup["oarc-mod-enable-buddy-spawn"].value + global.ocfg.mod_overlap.enable_main_force = settings.startup["oarc-mod-enable-main-force"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_separate_teams = settings.startup["oarc-mod-enable-separate-teams"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_spawning_on_other_surfaces = settings.startup["oarc-mod-enable-spawning-on-other-surfaces"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_buddy_spawn = settings.startup["oarc-mod-enable-buddy-spawn"].value --[[@as boolean]] -- TODO: Vanilla spawn point are not implemented yet. -- settings.startup["oarc-mod-enable-vanilla-spawn-points"].value @@ -46,21 +46,21 @@ function ReadModSettings() -- settings.startup["oarc-mod-vanilla-spawn-point-spacing"].value -- Copy in the global settings from the mod settings. - global.ocfg.mod_overlap.enable_regrowth = settings.global["oarc-mod-enable-regrowth"].value - global.ocfg.mod_overlap.enable_world_eater = settings.global["oarc-mod-enable-world-eater"].value - global.ocfg.mod_overlap.enable_offline_protection = settings.global["oarc-mod-enable-offline-protection"].value - global.ocfg.mod_overlap.enable_shared_team_vision = settings.global["oarc-mod-enable-shared-team-vision"].value - global.ocfg.mod_overlap.enable_shared_team_chat = settings.global["oarc-mod-enable-shared-team-chat"].value - global.ocfg.mod_overlap.enable_shared_spawns = settings.global["oarc-mod-enable-shared-spawns"].value - global.ocfg.mod_overlap.number_of_players_per_shared_spawn = settings.global["oarc-mod-number-of-players-per-shared-spawn"].value - global.ocfg.mod_overlap.enable_abandoned_base_cleanup = settings.global["oarc-mod-enable-abandoned-base-cleanup"].value - global.ocfg.mod_overlap.enable_friendly_fire = settings.global["oarc-mod-enable-friendly-fire"].value - global.ocfg.mod_overlap.enable_allow_moats_around_spawns = settings.global["oarc-mod-enable-allow-moats-around-spawns"].value + global.ocfg.mod_overlap.enable_regrowth = settings.global["oarc-mod-enable-regrowth"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_world_eater = settings.global["oarc-mod-enable-world-eater"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_offline_protection = settings.global["oarc-mod-enable-offline-protection"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_shared_team_vision = settings.global["oarc-mod-enable-shared-team-vision"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_shared_team_chat = settings.global["oarc-mod-enable-shared-team-chat"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_shared_spawns = settings.global["oarc-mod-enable-shared-spawns"].value --[[@as boolean]] + global.ocfg.mod_overlap.number_of_players_per_shared_spawn = settings.global["oarc-mod-number-of-players-per-shared-spawn"].value --[[@as integer]] + global.ocfg.mod_overlap.enable_abandoned_base_cleanup = settings.global["oarc-mod-enable-abandoned-base-cleanup"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_friendly_fire = settings.global["oarc-mod-enable-friendly-fire"].value --[[@as boolean]] + global.ocfg.mod_overlap.enable_allow_moats_around_spawns = settings.global["oarc-mod-enable-allow-moats-around-spawns"].value --[[@as boolean]] global.ocfg.mod_overlap.enable_moat_bridging = settings.global["oarc-mod-enable-force-bridges-next-to-moats"].value --[[@as boolean]] - global.ocfg.mod_overlap.minimum_distance_to_existing_chunks = settings.global["oarc-mod-minimum-distance-to-existing-chunks"].value - global.ocfg.mod_overlap.near_spawn_min_distance = settings.global["oarc-mod-near-spawn-min-distance"].value - global.ocfg.mod_overlap.near_spawn_max_distance = settings.global["oarc-mod-near-spawn-max-distance"].value - global.ocfg.mod_overlap.far_spawn_min_distance = settings.global["oarc-mod-far-spawn-min-distance"].value - global.ocfg.mod_overlap.far_spawn_max_distance = settings.global["oarc-mod-far-spawn-max-distance"].value + global.ocfg.mod_overlap.minimum_distance_to_existing_chunks = settings.global["oarc-mod-minimum-distance-to-existing-chunks"].value --[[@as integer]] + global.ocfg.mod_overlap.near_spawn_min_distance = settings.global["oarc-mod-near-spawn-min-distance"].value --[[@as integer]] + global.ocfg.mod_overlap.near_spawn_max_distance = settings.global["oarc-mod-near-spawn-max-distance"].value --[[@as integer]] + global.ocfg.mod_overlap.far_spawn_min_distance = settings.global["oarc-mod-far-spawn-min-distance"].value --[[@as integer]] + global.ocfg.mod_overlap.far_spawn_max_distance = settings.global["oarc-mod-far-spawn-max-distance"].value --[[@as integer]] end diff --git a/scenarios/OARC/lib/oarc_utils.lua b/scenarios/OARC/lib/oarc_utils.lua index d0959b7..0a46450 100644 --- a/scenarios/OARC/lib/oarc_utils.lua +++ b/scenarios/OARC/lib/oarc_utils.lua @@ -68,15 +68,15 @@ MAX_INT32_NEG = -2147483648 ---@param color Color ---@return nil function RenderPermanentGroundText(surface, position, scale, text, color) - rendering.draw_text{text=text, - surface=surface, - target=position, - color=color, - scale=scale, - --Allowed fonts: default-dialog-button default-game compilatron-message-font default-large default-large-semibold default-large-bold heading-1 compi - font="compi", - draw_on_ground=true} -end + rendering.draw_text { text = text, + surface = surface, + target = position, + color = color, + scale = scale, + --Allowed fonts: default-dialog-button default-game compilatron-message-font default-large default-large-semibold default-large-bold heading-1 compi + font = "compi", + draw_on_ground = true } +end ---A standardized helper text that fades out over time ---@param text string @@ -85,14 +85,14 @@ end ---@param ttl number ---@return nil function TemporaryHelperText(text, surface, position, ttl) - local rid = rendering.draw_text{text=text, - surface=surface, - target=position, - color={0.7,0.7,0.7,0.7}, - scale=1, - font="compi", - time_to_live=ttl, - draw_on_ground=false} + local rid = rendering.draw_text { text = text, + surface = surface, + target = position, + color = { 0.7, 0.7, 0.7, 0.7 }, + scale = 1, + font = "compi", + time_to_live = ttl, + draw_on_ground = false } table.insert(global.oarc_renders_fadeout, rid) end @@ -116,13 +116,13 @@ end ---@return nil function FadeoutRenderOnTick() if (global.oarc_renders_fadeout and (#global.oarc_renders_fadeout > 0)) then - for k,rid in pairs(global.oarc_renders_fadeout) do + for k, rid in pairs(global.oarc_renders_fadeout) do if (rendering.is_valid(rid)) then local ttl = rendering.get_time_to_live(rid) if ((ttl > 0) and (ttl < 200)) then local color = rendering.get_color(rid) if (color.a > 0.005) then - rendering.set_color(rid, {r=color.r, g=color.g, b=color.b, a=color.a-0.005}) + rendering.set_color(rid, { r = color.r, g = color.g, b = color.b, a = color.a - 0.005 }) end end else @@ -136,7 +136,7 @@ end ---@param msg LocalisedString ---@return nil function SendBroadcastMsg(msg) - for name,player in pairs(game.connected_players) do + for name, player in pairs(game.connected_players) do player.print(msg) end end @@ -194,9 +194,9 @@ end ---@param T table ---@return integer function TableLength(T) - local count = 0 - for _ in pairs(T) do count = count + 1 end - return count + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count end -- Fisher-Yares shuffle @@ -280,18 +280,18 @@ end ---@param surface LuaSurface function ChartArea(force, position, chunkDist, surface) force.chart(surface, - {{position.x-(CHUNK_SIZE*chunkDist), - position.y-(CHUNK_SIZE*chunkDist)}, - {position.x+(CHUNK_SIZE*chunkDist), - position.y+(CHUNK_SIZE*chunkDist)}}) + { { position.x - (CHUNK_SIZE * chunkDist), + position.y - (CHUNK_SIZE * chunkDist) }, + { position.x + (CHUNK_SIZE * chunkDist), + position.y + (CHUNK_SIZE * chunkDist) } }) end ---Gives the player the respawn items if there are any ---@param player LuaPlayer ---@return nil function GivePlayerRespawnItems(player) - for name,count in pairs(global.ocfg.starting_items.player_respawn_start_items) do - player.insert({name=name, count=count}) + for name, count in pairs(global.ocfg.starting_items.player_respawn_start_items) do + player.insert({ name = name, count = count }) end end @@ -301,8 +301,8 @@ end ---@param player LuaPlayer ---@return nil function GivePlayerStarterItems(player) - for name,count in pairs(global.ocfg.starting_items.player_spawn_start_items) do - player.insert({name=name, count=count}) + for name, count in pairs(global.ocfg.starting_items.player_spawn_start_items) do + player.insert({ name = name, count = count }) end end @@ -400,31 +400,33 @@ function CheckIfInArea(point, area) return false end --- -- Set all forces to ceasefire --- function SetCeaseFireBetweenAllForces() --- for name,team in pairs(game.forces) do --- if name ~= "neutral" and name ~= "enemy" and name ~= global.ocore.abandoned_force then --- for x,y in pairs(game.forces) do --- if x ~= "neutral" and x ~= "enemy" and name ~= global.ocore.abandoned_force then --- team.set_cease_fire(x,true) --- end --- end --- end --- end --- end +---Set all forces to ceasefire +---@return nil +function SetCeaseFireBetweenAllForces() + for name, team in pairs(game.forces) do + if name ~= "neutral" and name ~= "enemy" and name ~= global.ocore.abandoned_force then + for x, y in pairs(game.forces) do + if x ~= "neutral" and x ~= "enemy" and name ~= global.ocore.abandoned_force then + team.set_cease_fire(x, true) + end + end + end + end +end --- -- Set all forces to friendly --- function SetFriendlyBetweenAllForces() --- for name,team in pairs(game.forces) do --- if name ~= "neutral" and name ~= "enemy" and name ~= global.ocore.abandoned_force then --- for x,y in pairs(game.forces) do --- if x ~= "neutral" and x ~= "enemy" and name ~= global.ocore.abandoned_force then --- team.set_friend(x,true) --- end --- end --- end --- end --- end +---Set all forces to friendly +---@return nil +function SetFriendlyBetweenAllForces() + for name, team in pairs(game.forces) do + if name ~= "neutral" and name ~= "enemy" and name ~= global.ocore.abandoned_force then + for x, y in pairs(game.forces) do + if x ~= "neutral" and x ~= "enemy" and name ~= global.ocore.abandoned_force then + team.set_friend(x, true) + end + end + end + end +end -- -- For each other player force, share a chat msg. -- function ShareChatBetweenForces(player, msg) @@ -484,134 +486,150 @@ end -- end -- end --- -- Get a random 1 or -1 --- function RandomNegPos() --- if (math.random(0,1) == 1) then --- return 1 --- else --- return -1 --- end --- end +---Get a random 1 or -1 +---@return number +function RandomNegPos() + if (math.random(0,1) == 1) then + return 1 + else + return -1 + end +end --- -- Create a random direction vector to look in --- function GetRandomVector() --- local randVec = {x=0,y=0} --- while ((randVec.x == 0) and (randVec.y == 0)) do --- randVec.x = math.random(-3,3) --- randVec.y = math.random(-3,3) --- end --- log("direction: x=" .. randVec.x .. ", y=" .. randVec.y) --- return randVec --- end +---Create a random direction vector to look in +---@return MapPosition +function GetRandomVector() + local randVec = {x=0,y=0} + while ((randVec.x == 0) and (randVec.y == 0)) do + randVec.x = math.random(-3,3) + randVec.y = math.random(-3,3) + end + log("direction: x=" .. randVec.x .. ", y=" .. randVec.y) + return randVec +end --- -- Check for ungenerated chunks around a specific chunk --- -- +/- chunkDist in x and y directions --- function IsChunkAreaUngenerated(chunkPos, chunkDist, surface) --- for x=-chunkDist, chunkDist do --- for y=-chunkDist, chunkDist do --- local checkPos = {x=chunkPos.x+x, --- y=chunkPos.y+y} --- if (surface.is_chunk_generated(checkPos)) then --- return false --- end --- end --- end --- return true --- end +---Check for ungenerated chunks around a specific chunk +/- chunkDist in x and y directions +---@param chunkPos MapPosition +---@param chunkDist integer +---@param surface LuaSurface +---@return boolean +function IsChunkAreaUngenerated(chunkPos, chunkDist, surface) + for x=-chunkDist, chunkDist do + for y=-chunkDist, chunkDist do + local checkPos = {x=chunkPos.x+x, + y=chunkPos.y+y} + if (surface.is_chunk_generated(checkPos)) then + return false + end + end + end + return true +end -- Clear out enemies around an area with a certain distance ---@param pos MapPosition ---@param safeDist number ---@param surface LuaSurface function ClearNearbyEnemies(pos, safeDist, surface) - local safeArea = {left_top= - {x=pos.x-safeDist, - y=pos.y-safeDist}, - right_bottom= - {x=pos.x+safeDist, - y=pos.y+safeDist}} + local safeArea = { + left_top = + { + x = pos.x - safeDist, + y = pos.y - safeDist + }, + right_bottom = + { + x = pos.x + safeDist, + y = pos.y + safeDist + } + } - for _, entity in pairs(surface.find_entities_filtered{area = safeArea, force = "enemy"}) do + for _, entity in pairs(surface.find_entities_filtered { area = safeArea, force = "enemy" }) do entity.destroy() end end --- -- Function to find coordinates of ungenerated map area in a given direction --- -- starting from the center of the map --- function FindMapEdge(directionVec, surface) --- local position = {x=0,y=0} --- local chunkPos = {x=0,y=0} +---Function to find coordinates of ungenerated map area in a given direction starting from the center of the map +---@param directionVec MapPosition +---@param surface LuaSurface +---@return MapPosition +function FindMapEdge(directionVec, surface) + local position = {x=0,y=0} + local chunkPos = {x=0,y=0} --- -- Keep checking chunks in the direction of the vector --- while(true) do + -- Keep checking chunks in the direction of the vector + while(true) do --- -- Set some absolute limits. --- if ((math.abs(chunkPos.x) > 1000) or (math.abs(chunkPos.y) > 1000)) then --- break + -- Set some absolute limits. + if ((math.abs(chunkPos.x) > 1000) or (math.abs(chunkPos.y) > 1000)) then + break --- -- If chunk is already generated, keep looking --- elseif (surface.is_chunk_generated(chunkPos)) then --- chunkPos.x = chunkPos.x + directionVec.x --- chunkPos.y = chunkPos.y + directionVec.y + -- If chunk is already generated, keep looking + elseif (surface.is_chunk_generated(chunkPos)) then + chunkPos.x = chunkPos.x + directionVec.x + chunkPos.y = chunkPos.y + directionVec.y --- -- Found a possible ungenerated area --- else + -- Found a possible ungenerated area + else --- chunkPos.x = chunkPos.x + directionVec.x --- chunkPos.y = chunkPos.y + directionVec.y + chunkPos.x = chunkPos.x + directionVec.x + chunkPos.y = chunkPos.y + directionVec.y --- -- Check there are no generated chunks in a 10x10 area. --- if IsChunkAreaUngenerated(chunkPos, 10, surface) then --- position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) --- position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) --- break --- end --- end --- end + -- Check there are no generated chunks in a 10x10 area. + if IsChunkAreaUngenerated(chunkPos, 10, surface) then + position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) + position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) + break + end + end + end --- -- log("spawn: x=" .. position.x .. ", y=" .. position.y) --- return position --- end + -- log("spawn: x=" .. position.x .. ", y=" .. position.y) + return position +end --- -- Find random coordinates within a given distance away --- -- maxTries is the recursion limit basically. --- function FindUngeneratedCoordinates(minDistChunks, maxDistChunks, surface) --- local position = {x=0,y=0} --- local chunkPos = {x=0,y=0} +---Find random coordinates within a given distance away maxTries is the recursion limit basically. +---@param minDistChunks integer +---@param maxDistChunks integer +---@param surface LuaSurface +---@return MapPosition +function FindUngeneratedCoordinates(minDistChunks, maxDistChunks, surface) + local position = {x=0,y=0} + local chunkPos = {x=0,y=0} --- local maxTries = 100 --- local tryCounter = 0 + local maxTries = 100 + local tryCounter = 0 --- local minDistSqr = minDistChunks^2 --- local maxDistSqr = maxDistChunks^2 + local minDistSqr = minDistChunks^2 + local maxDistSqr = maxDistChunks^2 --- while(true) do --- chunkPos.x = math.random(0,maxDistChunks) * RandomNegPos() --- chunkPos.y = math.random(0,maxDistChunks) * RandomNegPos() + while(true) do + chunkPos.x = math.random(0,maxDistChunks) * RandomNegPos() + chunkPos.y = math.random(0,maxDistChunks) * RandomNegPos() --- local distSqrd = chunkPos.x^2 + chunkPos.y^2 + local distSqrd = chunkPos.x^2 + chunkPos.y^2 --- -- Enforce a max number of tries --- tryCounter = tryCounter + 1 --- if (tryCounter > maxTries) then --- log("FindUngeneratedCoordinates - Max Tries Hit!") --- break + -- Enforce a max number of tries + tryCounter = tryCounter + 1 + if (tryCounter > maxTries) then + log("FindUngeneratedCoordinates - Max Tries Hit!") + break --- -- Check that the distance is within the min,max specified --- elseif ((distSqrd < minDistSqr) or (distSqrd > maxDistSqr)) then --- -- Keep searching! + -- Check that the distance is within the min,max specified + elseif ((distSqrd < minDistSqr) or (distSqrd > maxDistSqr)) then + -- Keep searching! --- -- Check there are no generated chunks in a 10x10 area. --- elseif IsChunkAreaUngenerated(chunkPos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS, surface) then --- position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) --- position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) --- break -- SUCCESS --- end --- end + -- Check there are no generated chunks in a 10x10 area. + elseif IsChunkAreaUngenerated(chunkPos, global.ocfg.mod_overlap.minimum_distance_to_existing_chunks, surface) then + position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2) + position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2) + break -- SUCCESS + end + end --- log("spawn: x=" .. position.x .. ", y=" .. position.y) --- return position --- end + log("spawn: x=" .. position.x .. ", y=" .. position.y) + return position +end -- -- General purpose function for removing a particular recipe -- function RemoveRecipe(force, recipeName) @@ -650,13 +668,18 @@ end ---@param dist number ---@return BoundingBox function GetAreaAroundPos(pos, dist) - - return {left_top= - {x=pos.x-dist, - y=pos.y-dist}, - right_bottom= - {x=pos.x+dist, - y=pos.y+dist}} + return { + left_top = + { + x = pos.x - dist, + y = pos.y - dist + }, + right_bottom = + { + x = pos.x + dist, + y = pos.y + dist + } + } end -- -- Gets chunk position of a tile. @@ -696,9 +719,9 @@ end ---@param dist number ---@return nil function RemoveInCircle(surface, area, type, pos, dist) - for key, entity in pairs(surface.find_entities_filtered{area=area, type= type}) do + for key, entity in pairs(surface.find_entities_filtered { area = area, type = type }) do if entity.valid and entity and entity.position then - if ((pos.x - entity.position.x)^2 + (pos.y - entity.position.y)^2 < dist^2) then + if ((pos.x - entity.position.x) ^ 2 + (pos.y - entity.position.y) ^ 2 < dist ^ 2) then entity.destroy() end end @@ -809,7 +832,7 @@ end -- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+2, pos.y+1}}) -- table.insert(tiles, {name = "hazard-concrete-left", position = {pos.x+3, pos.y+1}}) -- end - + -- surface.set_tiles(tiles, true) -- end @@ -852,7 +875,7 @@ end ---@param area BoundingBox ---@return nil function RemoveAliensInArea(surface, area) - for _, entity in pairs(surface.find_entities_filtered{area = area, force = "enemy"}) do + for _, entity in pairs(surface.find_entities_filtered { area = area, force = "enemy" }) do entity.destroy() end end @@ -863,8 +886,8 @@ end ---@param reductionFactor integer Reduction factor divides the enemy spawns by that number. 2 = half, 3 = third, etc... ---@return nil function ReduceAliensInArea(surface, area, reductionFactor) - for _, entity in pairs(surface.find_entities_filtered{area = area, force = "enemy"}) do - if (math.random(0,reductionFactor) > 0) then + for _, entity in pairs(surface.find_entities_filtered { area = area, force = "enemy" }) do + if (math.random(0, reductionFactor) > 0) then entity.destroy() end end @@ -878,14 +901,12 @@ end ---@param big_percent integer ---@return nil function DowngradeWormsInArea(surface, area, small_percent, medium_percent, big_percent) - -- Leave out "small-worm-turret" as it's the lowest. - local worm_types = {"medium-worm-turret", "big-worm-turret", "behemoth-worm-turret"} - - for _, entity in pairs(surface.find_entities_filtered{area = area, name = worm_types}) do + local worm_types = { "medium-worm-turret", "big-worm-turret", "behemoth-worm-turret" } + for _, entity in pairs(surface.find_entities_filtered { area = area, name = worm_types }) do -- Roll a number between 0-100 - local rand_percent = math.random(0,100) + local rand_percent = math.random(0, 100) local worm_pos = entity.position local worm_name = entity.name @@ -893,35 +914,34 @@ function DowngradeWormsInArea(surface, area, small_percent, medium_percent, big_ if (rand_percent <= small_percent) then if (not (worm_name == "small-worm-turret")) then entity.destroy() - surface.create_entity{name = "small-worm-turret", position = worm_pos, force = game.forces.enemy} + surface.create_entity { name = "small-worm-turret", position = worm_pos, force = game.forces.enemy } end - -- ELSE If number is less than medium percent, change to small + -- ELSE If number is less than medium percent, change to small elseif (rand_percent <= medium_percent) then if (not (worm_name == "medium-worm-turret")) then entity.destroy() - surface.create_entity{name = "medium-worm-turret", position = worm_pos, force = game.forces.enemy} + surface.create_entity { name = "medium-worm-turret", position = worm_pos, force = game.forces.enemy } end - -- ELSE If number is less than big percent, change to small + -- ELSE If number is less than big percent, change to small elseif (rand_percent <= big_percent) then if (not (worm_name == "big-worm-turret")) then entity.destroy() - surface.create_entity{name = "big-worm-turret", position = worm_pos, force = game.forces.enemy} + surface.create_entity { name = "big-worm-turret", position = worm_pos, force = game.forces.enemy } end - -- ELSE ignore it. + -- ELSE ignore it. end end end - function DowngradeWormsDistanceBasedOnChunkGenerate(event) - if (util.distance({x=0,y=0}, event.area.left_top) < (global.ocfg.near_dist_end*CHUNK_SIZE)) then + if (util.distance({ x = 0, y = 0 }, event.area.left_top) < (global.ocfg.near_dist_end * CHUNK_SIZE)) then DowngradeWormsInArea(event.surface, event.area, 100, 100, 100) - elseif (util.distance({x=0,y=0}, event.area.left_top) < (global.ocfg.far_dist_start*CHUNK_SIZE)) then + elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (global.ocfg.far_dist_start * CHUNK_SIZE)) then DowngradeWormsInArea(event.surface, event.area, 50, 90, 100) - elseif (util.distance({x=0,y=0}, event.area.left_top) < (global.ocfg.far_dist_end*CHUNK_SIZE)) then + elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (global.ocfg.far_dist_end * CHUNK_SIZE)) then DowngradeWormsInArea(event.surface, event.area, 20, 80, 97) else DowngradeWormsInArea(event.surface, event.area, 0, 20, 90) @@ -954,8 +974,8 @@ function RemoveWormsInArea(surface, area, small, medium, big, behemoth) -- Destroy if (TableLength(worm_types) > 0) then - for _, entity in pairs(surface.find_entities_filtered{area = area, name = worm_types}) do - entity.destroy() + for _, entity in pairs(surface.find_entities_filtered { area = area, name = worm_types }) do + entity.destroy() end else log("RemoveWormsInArea had empty worm_types list!") @@ -1214,29 +1234,27 @@ end ---@param fillTile string ---@return nil function CreateCropCircle(surface, centerPos, chunkArea, tileRadius, fillTile) - - local tileRadSqr = tileRadius^2 + local tileRadSqr = tileRadius ^ 2 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 - + for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do + for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do -- This ( X^2 + Y^2 ) is used to calculate if something -- is inside a circle area. - local distSqr = math.floor((centerPos.x - i)^2 + (centerPos.y - j)^2) + local distSqr = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2) -- Fill in all unexpected water in a circle if (distSqr < tileRadSqr) then - if (surface.get_tile(i,j).collides_with("water-tile") or - global.ocfg.spawn_config.general.force_grass) then - table.insert(dirtTiles, {name = fillTile, position ={i,j}}) + if (surface.get_tile(i, j).collides_with("water-tile") or + global.ocfg.spawn_config.general.force_grass) then + table.insert(dirtTiles, { name = fillTile, position = { i, j } }) end end -- Create a circle of trees around the spawn point. - if ((distSqr < tileRadSqr-100) and - (distSqr > tileRadSqr-500)) then - surface.create_entity({name="tree-02", amount=1, position={i, j}}) + if ((distSqr < tileRadSqr - 100) and + (distSqr > tileRadSqr - 500)) then + surface.create_entity({ name = "tree-02", amount = 1, position = { i, j } }) end end end @@ -1252,28 +1270,26 @@ end ---@param fillTile string ---@return nil function CreateCropOctagon(surface, centerPos, chunkArea, tileRadius, fillTile) - 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 - + for i = chunkArea.left_top.x, chunkArea.right_bottom.x, 1 do + for j = chunkArea.left_top.y, chunkArea.right_bottom.y, 1 do local distVar1 = math.floor(math.max(math.abs(centerPos.x - i), math.abs(centerPos.y - j))) local distVar2 = math.floor(math.abs(centerPos.x - i) + math.abs(centerPos.y - j)) - local distVar = math.max(distVar1*1.1, distVar2 * 0.707*1.1); + local distVar = math.max(distVar1 * 1.1, distVar2 * 0.707 * 1.1); -- Fill in all unexpected water in a circle - if (distVar < tileRadius+2) then - if (surface.get_tile(i,j).collides_with("water-tile") or - global.ocfg.spawn_config.general.force_grass or - (game.active_mods["oarc-restricted-build"])) then - table.insert(dirtTiles, {name = fillTile, position ={i,j}}) + if (distVar < tileRadius + 2) then + if (surface.get_tile(i, j).collides_with("water-tile") or + global.ocfg.spawn_config.general.force_grass or + (game.active_mods["oarc-restricted-build"])) then + table.insert(dirtTiles, { name = fillTile, position = { i, j } }) end end -- Create a tree ring if ((distVar < tileRadius) and - (distVar > tileRadius-2)) then - surface.create_entity({name="tree-01", amount=1, position={i, j}}) + (distVar > tileRadius - 2)) then + surface.create_entity({ name = "tree-01", amount = 1, position = { i, j } }) end end end @@ -1289,26 +1305,23 @@ end ---@param bridge boolean ---@return nil function CreateMoat(surface, centerPos, chunkArea, tileRadius, moatTile, bridge) - - local tileRadSqr = tileRadius^2 + 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 + 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) + local distVar = math.floor((centerPos.x - i) ^ 2 + (centerPos.y - j) ^ 2) -- Create a circle of water - if ((distVar < tileRadSqr+(1500*global.ocfg.spawn_config.general.moat_size_modifier)) and - (distVar > tileRadSqr)) then - table.insert(tiles, {name = moatTile, position ={i,j}}) + if ((distVar < tileRadSqr + (1500 * global.ocfg.spawn_config.general.moat_size_modifier)) and + (distVar > tileRadSqr)) then + table.insert(tiles, { name = moatTile, position = { i, j } }) end end end @@ -1323,8 +1336,8 @@ end ---@param length integer function CreateWaterStrip(surface, leftPos, length) local waterTiles = {} - for i=0,length,1 do - table.insert(waterTiles, {name = "water", position={leftPos.x+i,leftPos.y}}) + for i = 0, length, 1 do + table.insert(waterTiles, { name = "water", position = { leftPos.x + i, leftPos.y } }) end surface.set_tiles(waterTiles) end @@ -1336,23 +1349,23 @@ end ---@param position TilePosition ---@param amount integer function GenerateResourcePatch(surface, resourceName, diameter, position, amount) - local midPoint = math.floor(diameter/2) + local midPoint = math.floor(diameter / 2) if (diameter == 0) then return end - for y=-midPoint, midPoint do - for x=-midPoint, midPoint do - if (not global.ocfg.spawn_config.general.resources_circle_shape or ((x)^2 + (y)^2 < midPoint^2)) then - surface.create_entity({name=resourceName, amount=amount, - position={position.x+x, position.y+y}}) + for y = -midPoint, midPoint do + for x = -midPoint, midPoint do + if (not global.ocfg.spawn_config.general.resources_circle_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 - - - -- -------------------------------------------------------------------------------- -- -- Holding pen for new players joining the map -- -------------------------------------------------------------------------------- diff --git a/scenarios/OARC/lib/regrowth_map.lua b/scenarios/OARC/lib/regrowth_map.lua new file mode 100644 index 0000000..25c348f --- /dev/null +++ b/scenarios/OARC/lib/regrowth_map.lua @@ -0,0 +1,385 @@ +-- regrowth_map.lua +-- Aug 2024 + +-- Code tracks all chunks generated and allows for deleting of inactive chunks. +-- +-- Basic rules of regrowth: +-- 1. Area around player is safe for quite a large distance. +-- 2. Chunks with pollution won't be deleted. +-- 3. Chunks with any player buildings won't be deleted. +-- 4. Anything within radar range won't be deleted, but radar MUST be active. +-- -- This works by refreshing all chunk timers within radar range using +-- the on_sector_scanned event. +-- 5. Chunks timeout after 1 hour-ish, configurable + +require("lib/oarc_utils") +require("config") + +-- TODO: Make this a mod startup setting? +REGROWTH_TIMEOUT_TICKS = TICKS_PER_HOUR -- TICKS_PER_HOUR TICKS_PER_MINUTE + +-- Init globals and set player join area to be off limits. +function RegrowthInit() + global.rg = {} + global.rg.player_refresh_index = nil + global.rg.force_removal_flag = -2000 + global.rg.map = {} + global.rg.removal_list = {} + global.rg.chunk_iter = nil + global.rg.world_eater_iter = nil + global.rg.timeout_ticks = REGROWTH_TIMEOUT_TICKS +end + +function TriggerCleanup() + global.rg.force_removal_flag = game.tick +end + +function RegrowthForceRemoveChunksCmd(cmd_table) + if (game.players[cmd_table.player_index].admin) then + TriggerCleanup() + end +end + +-- Get the next player index available +function GetNextPlayerIndex(player_index) + if (not global.rg.player_refresh_index or not game.players[global.rg.player_refresh_index]) then + global.rg.player_refresh_index = 1 + else + global.rg.player_refresh_index = global.rg.player_refresh_index + 1 + end + + if (global.rg.player_refresh_index > #game.players) then + global.rg.player_refresh_index = 1 + end + + return global.rg.player_refresh_index +end + +-- Adds new chunks to the global table to track them. +-- This should always be called first in the chunk generate sequence +-- (Compared to other RSO & Oarc related functions...) +function RegrowthChunkGenerate(event) + local c_pos = GetChunkPosFromTilePos(event.area.left_top) + + -- Surface must be "added" first. + if (global.rg == nil) then return end + + -- If this is the first chunk in that row: + if (global.rg.map[c_pos.x] == nil) then + global.rg.map[c_pos.x] = {} + end + + -- Only update it if it isn't already set! + if (global.rg.map[c_pos.x][c_pos.y] == nil) then + global.rg.map[c_pos.x][c_pos.y] = game.tick + end +end + +-- Mark an area for "immediate" forced removal +function RegrowthMarkAreaForRemoval(pos, chunk_radius) + local c_pos = GetChunkPosFromTilePos(pos) + for i=-chunk_radius,chunk_radius do + local x = c_pos.x+i + for k=-chunk_radius,chunk_radius do + local y = c_pos.y+k + + if (global.rg.map[x] ~= nil) then + global.rg.map[x][y] = nil + end + table.insert(global.rg.removal_list, {pos={x=x,y=y},force=true}) + end + if (table_size(global.rg.map[x]) == 0) then + global.rg.map[x] = nil + end + end +end + +-- Downgrades permanent flag to semi-permanent. +function RegrowthMarkAreaNotPermanentOVERWRITE(pos, chunk_radius) + local c_pos = GetChunkPosFromTilePos(pos) + for i=-chunk_radius,chunk_radius do + local x = c_pos.x+i + for k=-chunk_radius,chunk_radius do + local y = c_pos.y+k + + if (global.rg.map[x] and global.rg.map[x][y] and (global.rg.map[x][y] == -2)) then + global.rg.map[x][y] = -1 + end + end + end +end + +-- Marks a chunk containing a position to be relatively permanent. +function MarkChunkSafe(c_pos, permanent) + if (global.rg.map[c_pos.x] == nil) then + global.rg.map[c_pos.x] = {} + end + + if (permanent) then + global.rg.map[c_pos.x][c_pos.y] = -2 + + -- Make sure we don't overwrite... + elseif (global.rg.map[c_pos.x][c_pos.y] and (global.rg.map[c_pos.x][c_pos.y] ~= -2)) then + global.rg.map[c_pos.x][c_pos.y] = -1 + end +end + +-- Marks a safe area around a CHUNK position to be relatively permanent. +function RegrowthMarkAreaSafeGivenChunkPos(c_pos, chunk_radius, permanent) + if (global.rg == nil) then return end + + for i=-chunk_radius,chunk_radius do + for j=-chunk_radius,chunk_radius do + MarkChunkSafe({x=c_pos.x+i,y=c_pos.y+j}, permanent) + end + end +end + +-- Marks a safe area around a TILE position to be relatively permanent. +function RegrowthMarkAreaSafeGivenTilePos(pos, chunk_radius, permanent) + if (global.rg == nil) then return end + + local c_pos = GetChunkPosFromTilePos(pos) + RegrowthMarkAreaSafeGivenChunkPos(c_pos, chunk_radius, permanent) +end + +-- Refreshes timers on a chunk containing position +function RefreshChunkTimer(pos, bonus_time) + local c_pos = GetChunkPosFromTilePos(pos) + + if (global.rg.map[c_pos.x] == nil) then + global.rg.map[c_pos.x] = {} + end + if (global.rg.map[c_pos.x][c_pos.y] >= 0) then + global.rg.map[c_pos.x][c_pos.y] = game.tick + bonus_time + end +end + +-- Refreshes timers on all chunks around a certain area +function RefreshArea(pos, chunk_radius, bonus_time) + local c_pos = GetChunkPosFromTilePos(pos) + + for i=-chunk_radius,chunk_radius do + local x = c_pos.x+i + for k=-chunk_radius,chunk_radius do + local y = c_pos.y+k + + if (global.rg.map[x] == nil) then + global.rg.map[x] = {} + end + if ((global.rg.map[x][y] == nil) or (global.rg.map[x][y] >= 0)) then + global.rg.map[x][y] = game.tick + bonus_time + end + end + end +end + +-- Refreshes timers on all chunks near an ACTIVE radar +function RegrowthSectorScan(event) + if (event.radar.surface.name ~= GAME_SURFACE_NAME) then return end + + RefreshArea(event.radar.position, 14, 0) +end + +-- Refresh all chunks near a single player. Cyles through all connected players. +function RefreshPlayerArea() + player_index = GetNextPlayerIndex() + if (player_index and game.connected_players[player_index]) then + local player = game.connected_players[player_index] + + if (not player.character) then return end + if (player.character.surface.name ~= GAME_SURFACE_NAME) then return end + + RefreshArea(player.position, 4, 0) + end +end + +-- Gets the next chunk the array map and checks to see if it has timed out. +-- Adds it to the removal list if it has. +function RegrowthSingleStepArray() + + -- Make sure we have a valid iterator! + if (not global.rg.chunk_iter or not global.rg.chunk_iter.valid) then + global.rg.chunk_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks() + end + + local next_chunk = global.rg.chunk_iter() + + -- Check if we reached the end + if (not next_chunk) then + global.rg.chunk_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks() + next_chunk = global.rg.chunk_iter() + end + + -- Do we have it in our map? + if (not global.rg.map[next_chunk.x] or not global.rg.map[next_chunk.x][next_chunk.y]) then + return -- Chunk isn't in our map so we don't care? + end + + -- If the chunk has timed out, add it to the removal list + local c_timer = global.rg.map[next_chunk.x][next_chunk.y] + if ((c_timer ~= nil) and (c_timer >= 0) and ((c_timer + global.rg.timeout_ticks) < game.tick)) then + + -- Check chunk actually exists + if (game.surfaces[GAME_SURFACE_NAME].is_chunk_generated({x=next_chunk.x, y=next_chunk.y})) then + table.insert(global.rg.removal_list, {pos={x=next_chunk.x, y=next_chunk.y}, force=false}) + global.rg.map[next_chunk.x][next_chunk.y] = nil + end + end +end + +-- Remove all chunks at same time to reduce impact to FPS/UPS +function OarcRegrowthRemoveAllChunks() + for key,c_remove in pairs(global.rg.removal_list) do + local c_pos = c_remove.pos + + -- Confirm chunk is still expired + if (not global.rg.map[c_pos.x] or not global.rg.map[c_pos.x][c_pos.y]) then + + -- If it is FORCE removal, then remove it regardless of pollution. + if (c_remove.force) then + game.surfaces[GAME_SURFACE_NAME].delete_chunk(c_pos) + + -- If it is a normal timeout removal, don't do it if there is pollution in the chunk. + elseif (game.surfaces[GAME_SURFACE_NAME].get_pollution({c_pos.x*32,c_pos.y*32}) > 0) then + global.rg.map[c_pos.x][c_pos.y] = game.tick + + -- Else delete the chunk + else + game.surfaces[GAME_SURFACE_NAME].delete_chunk(c_pos) + end + end + + -- Remove entry + global.rg.removal_list[key] = nil + end + + -- MUST GET A NEW CHUNK ITERATOR ON DELETE CHUNK! + global.rg.chunk_iter = nil + global.rg.world_eater_iter = nil +end + +-- This is the main work function, it checks a single chunk in the list +-- per tick. It works according to the rules listed in the header of this +-- file. +function RegrowthOnTick() + + -- Every half a second, refresh all chunks near a single player + -- Cyles through all players. Tick is offset by 2 + if ((game.tick % (30)) == 2) then + RefreshPlayerArea() + end + + -- Every tick, check a few points in the 2d array of the only active surface According to /measured-command this + -- shouldn't take more than 0.1ms on average + for i=1,20 do + RegrowthSingleStepArray() + end + + if (not global.world_eater_disable) then + WorldEaterSingleStep() + end + + -- Allow enable/disable of auto cleanup, can change during runtime. + local interval_ticks = global.rg.timeout_ticks + -- Send a broadcast warning before it happens. + if ((game.tick % interval_ticks) == interval_ticks-(60*30 + 1)) then + if (#global.rg.removal_list > 100) then + SendBroadcastMsg("Map cleanup in 30 seconds... Unused and old map chunks will be deleted!") + end + end + + -- Delete all listed chunks across all active surfaces + if ((game.tick % interval_ticks) == interval_ticks-1) then + if (#global.rg.removal_list > 100) then + OarcRegrowthRemoveAllChunks() + SendBroadcastMsg("Map cleanup done, sorry for your loss.") + end + end +end + +-- This function removes any chunks flagged but on demand. +-- Controlled by the global.rg.force_removal_flag +-- This function may be used outside of the normal regrowth modse. +function RegrowthForceRemovalOnTick() + -- Catch force remove flag + if (game.tick == global.rg.force_removal_flag+60) then + SendBroadcastMsg("Map cleanup (forced) in 30 seconds... Unused and old map chunks will be deleted!") + end + + if (game.tick == global.rg.force_removal_flag+(60*30 + 60)) then + OarcRegrowthRemoveAllChunks() + SendBroadcastMsg("Map cleanup done, sorry for your loss.") + end +end + +function WorldEaterSingleStep() + + -- Make sure we have a valid iterator! + if (not global.rg.world_eater_iter or not global.rg.world_eater_iter.valid) then + global.rg.world_eater_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks() + end + + local next_chunk = global.rg.world_eater_iter() + + -- Check if we reached the end + if (not next_chunk) then + global.rg.world_eater_iter = game.surfaces[GAME_SURFACE_NAME].get_chunks() + next_chunk = global.rg.world_eater_iter() + end + + -- Do we have it in our map? + if (not global.rg.map[next_chunk.x] or not global.rg.map[next_chunk.x][next_chunk.y]) then + return -- Chunk isn't in our map so we don't care? + end + + -- Search for any abandoned radars and destroy them? + local entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=next_chunk.area, + force={global.ocore.abandoned_force}, + name="radar"} + for k,v in pairs(entities) do + v.die(nil) + end + + -- Search for any entities with _DESTROYED_ force and kill them. + entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=next_chunk.area, + force={global.ocore.destroyed_force}} + for k,v in pairs(entities) do + v.die(nil) + end + + -- If the chunk isn't marked permament, then check if we can remove it + local c_timer = global.rg.map[next_chunk.x][next_chunk.y] + if (c_timer == -1) then + + local area = {left_top = {next_chunk.area.left_top.x-8, next_chunk.area.left_top.y-8}, + right_bottom = {next_chunk.area.right_bottom.x+8, next_chunk.area.right_bottom.y+8}} + + local entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area=area, force={"enemy", "neutral"}, invert=true} + local total_count = #entities + local has_last_user_set = false + + if (total_count > 0) then + for k,v in pairs(entities) do + if (v.last_user or (v.type == "character") or string.contains(v.type, "robot")) then + has_last_user_set = true + return -- This means we're done checking this chunk. + end + end + + -- If all entities found have no last user, then KILL all entities! + if (not has_last_user_set) then + for k,v in pairs(entities) do + if (v and v.valid) then + v.die(nil) + end + end + -- SendBroadcastMsg(next_chunk.x .. "," .. next_chunk.y .. " WorldEaterSingleStep - ENTITIES FOUND") + global.rg.map[next_chunk.x][next_chunk.y] = game.tick -- Set the timer on it. + end + else + -- SendBroadcastMsg(next_chunk.x .. "," .. next_chunk.y .. " WorldEaterSingleStep - NO ENTITIES FOUND") + global.rg.map[next_chunk.x][next_chunk.y] = game.tick -- Set the timer on it. + end + end +end diff --git a/scenarios/OARC/lib/separate_spawns.lua b/scenarios/OARC/lib/separate_spawns.lua index 415b78c..4161587 100644 --- a/scenarios/OARC/lib/separate_spawns.lua +++ b/scenarios/OARC/lib/separate_spawns.lua @@ -27,7 +27,7 @@ BUDDY_SPAWN_CHOICE = { ---Contains the respawn point for a player. Usually this is their home base but it can be changed. ---@alias OarcPlayerSpawn { surface: LuaSurface, position: MapPosition } ---Table of [OarcSharedSpawn](lua://OarcSharedSpawn) indexed by player name. ----@alias OarcPlayerSpawnTable table +---@alias OarcPlayerSpawnsTable table ---A unique spawn point. This is what chunk generation checks against. ---@alias OarcUniqueSpawn { surface: LuaSurface, position: MapPosition, moat: boolean } @@ -35,7 +35,7 @@ BUDDY_SPAWN_CHOICE = { ---@alias OarcUniqueSpawnsTable table ---A shared spawn point. This is a spawn point that multiple players can share. ----@alias OarcSharedSpawn { surface: LuaSurface, position: MapPosition, openAccess: boolean, players: string[] } +---@alias OarcSharedSpawn { surface: LuaSurface, position: MapPosition, openAccess: boolean, players: string[], joinQueue: string[] } ---Table of [OarcSharedSpawn](lua://OarcSharedSpawn) indexed by a unique name. ---@alias OarcSharedSpawnsTable table @@ -70,10 +70,13 @@ function InitSpawnGlobalsAndForces() global.ocore = {} end + -- TODO: Add a global for which surfaces allow spawns. + -- This contains each player's spawn point. Literally where they will respawn. -- There is a way in game to change this under one of the little menu features I added. + ---TODO: Add support for multiple surfaces! if (global.ocore.playerSpawns == nil) then - global.ocore.playerSpawns --[[@as OarcPlayerSpawnTable]] = {} + global.ocore.playerSpawns --[[@as OarcPlayerSpawnsTable]] = {} end -- This is the most important table. It is a list of all the unique spawn points. @@ -208,7 +211,8 @@ function CreateNewSharedSpawn(player) surface = global.ocore.playerSpawns[player.name].surface, position = global.ocore.playerSpawns[player.name].position, openAccess = true, - players = {} + players = {}, + joinQueue = {} } end @@ -277,7 +281,6 @@ end ---@param delayedSpawn OarcDelayedSpawn ---@return nil function SendPlayerToNewSpawnAndCreateIt(delayedSpawn) - local ocfg --[[@as OarcConfig]] = global.ocfg -- DOUBLE CHECK and make sure the area is super safe. @@ -285,20 +288,20 @@ function SendPlayerToNewSpawnAndCreateIt(delayedSpawn) -- TODO: Vanilla spawn point are not implemented yet. -- if (not delayedSpawn.vanilla) then - - -- Generate water strip only if we don't have a moat. - if (not delayedSpawn.moat) then - local water_data = ocfg.spawn_config.water - CreateWaterStrip(delayedSpawn.surface, - { x = delayedSpawn.position.x + water_data.x_offset, y = delayedSpawn.position.y + water_data.y_offset }, - water_data.length) - CreateWaterStrip(delayedSpawn.surface, - { x = delayedSpawn.position.x + water_data.x_offset, y = delayedSpawn.position.y + water_data.y_offset + 1 }, - water_data.length) - end - -- Create the spawn resources here - GenerateStartingResources(delayedSpawn.surface, delayedSpawn.position) + -- Generate water strip only if we don't have a moat. + if (not delayedSpawn.moat) then + local water_data = ocfg.spawn_config.water + CreateWaterStrip(delayedSpawn.surface, + { x = delayedSpawn.position.x + water_data.x_offset, y = delayedSpawn.position.y + water_data.y_offset }, + water_data.length) + CreateWaterStrip(delayedSpawn.surface, + { x = delayedSpawn.position.x + water_data.x_offset, y = delayedSpawn.position.y + water_data.y_offset + 1 }, + water_data.length) + end + + -- Create the spawn resources here + GenerateStartingResources(delayedSpawn.surface, delayedSpawn.position) -- end -- Vanilla spawn point are not implemented yet. @@ -380,11 +383,9 @@ end ---@param chunkArea BoundingBox ---@return nil function SetupAndClearSpawnAreas(surface, chunkArea) - local spawn_config --[[@as OarcConfigSpawn]] = global.ocfg.spawn_config - for name,spawn in pairs(global.ocore.uniqueSpawns --[[@as OarcUniqueSpawnsTable]] ) do - + for name, spawn in pairs(global.ocore.uniqueSpawns --[[@as OarcUniqueSpawnsTable]]) do if (spawn.surface ~= surface) then return end @@ -407,13 +408,13 @@ function SetupAndClearSpawnAreas(surface, chunkArea) if (util.distance(spawn.position, chunkAreaCenter) < spawn_config.safe_area.safe_radius) then RemoveAliensInArea(surface, chunkArea) - -- Create a warning area with heavily reduced enemies + -- Create a warning area with heavily reduced enemies elseif (util.distance(spawn.position, chunkAreaCenter) < spawn_config.safe_area.warn_radius) then ReduceAliensInArea(surface, chunkArea, spawn_config.safe_area.warn_reduction) -- DowngradeWormsInArea(surface, chunkArea, 100, 100, 100) RemoveWormsInArea(surface, chunkArea, false, true, true, true) -- remove all non-small worms. - -- Create a third area with moderatly reduced enemies + -- Create a third area with moderatly reduced enemies elseif (util.distance(spawn.position, chunkAreaCenter) < spawn_config.safe_area.danger_radius) then ReduceAliensInArea(surface, chunkArea, spawn_config.safe_area.danger_reduction) -- DowngradeWormsInArea(surface, chunkArea, 50, 100, 100) @@ -422,52 +423,53 @@ function SetupAndClearSpawnAreas(surface, chunkArea) -- TODO: Vanilla spawn point are not implemented yet. -- if (not spawn.vanilla) then - + local enable_test = global.ocfg.mod_overlap.enable_moat_bridging - -- 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) - if CheckIfInArea(chunkAreaCenter, landArea) then - -- Remove trees/resources inside the spawn area - RemoveInCircle(surface, chunkArea, "tree", spawn.position, spawn_config.general.land_area_tiles) - RemoveInCircle(surface, chunkArea, "resource", spawn.position, spawn_config.general.land_area_tiles + 5) - RemoveInCircle(surface, chunkArea, "cliff", spawn.position, spawn_config.general.land_area_tiles + 5) + -- 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) + if CheckIfInArea(chunkAreaCenter, landArea) then + -- Remove trees/resources inside the spawn area + RemoveInCircle(surface, chunkArea, "tree", spawn.position, spawn_config.general.land_area_tiles) + RemoveInCircle(surface, chunkArea, "resource", spawn.position, spawn_config.general.land_area_tiles + 5) + RemoveInCircle(surface, chunkArea, "cliff", spawn.position, spawn_config.general.land_area_tiles + 5) - local fill_tile = "landfill" - if (spawn_config.general.tree_circle) then - CreateCropCircle(surface, spawn.position, chunkArea, spawn_config.general.land_area_tiles, fill_tile) - end - if (spawn_config.general.tree_octagon) then - CreateCropOctagon(surface, spawn.position, chunkArea, spawn_config.general.land_area_tiles, fill_tile) - end - -- TODO: Confirm removal of global setting check of enable_allow_moats_around_spawns is okay? - if (spawn.moat) then - CreateMoat(surface, - spawn.position, - chunkArea, - spawn_config.general.land_area_tiles, - "water", - global.ocfg.mod_overlap.enable_moat_bridging) - end + local fill_tile = "landfill" + if (spawn_config.general.tree_circle) then + CreateCropCircle(surface, spawn.position, chunkArea, spawn_config.general.land_area_tiles, fill_tile) end + if (spawn_config.general.tree_octagon) then + CreateCropOctagon(surface, spawn.position, chunkArea, spawn_config.general.land_area_tiles, fill_tile) + end + -- TODO: Confirm removal of global setting check of enable_allow_moats_around_spawns is okay? + if (spawn.moat) then + CreateMoat(surface, + spawn.position, + chunkArea, + spawn_config.general.land_area_tiles, + "water", + global.ocfg.mod_overlap.enable_moat_bridging) + end + end -- end -- Vanilla spawn point are not implemented yet. end end --- This is the main function that creates the spawn area --- Provides resources, land and a safe zone +---This is the main function that creates the spawn area. Provides resources, land and a safe zone. +---@param event EventData.on_chunk_generated +---@return nil function SeparateSpawnsGenerateChunk(event) local surface = event.surface local chunkArea = event.area -- Modify enemies first. - if global.ocfg.modified_enemy_spawning then + if global.ocfg.gameplay.oarc_modified_enemy_spawning then DowngradeWormsDistanceBasedOnChunkGenerate(event) end -- Downgrade resources near to spawns - if global.ocfg.scale_resources_around_spawns then + if global.ocfg.gameplay.scale_resources_around_spawns then DowngradeResourcesDistanceBasedOnChunkGenerate(surface, chunkArea) end @@ -477,13 +479,16 @@ function SeparateSpawnsGenerateChunk(event) SetupAndClearSpawnAreas(surface, chunkArea) end --- Based on the danger distance, you get full resources, and it is exponential from the spawn point to that distance. +---Based on the danger distance, you get full resources, and it is exponential from the spawn point to that distance. +---@param surface LuaSurface +---@param chunkArea BoundingBox +---@return nil function DowngradeResourcesDistanceBasedOnChunkGenerate(surface, chunkArea) - local closestSpawn = GetClosestUniqueSpawn(chunkArea.left_top) + local closestSpawn = GetClosestUniqueSpawn(surface, chunkArea.left_top) if (closestSpawn == nil) then return end - local distance = util.distance(chunkArea.left_top, closestSpawn.pos) + local distance = util.distance(chunkArea.left_top, closestSpawn.position) -- Adjust multiplier to bring it in or out local modifier = (distance / (global.ocfg.spawn_config.safe_area.danger_radius * 1)) ^ 3 if modifier < 0.1 then modifier = 0.1 end @@ -510,6 +515,8 @@ end -- I wrote this to ensure everyone gets safer spawns regardless of evolution level. -- This is intended to downgrade any biters/spitters spawning near player bases. -- I'm not sure the performance impact of this but I'm hoping it's not bad. +---@param event EventData.on_entity_spawned|EventData.on_biter_base_built +---@return nil function ModifyEnemySpawnsNearPlayerStartingAreas(event) if (not event.entity or not (event.entity.force.name == "enemy") or not event.entity.position) then log("ModifyBiterSpawns - Unexpected use.") @@ -520,7 +527,7 @@ function ModifyEnemySpawnsNearPlayerStartingAreas(event) local surface = event.entity.surface local enemy_name = event.entity.name - local closest_spawn = GetClosestUniqueSpawn(enemy_pos) + local closest_spawn = GetClosestUniqueSpawn(surface, enemy_pos) if (closest_spawn == nil) then -- log("GetClosestUniqueSpawn ERROR - None found?") @@ -528,11 +535,11 @@ function ModifyEnemySpawnsNearPlayerStartingAreas(event) end -- No enemies inside safe radius! - if (util.distance(enemy_pos, closest_spawn.pos) < global.ocfg.spawn_config.safe_area.safe_radius) then + if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.spawn_config.safe_area.safe_radius) then event.entity.destroy() -- Warn distance is all SMALL only. - elseif (util.distance(enemy_pos, closest_spawn.pos) < global.ocfg.spawn_config.safe_area.warn_radius) then + elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.spawn_config.safe_area.warn_radius) then if ((enemy_name == "big-biter") or (enemy_name == "behemoth-biter") or (enemy_name == "medium-biter")) then event.entity.destroy() surface.create_entity { name = "small-biter", position = enemy_pos, force = game.forces.enemy } @@ -548,7 +555,7 @@ function ModifyEnemySpawnsNearPlayerStartingAreas(event) end -- Danger distance is MEDIUM max. - elseif (util.distance(enemy_pos, closest_spawn.pos) < global.ocfg.spawn_config.safe_area.danger_radius) then + elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.spawn_config.safe_area.danger_radius) then if ((enemy_name == "big-biter") or (enemy_name == "behemoth-biter")) then event.entity.destroy() surface.create_entity { name = "medium-biter", position = enemy_pos, force = game.forces.enemy } @@ -573,15 +580,17 @@ end --]] - +---Resets the player and destroys their force if they are not on the main one. +---@param player LuaPlayer +---@return nil function ResetPlayerAndDestroyForce(player) local player_old_force = player.force - player.force = global.ocfg.main_force + player.force = global.ocfg.gameplay.main_force_name - if ((#player_old_force.players == 0) and (player_old_force.name ~= global.ocfg.main_force)) then + if ((#player_old_force.players == 0) and (player_old_force.name ~= global.ocfg.gameplay.main_force_name)) then SendBroadcastMsg("Team " .. - player_old_force.name .. " has been destroyed! All buildings will slowly be destroyed now.") + player_old_force.name .. " has been destroyed! All buildings will slowly be destroyed now.") log("DestroyForce - FORCE DESTROYED: " .. player_old_force.name) game.merge_forces(player_old_force, global.ocore.destroyed_force) end @@ -590,12 +599,15 @@ function ResetPlayerAndDestroyForce(player) SeparateSpawnsPlayerCreated(player.index, false) end +---Resets the player and merges their force into the abandoned_force. +---@param player LuaPlayer +---@return nil function ResetPlayerAndAbandonForce(player) local player_old_force = player.force - player.force = global.ocfg.main_force + player.force = global.ocfg.gameplay.main_force_name - if ((#player_old_force.players == 0) and (player_old_force.name ~= global.ocfg.main_force)) then + if ((#player_old_force.players == 0) and (player_old_force.name ~= global.ocfg.gameplay.main_force_name)) then SendBroadcastMsg("Team " .. player_old_force.name .. " has been abandoned!") log("AbandonForce - FORCE ABANDONED: " .. player_old_force.name) game.merge_forces(player_old_force, global.ocore.abandoned_force) @@ -605,11 +617,17 @@ function ResetPlayerAndAbandonForce(player) SeparateSpawnsPlayerCreated(player.index, false) end +---Reset player and merge their force to neutral +---@param player LuaPlayer +---@return nil function ResetPlayerAndMergeForceToNeutral(player) RemoveOrResetPlayer(player, false, true, true, true) SeparateSpawnsPlayerCreated(player.index, true) end +---Kicks player from game and marks player for removal from globals. +---@param player LuaPlayer +---@return nil function KickAndMarkPlayerForRemoval(player) game.kick_player(player, "KickAndMarkPlayerForRemoval") if (not global.ocore.player_removal_list) then @@ -618,7 +636,12 @@ function KickAndMarkPlayerForRemoval(player) table.insert(global.ocore.player_removal_list, player) end --- Call this if a player leaves the game early (or a player wants an early game reset) +---Call this if a player leaves the game early (or a player wants an early game reset) +---@param player LuaPlayer +---@param remove_player boolean Deletes player from the game assuming they are offline. +---@param remove_force boolean +---@param remove_base boolean +---@param immediate boolean function RemoveOrResetPlayer(player, remove_player, remove_force, remove_base, immediate) if (not player) then log("ERROR - CleanupPlayer on NIL Player!") @@ -627,9 +650,9 @@ function RemoveOrResetPlayer(player, remove_player, remove_force, remove_base, i -- If this player is staying in the game, lets make sure we don't delete them along with the map chunks being -- cleared. - player.teleport({ x = 0, y = 0 }, GAME_SURFACE_NAME) + player.teleport({ x = 0, y = 0 }, global.ocfg.gameplay.main_force_surface) local player_old_force = player.force - player.force = global.ocfg.main_force + player.force = global.ocfg.gameplay.main_force_name -- Clear globals CleanupPlayerGlobals(player.name) -- Except global.ocore.uniqueSpawns @@ -639,7 +662,7 @@ function RemoveOrResetPlayer(player, remove_player, remove_force, remove_base, i -- Remove a force if this player created it and they are the only one on it if (remove_force) then - if ((#player_old_force.players == 0) and (player_old_force.name ~= global.ocfg.main_force)) then + if ((#player_old_force.players == 0) and (player_old_force.name ~= global.ocfg.gameplay.main_force_name)) then log("RemoveOrResetPlayer - FORCE REMOVED: " .. player_old_force.name) game.merge_forces(player_old_force, "neutral") end @@ -651,42 +674,55 @@ function RemoveOrResetPlayer(player, remove_player, remove_force, remove_base, i end end +---Cleans up a player's unique spawn point. +---TODO: Move relevant stuff to regrowth? +---@param playerName string +---@param cleanup boolean +---@param immediate boolean Trrigger cleanup immediately. +---@return nil function UniqueSpawnCleanupRemove(playerName, cleanup, immediate) if (global.ocore.uniqueSpawns[playerName] == nil) then return end -- Safety log("UniqueSpawnCleanupRemove - " .. playerName) local spawnPos = global.ocore.uniqueSpawns[playerName].pos + local land_area_tiles = global.ocfg.spawn_config.general.land_area_tiles -- Check if it was near someone else's base. (Really just buddy base is possible I think.) nearOtherSpawn = false for spawnPlayerName, otherSpawnPos in pairs(global.ocore.uniqueSpawns) do - if ((spawnPlayerName ~= playerName) and (util.distance(spawnPos, otherSpawnPos.pos) < (global.ocfg.spawn_config.land_area_tiles * 3))) then + if ((spawnPlayerName ~= playerName) and + (util.distance(spawnPos, otherSpawnPos.pos) < (land_area_tiles * 3))) then log("Won't remove base as it's close to another spawn: " .. spawnPlayerName) nearOtherSpawn = true end end -- Unused Chunk Removal mod (aka regrowth) - if (cleanup and global.ocfg.enable_abandoned_base_removal and (not nearOtherSpawn) and global.ocfg.enable_regrowth) then - if (global.ocore.uniqueSpawns[playerName].vanilla) then - log("Returning a vanilla spawn back to available.") - table.insert(global.vanillaSpawns, { x = spawnPos.x, y = spawnPos.y }) - end + if (cleanup and global.ocfg.mod_overlap.enable_abandoned_base_cleanup and + (not nearOtherSpawn) and global.ocfg.mod_overlap.enable_regrowth) then + -- TODO: Vanilla spawn point are not implemented yet. + -- if (global.ocore.uniqueSpawns[playerName].vanilla) then + -- log("Returning a vanilla spawn back to available.") + -- table.insert(global.vanillaSpawns, { x = spawnPos.x, y = spawnPos.y }) + -- end if (immediate) then log("IMMEDIATE Removing base: " .. spawnPos.x .. "," .. spawnPos.y) - RegrowthMarkAreaForRemoval(spawnPos, math.ceil(global.ocfg.spawn_config.land_area_tiles / CHUNK_SIZE)) + RegrowthMarkAreaForRemoval(spawnPos, math.ceil(land_area_tiles / CHUNK_SIZE)) TriggerCleanup() else log("Removing permanent flags on base: " .. spawnPos.x .. "," .. spawnPos.y) RegrowthMarkAreaNotPermanentOVERWRITE(spawnPos, - math.ceil(global.ocfg.spawn_config.land_area_tiles / CHUNK_SIZE)) + math.ceil(land_area_tiles / CHUNK_SIZE)) end end global.ocore.uniqueSpawns[playerName] = nil end +---Cleans up all references to a player in the global tables. +---@param playerName string +---@return nil function CleanupPlayerGlobals(playerName) -- Clear the buddy pair IF one exists if (global.ocore.buddyPairs[playerName] ~= nil) then @@ -710,7 +746,8 @@ function CleanupPlayerGlobals(playerName) -- Transfer or remove a shared spawn if player is owner if (global.ocore.sharedSpawns[playerName] ~= nil) then - local teamMates = global.ocore.sharedSpawns[playerName].players + local sharedSpawn = global.ocore.sharedSpawns[playerName] --[[@as OarcSharedSpawn]] + local teamMates = sharedSpawn.players if (#teamMates >= 1) then local newOwnerName = table.remove(teamMates) -- Remove 1 to use as new owner. @@ -722,7 +759,7 @@ function CleanupPlayerGlobals(playerName) end -- Remove from other shared spawns (need to search all) - for _, sharedSpawn in pairs(global.ocore.sharedSpawns) do + for _, sharedSpawn in pairs(global.ocore.sharedSpawns --[[@as OarcSharedSpawnsTable]]) do for key, name in pairs(sharedSpawn.players) do if (playerName == name) then sharedSpawn.players[key] = nil; @@ -738,14 +775,15 @@ function CleanupPlayerGlobals(playerName) end -- Remove them from the delayed spawn queue if they are in it - for idx, delayedSpawn in pairs(global.ocore.delayedSpawns) do + for index, delayedSpawn in pairs(global.ocore.delayedSpawns --[[@as OarcDelayedSpawnsTable]]) do if (playerName == delayedSpawn.playerName) then - if (delayedSpawn.vanilla) then - log("Returning a vanilla spawn back to available.") - table.insert(global.vanillaSpawns, { x = delayedSpawn.pos.x, y = delayedSpawn.pos.y }) - end + ---TODO: Vanilla spawn point are not implemented yet. + -- if (delayedSpawn.vanilla) then + -- log("Returning a vanilla spawn back to available.") + -- table.insert(global.vanillaSpawns, { x = delayedSpawn.pos.x, y = delayedSpawn.pos.y }) + -- end - table.remove(global.ocore.delayedSpawns, idx) + global.ocore.delayedSpawns[index] = nil log("Removing player from delayed spawn queue: " .. playerName) break end @@ -754,18 +792,20 @@ function CleanupPlayerGlobals(playerName) if (global.ocore.playerCooldowns[playerName] ~= nil) then global.ocore.playerCooldowns[playerName] = nil end - - global.oarc_store.pmf_counts[playerName] = {} end +---Transfers ownership of a shared spawn to another player. +---@param prevOwnerName string +---@param newOwnerName string +---@return nil function TransferOwnershipOfSharedSpawn(prevOwnerName, newOwnerName) -- Transfer the shared spawn global - global.ocore.sharedSpawns[newOwnerName] = global.ocore.sharedSpawns[prevOwnerName] + global.ocore.sharedSpawns[newOwnerName] = global.ocore.sharedSpawns[prevOwnerName] --[[@as OarcSharedSpawn]] global.ocore.sharedSpawns[newOwnerName].openAccess = false global.ocore.sharedSpawns[prevOwnerName] = nil -- Transfer the unique spawn global - global.ocore.uniqueSpawns[newOwnerName] = global.ocore.uniqueSpawns[prevOwnerName] + global.ocore.uniqueSpawns[newOwnerName] = global.ocore.uniqueSpawns[prevOwnerName] --[[@as OarcUniqueSpawn]] global.ocore.uniqueSpawns[prevOwnerName] = nil game.players[newOwnerName].print("You have been given ownership of this base!") @@ -779,12 +819,19 @@ end --]] --- Same as GetClosestPosFromTable but specific to global.ocore.uniqueSpawns -function GetClosestUniqueSpawn(pos) +---Same as GetClosestPosFromTable but specific to global.ocore.uniqueSpawns +---@param surface LuaSurface +---@param pos MapPosition +---@return OarcUniqueSpawn? +function GetClosestUniqueSpawn(surface, pos) local closest_dist = nil local closest_key = nil for k, s in pairs(global.ocore.uniqueSpawns) do + if (s.surface ~= surface) then + goto CONTINUE + end + local new_dist = util.distance(pos, s.pos) if (closest_dist == nil) then closest_dist = new_dist @@ -793,6 +840,8 @@ function GetClosestUniqueSpawn(pos) closest_dist = new_dist closest_key = k end + + ::CONTINUE:: -- Continue loop label end if (closest_key == nil) then @@ -803,8 +852,9 @@ function GetClosestUniqueSpawn(pos) return global.ocore.uniqueSpawns[closest_key] end --- Return the owner of the shared spawn for this player. --- May return nil if player has not spawned yet. +---Return the owner of the shared spawn for this player. May return nil if player has not spawned yet. +---@param playerName string +---@return string? function FindPlayerSharedSpawn(playerName) -- If the player IS an owner, he can't be in any other shared base. if (global.ocore.sharedSpawns[playerName] ~= nil) then @@ -812,7 +862,7 @@ function FindPlayerSharedSpawn(playerName) end -- Otherwise, search all shared spawns for this player and return the owner. - for ownerName, sharedSpawn in pairs(global.ocore.sharedSpawns) do + for ownerName, sharedSpawn in pairs(global.ocore.sharedSpawns --[[@as OarcSharedSpawnsTable]]) do for _, sharingPlayerName in pairs(sharedSpawn.players) do if (playerName == sharingPlayerName) then return ownerName @@ -824,9 +874,13 @@ function FindPlayerSharedSpawn(playerName) return nil end --- Returns the number of players currently online at the shared spawn +---Returns the number of players currently online at the shared spawn +---@param ownerName string +---@return number function GetOnlinePlayersAtSharedSpawn(ownerName) - if (global.ocore.sharedSpawns[ownerName] ~= nil) then + local sharedSpawn = global.ocore.sharedSpawns[ownerName] --[[@as OarcSharedSpawn]] + + if (sharedSpawn ~= nil) then -- Does not count base owner local count = 0 @@ -836,7 +890,8 @@ function GetOnlinePlayersAtSharedSpawn(ownerName) count = count + 1 end - for _, playerName in pairs(global.ocore.sharedSpawns[ownerName].players) do + + for _, playerName in pairs(sharedSpawn.players) do if (playerName == player.name) then count = count + 1 end @@ -849,18 +904,21 @@ function GetOnlinePlayersAtSharedSpawn(ownerName) end end --- Get the number of currently available shared spawns +-- Get the number of currently available shared spawns. -- This means the base owner has enabled access AND the number of online players -- is below the threshold. +---@return number function GetNumberOfAvailableSharedSpawns() local count = 0 - for ownerName, sharedSpawn in pairs(global.ocore.sharedSpawns) do + local number_of_players_per_shared_spawn = global.ocfg.mod_overlap.number_of_players_per_shared_spawn + + for ownerName, sharedSpawn in pairs(global.ocore.sharedSpawns --[[@as OarcSharedSpawnsTable]]) do if (sharedSpawn.openAccess and (game.players[ownerName] ~= nil) and game.players[ownerName].connected) then - if ((global.ocfg.max_players_shared_spawn == 0) or - (#global.ocore.sharedSpawns[ownerName].players < global.ocfg.max_players_shared_spawn)) then + if ((number_of_players_per_shared_spawn == 0) or + (#global.ocore.sharedSpawns[ownerName].players < number_of_players_per_shared_spawn)) then count = count + 1 end end @@ -869,8 +927,11 @@ function GetNumberOfAvailableSharedSpawns() return count end +---Checks if player has a custom spawn point set. +---@param player LuaPlayer +---@return boolean function DoesPlayerHaveCustomSpawn(player) - for name, spawnPos in pairs(global.ocore.playerSpawns) do + for name, spawnPos in pairs(global.ocore.playerSpawns --[[@as OarcPlayerSpawnsTable]]) do if (player.name == name) then return true end @@ -882,7 +943,7 @@ end ---@param player LuaPlayer ---@return OarcPlayerSpawn? function GetPlayerCustomSpawn(player) - for name, playerSpawn in pairs(global.ocore.playerSpawns) do + for name, playerSpawn in pairs(global.ocore.playerSpawns --[[@as OarcPlayerSpawnsTable]]) do if (player.name == name) then return playerSpawn end @@ -890,30 +951,59 @@ function GetPlayerCustomSpawn(player) return nil end -function ChangePlayerSpawn(player, pos) - global.ocore.playerSpawns[player.name] = pos +--TODO: Add support for multiple surfaces +---Sets the custom spawn point for a player. +---@param player LuaPlayer +---@param position MapPosition +---@return nil +function ChangePlayerSpawn(player, position) + global.ocore.playerSpawns[player.name] = position global.ocore.playerCooldowns[player.name] = { setRespawn = game.tick } end -function QueuePlayerForDelayedSpawn(playerName, spawn, moatEnabled, vanillaSpawn) +---Queue a player for a delayed spawn. +---@param playerName string +---@param surface LuaSurface +---@param spawnPosition MapPosition +---@param moatEnabled boolean +---@param vanillaSpawn boolean +---@return nil +function QueuePlayerForDelayedSpawn(playerName, surface, spawnPosition, moatEnabled, vanillaSpawn) -- If we get a valid spawn point, setup the area - if ((spawn.x ~= 0) or (spawn.y ~= 0)) then - global.ocore.uniqueSpawns[playerName] = { pos = spawn, moat = moatEnabled, vanilla = vanillaSpawn } + if ((spawnPosition.x ~= 0) or (spawnPosition.y ~= 0)) then + ---@type OarcUniqueSpawn + local newUniqueSpawn = {} + newUniqueSpawn.surface = surface + newUniqueSpawn.position = spawnPosition + newUniqueSpawn.moat = moatEnabled - local delay_spawn_seconds = 5 * (math.ceil(global.ocfg.spawn_config.land_area_tiles / CHUNK_SIZE)) + ---TODO: Vanilla spawn point are not implemented yet. + -- newUniqueSpawn.vanilla = vanillaSpawn + global.ocore.uniqueSpawns[playerName] = newUniqueSpawn + local delay_spawn_seconds = 5 * (math.ceil(global.ocfg.spawn_config.general.land_area_tiles / CHUNK_SIZE)) + + ---TODO: Move text to locale. game.players[playerName].print("Generating your spawn now, please wait for at least " .. - delay_spawn_seconds .. " seconds...") - game.players[playerName].surface.request_to_generate_chunks(spawn, 4) - delayedTick = game.tick + delay_spawn_seconds * TICKS_PER_SECOND - table.insert(global.ocore.delayedSpawns, - { playerName = playerName, pos = spawn, moat = moatEnabled, vanilla = vanillaSpawn, delayedTick = delayedTick }) + delay_spawn_seconds .. " seconds...") + game.surfaces[surface].request_to_generate_chunks(spawnPosition, 4) + + + ---@type OarcDelayedSpawn + local delayedSpawn = {} + delayedSpawn.playerName = playerName + delayedSpawn.surface = surface + delayedSpawn.position = spawnPosition + delayedSpawn.moat = moatEnabled + delayedSpawn.delayedTick = game.tick + delay_spawn_seconds * TICKS_PER_SECOND + + table.insert(global.ocore.delayedSpawns, delayedSpawn) HideOarcGui(game.players[playerName]) - HideOarcStore(game.players[playerName]) DisplayPleaseWaitForSpawnDialog(game.players[playerName], delay_spawn_seconds) - RegrowthMarkAreaSafeGivenTilePos(spawn, math.ceil(global.ocfg.spawn_config.land_area_tiles / CHUNK_SIZE), true) + RegrowthMarkAreaSafeGivenTilePos(spawnPosition, + math.ceil(global.ocfg.spawn_config.general.land_area_tiles / CHUNK_SIZE), true) else log("THIS SHOULD NOT EVER HAPPEN! Spawn failed!") SendBroadcastMsg("ERROR!! Failed to create spawn point for: " .. playerName) @@ -923,14 +1013,18 @@ end -- Check a table to see if there are any players waiting to spawn -- Check if we are past the delayed tick count -- Spawn the players and remove them from the table. +---@return nil function DelayedSpawnOnTick() if ((game.tick % (30)) == 1) then if ((global.ocore.delayedSpawns ~= nil) and (#global.ocore.delayedSpawns > 0)) then + + --TODO: Investigate this magic indexing with ints and keys? + -- I think this loop removes from the back of the table to the front?? for i = #global.ocore.delayedSpawns, 1, -1 do - delayedSpawn = global.ocore.delayedSpawns[i] + delayedSpawn = global.ocore.delayedSpawns[i] --[[@as OarcDelayedSpawn]] if (delayedSpawn.delayedTick < game.tick) then - -- TODO, add check here for if chunks around spawn are generated surface.is_chunk_generated(chunkPos) + -- TODO: add check here for if chunks around spawn are generated surface.is_chunk_generated(chunkPos) if (game.players[delayedSpawn.playerName] ~= nil) then SendPlayerToNewSpawnAndCreateIt(delayedSpawn) end @@ -941,6 +1035,9 @@ function DelayedSpawnOnTick() end end +---Send player to their custom spawn point if one exists, otherwise to the force's spawn point. +---@param player LuaPlayer +---@return nil function SendPlayerToSpawn(player) local playerSpawn = GetPlayerCustomSpawn(player) if (playerSpawn ~= nil) then @@ -948,19 +1045,24 @@ function SendPlayerToSpawn(player) playerSpawn.surface, playerSpawn.position) else + local gameplayConfig = global.ocfg.gameplay --[[@as OarcConfigGameplaySettings]] SafeTeleport(player, - global.ocfg.gameplay.main_force_surface, - game.forces[global.ocfg.gameplay.main_force_name].get_spawn_position(GAME_SURFACE_NAME)) + gameplayConfig.main_force_surface, + game.forces[gameplayConfig.main_force_name].get_spawn_position(gameplayConfig.main_force_surface)) end end +---Send player to a random spawn point. +---@param player LuaPlayer +---@return nil function SendPlayerToRandomSpawn(player) local numSpawns = TableLength(global.ocore.uniqueSpawns) local rndSpawn = math.random(0, numSpawns) local counter = 0 if (rndSpawn == 0) then - player.teleport(game.forces[global.ocfg.main_force].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) + local gameplayConfig = global.ocfg.gameplay --[[@as OarcConfigGameplaySettings]] + player.teleport(game.forces[gameplayConfig.main_force_name].get_spawn_position(gameplayConfig.main_force_surface), gameplayConfig.main_force_surface) else counter = counter + 1 for name, spawn in pairs(global.ocore.uniqueSpawns) do @@ -981,6 +1083,10 @@ end --]] + +---Create a new force +---@param force_name string +---@return LuaForce function CreateForce(force_name) local newForce = nil @@ -992,47 +1098,47 @@ function CreateForce(force_name) -- Create a new force elseif (TableLength(game.forces) < MAX_FORCES) then newForce = game.create_force(force_name) - if global.ocfg.enable_shared_team_vision then + if global.ocfg.mod_overlap.enable_shared_team_vision then newForce.share_chart = true end - if global.ocfg.enable_research_queue then - newForce.research_queue_enabled = true - end - -- Chart silo areas if necessary - if global.ocfg.frontier_rocket_silo and global.ocfg.frontier_silo_vision then - ChartRocketSiloAreas(game.surfaces[GAME_SURFACE_NAME], newForce) - end + + -- This now defaults to true? + -- if global.ocfg.enable_research_queue then + -- newForce.research_queue_enabled = true + -- end + + SetCeaseFireBetweenAllForces() SetFriendlyBetweenAllForces() - newForce.friendly_fire = global.ocfg.enable_friendly_fire - if (global.ocfg.enable_anti_grief) then - AntiGriefing(newForce) - end - if global.ocfg.lock_goodies_rocket_launch and not global.ocore.satellite_sent then - for _, v in ipairs(LOCKED_TECHNOLOGIES) do - DisableTech(newForce, v.t) - end - end + newForce.friendly_fire = global.ocfg.mod_overlap.enable_friendly_fire + + -- if (global.ocfg.enable_anti_grief) then + -- AntiGriefing(newForce) + -- end + else log("TOO MANY FORCES!!! - CreateForce()") - return game.forces[global.ocfg.main_force] + return game.forces[global.ocfg.gameplay.main_force_name] end -- Add productivity bonus for solo teams. - if (ENABLE_FORCE_LAB_PROD_BONUS) then - local tech_mult = game.difficulty_settings.technology_price_multiplier - if (tech_mult > 1) and (force_name ~= global.ocfg.main_force) then - newForce.laboratory_productivity_bonus = (tech_mult - 1) - end - end + -- if (ENABLE_FORCE_LAB_PROD_BONUS) then + -- local tech_mult = game.difficulty_settings.technology_price_multiplier + -- if (tech_mult > 1) and (force_name ~= global.ocfg.main_force) then + -- newForce.laboratory_productivity_bonus = (tech_mult - 1) + -- end + -- end -- Loot distance buff - newForce.character_loot_pickup_distance_bonus = 16 + -- newForce.character_loot_pickup_distance_bonus = 16 return newForce end +---Create a new player force and assign the player to it. +---@param player LuaPlayer +---@return LuaForce function CreatePlayerCustomForce(player) local newForce = CreateForce(player.name) player.force = newForce @@ -1056,107 +1162,107 @@ end -- Function to generate some map_gen_settings.starting_points -- You should only use this at the start of the game really. -function CreateVanillaSpawns(count, spacing) - local points = {} +-- function CreateVanillaSpawns(count, spacing) +-- local points = {} - -- Get an ODD number from the square of the input count. - -- Always rounding up so we don't end up with less points that requested. - local sqrt_count = math.ceil(math.sqrt(count)) - if (sqrt_count % 2 == 0) then - sqrt_count = sqrt_count + 1 - end +-- -- Get an ODD number from the square of the input count. +-- -- Always rounding up so we don't end up with less points that requested. +-- local sqrt_count = math.ceil(math.sqrt(count)) +-- if (sqrt_count % 2 == 0) then +-- sqrt_count = sqrt_count + 1 +-- end - -- Need to know how much to offset the grid. - local sqrt_half = math.floor((sqrt_count - 1) / 2) +-- -- Need to know how much to offset the grid. +-- local sqrt_half = math.floor((sqrt_count - 1) / 2) - if (sqrt_count < 1) then - log("CreateVanillaSpawns less than 1!!") - return - end +-- if (sqrt_count < 1) then +-- log("CreateVanillaSpawns less than 1!!") +-- return +-- end - if (global.vanillaSpawns == nil) then - global.vanillaSpawns = {} - end +-- if (global.vanillaSpawns == nil) then +-- global.vanillaSpawns = {} +-- end - -- This should give me points centered around 0,0 I think. - for i = -sqrt_half, sqrt_half, 1 do - for j = -sqrt_half, sqrt_half, 1 do - if (i ~= 0 or j ~= 0) then -- EXCEPT don't put 0,0 - local x_pos = (i * spacing) - x_pos = x_pos - (x_pos % CHUNK_SIZE) + (CHUNK_SIZE / 2) - local y_pos = (j * spacing) - y_pos = y_pos - (y_pos % CHUNK_SIZE) + (CHUNK_SIZE / 2) +-- -- This should give me points centered around 0,0 I think. +-- for i = -sqrt_half, sqrt_half, 1 do +-- for j = -sqrt_half, sqrt_half, 1 do +-- if (i ~= 0 or j ~= 0) then -- EXCEPT don't put 0,0 +-- local x_pos = (i * spacing) +-- x_pos = x_pos - (x_pos % CHUNK_SIZE) + (CHUNK_SIZE / 2) +-- local y_pos = (j * spacing) +-- y_pos = y_pos - (y_pos % CHUNK_SIZE) + (CHUNK_SIZE / 2) - table.insert(points, { x = x_pos, y = y_pos }) - table.insert(global.vanillaSpawns, { x = x_pos, y = y_pos }) - end - end - end +-- table.insert(points, { x = x_pos, y = y_pos }) +-- table.insert(global.vanillaSpawns, { x = x_pos, y = y_pos }) +-- end +-- end +-- end - -- Do something with the return value. - return points -end +-- -- Do something with the return value. +-- return points +-- end --- Useful when combined with something like CreateVanillaSpawns --- Where it helps ensure ALL chunks generated use new map_gen_settings. -function DeleteAllChunksExceptCenter(surface) - -- Delete the starting chunks that make it into the game before settings are changed. - for chunk in surface.get_chunks() do - -- Don't delete the chunk that might contain players lol. - -- This is really only a problem for launching AS the host. Not headless - if ((chunk.x ~= 0) and (chunk.y ~= 0)) then - surface.delete_chunk({ chunk.x, chunk.y }) - end - end -end +-- -- Useful when combined with something like CreateVanillaSpawns +-- -- Where it helps ensure ALL chunks generated use new map_gen_settings. +-- function DeleteAllChunksExceptCenter(surface) +-- -- Delete the starting chunks that make it into the game before settings are changed. +-- for chunk in surface.get_chunks() do +-- -- Don't delete the chunk that might contain players lol. +-- -- This is really only a problem for launching AS the host. Not headless +-- if ((chunk.x ~= 0) and (chunk.y ~= 0)) then +-- surface.delete_chunk({ chunk.x, chunk.y }) +-- end +-- end +-- end --- Find a vanilla spawn as close as possible to the given target_distance -function FindUnusedVanillaSpawn(surface, target_distance) - local best_key = nil - local best_distance = nil +-- -- Find a vanilla spawn as close as possible to the given target_distance +-- function FindUnusedVanillaSpawn(surface, target_distance) +-- local best_key = nil +-- local best_distance = nil - for k, v in pairs(global.vanillaSpawns) do - -- Check if chunks nearby are not generated. - local chunk_pos = GetChunkPosFromTilePos(v) - if IsChunkAreaUngenerated(chunk_pos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS, surface) then - -- Is this our first valid find? - if ((best_key == nil) or (best_distance == nil)) then - best_key = k - best_distance = math.abs(math.sqrt((v.x ^ 2) + (v.y ^ 2)) - target_distance) +-- for k, v in pairs(global.vanillaSpawns) do +-- -- Check if chunks nearby are not generated. +-- local chunk_pos = GetChunkPosFromTilePos(v) +-- if IsChunkAreaUngenerated(chunk_pos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS, surface) then +-- -- Is this our first valid find? +-- if ((best_key == nil) or (best_distance == nil)) then +-- best_key = k +-- best_distance = math.abs(math.sqrt((v.x ^ 2) + (v.y ^ 2)) - target_distance) - -- Check if it is closer to target_distance than previous option. - else - local new_distance = math.abs(math.sqrt((v.x ^ 2) + (v.y ^ 2)) - target_distance) - if (new_distance < best_distance) then - best_key = k - best_distance = new_distance - end - end +-- -- Check if it is closer to target_distance than previous option. +-- else +-- local new_distance = math.abs(math.sqrt((v.x ^ 2) + (v.y ^ 2)) - target_distance) +-- if (new_distance < best_distance) then +-- best_key = k +-- best_distance = new_distance +-- end +-- end - -- If it's not a valid spawn anymore, let's remove it. - else - log("Removing vanilla spawn due to chunks generated: x=" .. v.x .. ",y=" .. v.y) - table.remove(global.vanillaSpawns, k) - end - end +-- -- If it's not a valid spawn anymore, let's remove it. +-- else +-- log("Removing vanilla spawn due to chunks generated: x=" .. v.x .. ",y=" .. v.y) +-- table.remove(global.vanillaSpawns, k) +-- end +-- end - local spawn_pos = { x = 0, y = 0 } - if ((best_key ~= nil) and (global.vanillaSpawns[best_key] ~= nil)) then - spawn_pos.x = global.vanillaSpawns[best_key].x - spawn_pos.y = global.vanillaSpawns[best_key].y - table.remove(global.vanillaSpawns, best_key) - end - log("Found unused vanilla spawn: x=" .. spawn_pos.x .. ",y=" .. spawn_pos.y) - return spawn_pos -end +-- local spawn_pos = { x = 0, y = 0 } +-- if ((best_key ~= nil) and (global.vanillaSpawns[best_key] ~= nil)) then +-- spawn_pos.x = global.vanillaSpawns[best_key].x +-- spawn_pos.y = global.vanillaSpawns[best_key].y +-- table.remove(global.vanillaSpawns, best_key) +-- end +-- log("Found unused vanilla spawn: x=" .. spawn_pos.x .. ",y=" .. spawn_pos.y) +-- return spawn_pos +-- end -function ValidateVanillaSpawns(surface) - for k, v in pairs(global.vanillaSpawns) do - -- Check if chunks nearby are not generated. - local chunk_pos = GetChunkPosFromTilePos(v) - if not IsChunkAreaUngenerated(chunk_pos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS + 15, surface) then - log("Removing vanilla spawn due to chunks generated: x=" .. v.x .. ",y=" .. v.y) - table.remove(global.vanillaSpawns, k) - end - end -end +-- function ValidateVanillaSpawns(surface) +-- for k, v in pairs(global.vanillaSpawns) do +-- -- Check if chunks nearby are not generated. +-- local chunk_pos = GetChunkPosFromTilePos(v) +-- if not IsChunkAreaUngenerated(chunk_pos, CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS + 15, surface) then +-- log("Removing vanilla spawn due to chunks generated: x=" .. v.x .. ",y=" .. v.y) +-- table.remove(global.vanillaSpawns, k) +-- end +-- end +-- end diff --git a/scenarios/OARC/lib/separate_spawns_guis.lua b/scenarios/OARC/lib/separate_spawns_guis.lua index 1251a7b..7b3d8cc 100644 --- a/scenarios/OARC/lib/separate_spawns_guis.lua +++ b/scenarios/OARC/lib/separate_spawns_guis.lua @@ -1,7 +1,7 @@ -- separate_spawns_guis.lua -- Nov 2016 --- I made a separate file for all the GUI related functions +-- I made a separate file for all the GUI related functions. Yay me. require("lib/separate_spawns") @@ -23,8 +23,9 @@ local SPAWN_GUI_MAX_HEIGHT = 1000 -- Oarc=sharedSpawnExample3} --- A display gui message --- Meant to be display the first time a player joins. +---A display gui message. Meant to be display the first time a player joins. +---@param player LuaPlayer +---@return boolean function DisplayWelcomeTextGui(player) if ((player.gui.screen["welcome_msg"] ~= nil) or (player.gui.screen["spawn_opts"] ~= nil) or @@ -73,7 +74,9 @@ function DisplayWelcomeTextGui(player) end --- Handle the gui click of the welcome msg +---Handle the gui click of the welcome msg +---@param event EventData.on_gui_click +---@return nil function WelcomeTextGuiClick(event) if not (event and event.element and event.element.valid) then return end local player = game.players[event.player_index] @@ -93,7 +96,9 @@ function WelcomeTextGuiClick(event) end --- Display the spawn options and explanation +---Display the spawn options and explanation +---@param player LuaPlayer +---@return nil function DisplaySpawnOptions(player) if (player == nil) then log("DisplaySpawnOptions with no valid player...") @@ -104,6 +109,9 @@ function DisplaySpawnOptions(player) log("Tried to display spawn options when it was already displayed!") return end + + local mod_overlap = global.ocfg.mod_overlap + player.gui.screen.add{name = "spawn_opts", type = "frame", direction = "vertical", @@ -116,7 +124,9 @@ function DisplaySpawnOptions(player) -- Warnings and explanations... local warn_msg = {"oarc-click-info-btn-help"} AddLabel(sGui, "warning_lbl1", warn_msg, my_warning_style) - AddLabel(sGui, "spawn_msg_lbl1", SPAWN_MSG1, my_label_style) + + -- TODO: Not sure what this is for...? SPAWN_MSG1 is not defined anywhere. + -- AddLabel(sGui, "spawn_msg_lbl1", SPAWN_MSG1, my_label_style) -- Button and message about the regular vanilla spawn -- if ENABLE_DEFAULT_SPAWN then @@ -136,7 +146,7 @@ function DisplaySpawnOptions(player) style = "bordered_frame"} -- Radio buttons to pick your team. - if (global.ocfg.enable_separate_teams) then + if (mod_overlap.enable_separate_teams) then soloSpawnFlow.add{name = "isolated_spawn_main_team_radio", type = "radiobutton", caption={"oarc-join-main-team-radio"}, @@ -152,7 +162,9 @@ function DisplaySpawnOptions(player) -- "Additional spawn options can be selected here. Not all are compatible with each other.", my_label_style) -- Allow players to spawn with a moat around their area. - if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled and not global.ocfg.enable_vanilla_spawns) then + --TODO: Vanilla spawn points are not implemented yet. + -- and not global.ocfg.enable_vanilla_spawns + if (mod_overlap.enable_allow_moats_around_spawns) then soloSpawnFlow.add{name = "isolated_spawn_moat_option_checkbox", type = "checkbox", caption={"oarc-moat-option"}, @@ -180,22 +192,22 @@ function DisplaySpawnOptions(player) caption={"oarc-solo-spawn-far"}, style = "confirm_button"} - if (global.ocfg.enable_vanilla_spawns) then - AddLabel(soloSpawnFlow, "isolated_spawn_lbl1", - {"oarc-starting-area-vanilla"}, my_label_style) - AddLabel(soloSpawnFlow, "vanilla_spawn_lbl2", - {"oarc-vanilla-spawns-available", #global.vanillaSpawns}, my_label_style) - else + -- if (global.ocfg.enable_vanilla_spawns) then + -- AddLabel(soloSpawnFlow, "isolated_spawn_lbl1", + -- {"oarc-starting-area-vanilla"}, my_label_style) + -- AddLabel(soloSpawnFlow, "vanilla_spawn_lbl2", + -- {"oarc-vanilla-spawns-available", #global.vanillaSpawns}, my_label_style) + -- else AddLabel(soloSpawnFlow, "isolated_spawn_lbl1", {"oarc-starting-area-normal"}, my_label_style) - end + -- end -- Spawn options to join another player's base. local sharedSpawnFrame = sGui.add{name = "spawn_shared_flow", type = "frame", direction="vertical", style = "bordered_frame"} - if global.ocfg.enable_shared_spawns then + if mod_overlap.enable_shared_spawns then local numAvailSpawns = GetNumberOfAvailableSharedSpawns() if (numAvailSpawns > 0) then sharedSpawnFrame.add{name = "join_other_spawn", @@ -215,8 +227,9 @@ function DisplaySpawnOptions(player) end -- Awesome buddy spawning system - if (not global.ocfg.enable_vanilla_spawns) then - if global.ocfg.enable_shared_spawns and global.ocfg.enable_buddy_spawn then + ---TODO: Vanilla spawn points are not implemented yet. + -- if (not global.ocfg.enable_vanilla_spawns) then + if mod_overlap.enable_shared_spawns and mod_overlap.enable_buddy_spawn then local buddySpawnFrame = sGui.add{name = "spawn_buddy_flow", type = "frame", direction="vertical", @@ -229,21 +242,27 @@ function DisplaySpawnOptions(player) AddLabel(buddySpawnFrame, "buddy_spawn_lbl1", {"oarc-buddy-spawn-info"} , my_label_style) end - end + -- end -- Some final notes - if (global.ocfg.max_players_shared_spawn > 0) then + if (mod_overlap.number_of_players_per_shared_spawn > 0) then AddLabel(sGui, "max_players_lbl2", - {"oarc-max-players-shared-spawn", global.ocfg.max_players_shared_spawn-1}, + {"oarc-max-players-shared-spawn", mod_overlap.number_of_players_per_shared_spawn-1}, my_note_style) end - local spawn_distance_notes={"oarc-spawn-dist-notes", global.ocfg.near_dist_start, global.ocfg.near_dist_end, global.ocfg.far_dist_start, global.ocfg.far_dist_end} + + local spawn_distance_notes={"oarc-spawn-dist-notes", + mod_overlap.near_spawn_min_distance, + mod_overlap.near_spawn_max_distance, + mod_overlap.far_spawn_min_distance, + mod_overlap.far_spawn_max_distance} AddLabel(sGui, "note_lbl1", spawn_distance_notes, my_note_style) end ---This just updates the radio buttons/checkboxes when players click them. ---@param event EventData.on_gui_checked_state_changed +---@return nil function SpawnOptsRadioSelect(event) if not (event and event.element and event.element.valid) then return end local elemName = event.element.name @@ -267,7 +286,9 @@ function SpawnOptsRadioSelect(event) end --- Handle the gui click of the spawn options +---Handle the gui click of the spawn options +---@param event EventData.on_gui_click +---@return nil function SpawnOptsGuiClick(event) if not (event and event.element and event.element.valid) then return end local player = game.players[event.player_index] @@ -284,7 +305,7 @@ function SpawnOptsGuiClick(event) local pgcs = player.gui.screen.spawn_opts - local joinMainTeamRadio, joinOwnTeamRadio, moatChoice, vanillaChoice = false + local joinMainTeamRadio, joinOwnTeamRadio, moatChoice, vanillaChoice = false, false, false, false -- Check if a valid button on the gui was pressed -- and delete the GUI @@ -295,7 +316,7 @@ function SpawnOptsGuiClick(event) (elemName == "buddy_spawn") or (elemName == "join_other_spawn_check")) then - if (global.ocfg.enable_separate_teams) then + if (global.ocfg.mod_overlap.enable_separate_teams) then joinMainTeamRadio = pgcs.spawn_solo_flow.isolated_spawn_main_team_radio.state joinOwnTeamRadio = @@ -304,7 +325,8 @@ function SpawnOptsGuiClick(event) joinMainTeamRadio = true joinOwnTeamRadio = false end - if (global.ocfg.spawn_config.gen_settings.moat_choice_enabled and not global.ocfg.enable_vanilla_spawns and + ---TODO: Vanilla spawn points are not implemented yet. and not global.ocfg.enable_vanilla_spawns + if (global.ocfg.mod_overlap.enable_allow_moats_around_spawns and (pgcs.spawn_solo_flow.isolated_spawn_moat_option_checkbox ~= nil)) then moatChoice = pgcs.spawn_solo_flow.isolated_spawn_moat_option_checkbox.state end @@ -319,9 +341,9 @@ function SpawnOptsGuiClick(event) if (elemName == "default_spawn_btn") then GivePlayerStarterItems(player) - ChangePlayerSpawn(player, player.force.get_spawn_position(GAME_SURFACE_NAME)) + ChangePlayerSpawn(player, player.force.get_spawn_position(global.ocfg.gameplay.main_force_surface)) SendBroadcastMsg({"oarc-player-is-joining-main-force", player.name}) - ChartArea(player.force, player.position, math.ceil(global.ocfg.spawn_config.gen_settings.land_area_tiles/CHUNK_SIZE), player.surface) + ChartArea(player.force, player.position, math.ceil(global.ocfg.spawn_config.general.land_area_tiles/CHUNK_SIZE), player.surface) -- Unlock spawn control gui tab SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_GUI_NAME, true) @@ -330,35 +352,40 @@ function SpawnOptsGuiClick(event) -- Create a new spawn point local newSpawn = {x=0,y=0} + local mod_overlap = global.ocfg.mod_overlap + -- Create a new force for player if they choose that radio button - if global.ocfg.enable_separate_teams and joinOwnTeamRadio then + if mod_overlap.enable_separate_teams and joinOwnTeamRadio then local newForce = CreatePlayerCustomForce(player) end - -- Find an unused vanilla spawn - -- if (vanillaChoice) then - if (global.ocfg.enable_vanilla_spawns) then - if (elemName == "isolated_spawn_far") then - newSpawn = FindUnusedVanillaSpawn(game.surfaces[GAME_SURFACE_NAME], - global.ocfg.far_dist_end*CHUNK_SIZE) - elseif (elemName == "isolated_spawn_near") then - newSpawn = FindUnusedVanillaSpawn(game.surfaces[GAME_SURFACE_NAME], - global.ocfg.near_dist_start*CHUNK_SIZE) - end + ---TODO: Vanilla spawn points are not implemented yet. + -- -- Find an unused vanilla spawn + -- -- if (vanillaChoice) then + -- if (global.ocfg.enable_vanilla_spawns) then + -- if (elemName == "isolated_spawn_far") then + -- newSpawn = FindUnusedVanillaSpawn(game.surfaces[GAME_SURFACE_NAME], + -- global.ocfg.far_dist_end*CHUNK_SIZE) + -- elseif (elemName == "isolated_spawn_near") then + -- newSpawn = FindUnusedVanillaSpawn(game.surfaces[GAME_SURFACE_NAME], + -- global.ocfg.near_dist_start*CHUNK_SIZE) + -- end - -- Default OARC-type pre-set layout spawn. - else + + -- -- Default OARC-type pre-set layout spawn. + -- else -- Find coordinates of a good place to spawn if (elemName == "isolated_spawn_far") then - newSpawn = FindUngeneratedCoordinates(global.ocfg.far_dist_start,global.ocfg.far_dist_end, player.surface) + newSpawn = FindUngeneratedCoordinates(mod_overlap.far_spawn_min_distance, mod_overlap.far_spawn_max_distance, player.surface) elseif (elemName == "isolated_spawn_near") then - newSpawn = FindUngeneratedCoordinates(global.ocfg.near_dist_start,global.ocfg.near_dist_end, player.surface) + newSpawn = FindUngeneratedCoordinates(mod_overlap.near_spawn_min_distance, mod_overlap.near_spawn_max_distance, player.surface) end - end + -- end -- If that fails, find a random map edge in a rand direction. + ---TODO: Add support for multiple surfaces. if ((newSpawn.x == 0) and (newSpawn.y == 0)) then - newSpawn = FindMapEdge(GetRandomVector(), player.surface) + newSpawn = FindMapEdge(GetRandomVector(), game.surfaces[global.ocfg.gameplay.main_force_surface]) ---TODO: Add support for multiple surfaces. log("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y) end @@ -366,7 +393,12 @@ function SpawnOptsGuiClick(event) ChangePlayerSpawn(player, newSpawn) -- Send the player there - QueuePlayerForDelayedSpawn(player.name, newSpawn, moatChoice, global.ocfg.enable_vanilla_spawns) + ---TODO: Add support for multiple surfaces. + QueuePlayerForDelayedSpawn(player.name, + game.surfaces[global.ocfg.gameplay.main_force_surface], + newSpawn, + moatChoice, + false) -- global.ocfg.enable_vanilla_spawns --TODO: Vanilla spawn points are not implemented yet. if (elemName == "isolated_spawn_near") then SendBroadcastMsg({"oarc-player-is-joining-near", player.name}) elseif (elemName == "isolated_spawn_far") then @@ -398,7 +430,9 @@ function SpawnOptsGuiClick(event) end --- Display the spawn options and explanation +---Display the spawn options and explanation +---@param player LuaPlayer +---@return nil function DisplaySharedSpawnOptions(player) player.gui.screen.add{name = "shared_spawn_opts", type = "frame", @@ -418,8 +452,8 @@ function DisplaySharedSpawnOptions(player) if (sharedSpawn.openAccess and (game.players[spawnName] ~= nil) and game.players[spawnName].connected) then - local spotsRemaining = global.ocfg.max_players_shared_spawn - #global.ocore.sharedSpawns[spawnName].players - if (global.ocfg.max_players_shared_spawn == 0) then + local spotsRemaining = global.ocfg.mod_overlap.number_of_players_per_shared_spawn - #global.ocore.sharedSpawns[spawnName].players + if (global.ocfg.mod_overlap.number_of_players_per_shared_spawn == 0) then shGui.add{type="button", caption=spawnName, name=spawnName} elseif (spotsRemaining > 0) then shGui.add{type="button", caption={"oarc-spawn-spots-remaining", spawnName, spotsRemaining}, name=spawnName} @@ -438,7 +472,9 @@ function DisplaySharedSpawnOptions(player) style = "back_button"} end --- Handle the gui click of the shared spawn options +---Handle the gui click of the shared spawn options +---@param event EventData.on_gui_click +---@return nil function SharedSpwnOptsGuiClick(event) if not (event and event.element and event.element.valid) then return end local player = game.players[event.player_index] @@ -465,15 +501,12 @@ function SharedSpwnOptsGuiClick(event) -- Else check for which spawn was selected -- If a spawn is removed during this time, the button will not do anything else - for spawnName,sharedSpawn in pairs(global.ocore.sharedSpawns) do + for spawnName,sharedSpawn in pairs(global.ocore.sharedSpawns --[[@as OarcSharedSpawnsTable]]) do if ((buttonClicked == spawnName) and (game.players[spawnName] ~= nil) and (game.players[spawnName].connected)) then -- Add the player to that shared spawns join queue. - if (global.ocore.sharedSpawns[spawnName].joinQueue == nil) then - global.ocore.sharedSpawns[spawnName].joinQueue = {} - end table.insert(global.ocore.sharedSpawns[spawnName].joinQueue, player.name) -- Clear the shared spawn options gui. @@ -492,6 +525,9 @@ function SharedSpwnOptsGuiClick(event) end end +---Display shared spawn join wait menu +---@param player LuaPlayer +---@return nil function DisplaySharedSpawnJoinWaitMenu(player) local sGui = player.gui.screen.add{name = "join_shared_spawn_wait_menu", @@ -511,7 +547,9 @@ function DisplaySharedSpawnJoinWaitMenu(player) style = "back_button"} end --- Handle the gui click of the buddy wait menu +---Handle the gui click of the shared spawn join wait menu +---@param event EventData.on_gui_click +---@return nil function SharedSpawnJoinWaitMenuClick(event) if not (event and event.element and event.element.valid) then return end local player = game.players[event.player_index] @@ -532,16 +570,14 @@ function SharedSpawnJoinWaitMenuClick(event) DisplaySpawnOptions(player) -- Find and remove the player from the joinQueue they were in. - for spawnName,sharedSpawn in pairs(global.ocore.sharedSpawns) do - if (sharedSpawn.joinQueue ~= nil) then + for spawnName,sharedSpawn in pairs(global.ocore.sharedSpawns --[[@as OarcSharedSpawnsTable]]) do for index,requestingPlayer in pairs(sharedSpawn.joinQueue) do if (requestingPlayer == player.name) then - global.ocore.sharedSpawns[spawnName].joinQueue[index] = false + global.ocore.sharedSpawns[spawnName].joinQueue[index] = nil game.players[spawnName].print({"oarc-player-cancel-join-request", player.name}) return end end - end end log("ERROR! Failed to remove player from joinQueue!") @@ -615,8 +651,7 @@ function CreateSpawnCtrlGuiTab(tab_container, player) -- Display a list of people in the join queue for your base. if (global.ocfg.enable_shared_spawns and IsSharedSpawnActive(player)) then - if ((global.ocore.sharedSpawns[player.name].joinQueue ~= nil) and - (#global.ocore.sharedSpawns[player.name].joinQueue > 0)) then + if (#global.ocore.sharedSpawns[player.name].joinQueue > 0) then AddLabel(spwnCtrls, "drop_down_msg_lbl1", {"oarc-select-player-join-queue"}, my_label_style)