1
0
mirror of https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git synced 2025-01-05 22:53:48 +02:00

Saving progress. Copy in old regrowth_map file. Halfway through updating the separate_spawns_guis.

This commit is contained in:
Oarcinae 2024-08-10 19:29:50 -04:00
parent 8bd24d9732
commit bd337e17e9
7 changed files with 1101 additions and 545 deletions

View File

@ -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.

View File

@ -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
----------------------------------------

View File

@ -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

View File

@ -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
-- --------------------------------------------------------------------------------

View File

@ -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

View File

@ -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<string, OarcPlayerSpawn>
---@alias OarcPlayerSpawnsTable table<string, OarcPlayerSpawn>
---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<string, OarcUniqueSpawn>
---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<string, OarcSharedSpawn>
@ -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

View File

@ -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)