2019-03-01 21:07:24 -05:00
-- separate_spawns.lua
-- Nov 2016
--
-- Code that handles everything regarding giving each player a separate spawn
-- Includes the GUI stuff
2019-03-11 09:29:00 -04:00
require ( " lib/oarc_utils " )
require ( " config " )
2019-03-01 21:07:24 -05:00
--------------------------------------------------------------------------------
-- EVENT RELATED FUNCTIONS
--------------------------------------------------------------------------------
-- When a new player is created, present the spawn options
-- Assign them to the main force so they can communicate with the team
-- without shouting.
2019-03-15 15:58:39 -04:00
function SeparateSpawnsPlayerCreated ( player_index )
local player = game.players [ player_index ]
2019-04-28 14:41:37 -04:00
2019-05-14 13:08:14 +08:00
-- This checks if they have just joined the server.
-- No assigned force yet.
2019-04-28 14:41:37 -04:00
if ( player.force . name ~= " player " ) then
FindUnusedSpawns ( player , false )
end
2019-07-24 06:09:55 -05:00
2019-04-10 20:40:34 -04:00
player.force = global.ocfg . main_force
2019-03-01 21:07:24 -05:00
DisplayWelcomeTextGui ( player )
end
-- Check if the player has a different spawn point than the default one
-- Make sure to give the default starting items
function SeparateSpawnsPlayerRespawned ( event )
local player = game.players [ event.player_index ]
SendPlayerToSpawn ( player )
end
-- This is the main function that creates the spawn area
-- Provides resources, land and a safe zone
function SeparateSpawnsGenerateChunk ( event )
local surface = event.surface
local chunkArea = event.area
2019-07-24 06:09:55 -05:00
2019-03-11 09:29:00 -04:00
-- Modify enemies first.
2019-04-10 20:40:34 -04:00
if global.ocfg . modified_enemy_spawning then
2019-03-11 09:29:00 -04:00
DowngradeWormsDistanceBasedOnChunkGenerate ( event )
end
2019-03-01 21:07:24 -05:00
-- This handles chunk generation near player spawns
-- If it is near a player spawn, it does a few things like make the area
-- safe and provide a guaranteed area of land and water tiles.
2019-03-19 16:46:31 -04:00
SetupAndClearSpawnAreas ( surface , chunkArea )
2019-03-01 21:07:24 -05:00
end
2019-04-28 14:41:37 -04:00
-- Call this if a player leaves the game or is reset
function FindUnusedSpawns ( player , remove_player )
if not player then
log ( " ERROR - FindUnusedSpawns on NIL Player! " )
return
end
2019-04-10 20:40:34 -04:00
if ( player.online_time < ( global.ocfg . minimum_online_time * TICKS_PER_MINUTE ) ) then
2019-03-01 21:07:24 -05:00
2019-05-14 13:08:14 +08:00
-- 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 )
2019-03-01 21:07:24 -05:00
-- Clear out global variables for that player
if ( global.playerSpawns [ player.name ] ~= nil ) then
global.playerSpawns [ player.name ] = nil
end
2019-07-24 06:09:55 -05:00
2019-03-13 21:06:08 -04:00
-- Remove them from the delayed spawn queue if they are in it
2019-03-01 21:07:24 -05:00
for i =# global.delayedSpawns , 1 , - 1 do
delayedSpawn = global.delayedSpawns [ i ]
if ( player.name == delayedSpawn.playerName ) then
2019-03-20 20:18:41 -04:00
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
2019-03-01 21:07:24 -05:00
table.remove ( global.delayedSpawns , i )
2019-03-26 16:56:59 -04:00
log ( " Removing player from delayed spawn queue: " .. player.name )
2019-03-01 21:07:24 -05:00
end
end
-- Transfer or remove a shared spawn if player is owner
if ( global.sharedSpawns [ player.name ] ~= nil ) then
2019-07-24 06:09:55 -05:00
2019-03-01 21:07:24 -05:00
local teamMates = global.sharedSpawns [ player.name ] . players
if ( # teamMates >= 1 ) then
local newOwnerName = table.remove ( teamMates )
TransferOwnershipOfSharedSpawn ( player.name , newOwnerName )
else
global.sharedSpawns [ player.name ] = nil
end
end
-- If a uniqueSpawn was created for the player, mark it as unused.
if ( global.uniqueSpawns [ player.name ] ~= nil ) then
local spawnPos = global.uniqueSpawns [ player.name ] . pos
-- Check if it was near someone else's base.
nearOtherSpawn = false
for spawnPlayerName , otherSpawnPos in pairs ( global.uniqueSpawns ) do
2019-04-10 13:59:57 -04:00
if ( ( spawnPlayerName ~= player.name ) and ( getDistance ( spawnPos , otherSpawnPos.pos ) < ( global.ocfg . spawn_config.gen_settings . land_area_tiles * 3 ) ) ) then
2019-03-26 16:56:59 -04:00
log ( " Won't remove base as it's close to another spawn: " .. spawnPlayerName )
2019-03-01 21:07:24 -05:00
nearOtherSpawn = true
end
end
2019-04-10 20:40:34 -04:00
if ( global.ocfg . enable_abandoned_base_removal and not nearOtherSpawn ) then
2019-03-20 20:18:41 -04:00
if ( global.uniqueSpawns [ player.name ] . vanilla ) then
log ( " Returning a vanilla spawn back to available. " )
table.insert ( global.vanillaSpawns , { x = spawnPos.x , y = spawnPos.y } )
end
2019-03-01 21:07:24 -05:00
2019-03-20 20:18:41 -04:00
global.uniqueSpawns [ player.name ] = nil
2019-03-20 16:02:50 -04:00
2019-03-29 21:27:42 -04:00
log ( " Removing base: " .. spawnPos.x .. " , " .. spawnPos.y )
2019-04-28 21:17:39 -04:00
OarcRegrowthMarkForRemoval ( spawnPos , CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS + 5 )
2019-04-10 20:40:34 -04:00
SendBroadcastMsg ( player.name .. " 's base was marked for immediate clean up because they left within " .. global.ocfg . minimum_online_time .. " minutes of joining. " )
2019-03-20 20:18:41 -04:00
global.chunk_regrow . force_removal_flag = game.tick
else
-- table.insert(global.unusedSpawns, global.uniqueSpawns[player.name]) -- Not used/implemented right now.
2019-03-01 21:07:24 -05:00
global.uniqueSpawns [ player.name ] = nil
2019-04-10 20:40:34 -04:00
SendBroadcastMsg ( player.name .. " base was freed up because they left within " .. global.ocfg . minimum_online_time .. " minutes of joining. " )
2019-03-20 20:18:41 -04:00
end
2019-03-01 21:07:24 -05:00
end
-- remove that player's cooldown setting
if ( global.playerCooldowns [ player.name ] ~= nil ) then
global.playerCooldowns [ player.name ] = nil
end
-- Remove from shared spawn player slots (need to search all)
for _ , sharedSpawn in pairs ( global.sharedSpawns ) do
for key , playerName in pairs ( sharedSpawn.players ) do
if ( player.name == playerName ) then
sharedSpawn.players [ key ] = nil ;
end
end
end
-- Remove a force if this player created it and they are the only one on it
2019-04-10 20:40:34 -04:00
if ( ( # player.force . players <= 1 ) and ( player.force . name ~= global.ocfg . main_force ) ) then
game.merge_forces ( player.force , global.ocfg . main_force )
2019-03-01 21:07:24 -05:00
end
-- Remove the character completely
2019-04-28 14:41:37 -04:00
if ( remove_player ) then
game.remove_offline_players ( { player } )
end
2019-03-01 21:07:24 -05:00
end
end
2019-03-11 09:29:00 -04:00
-- Clear the spawn areas.
-- This should be run inside the chunk generate event and be given a list of all
-- unique spawn points.
-- This clears enemies in the immediate area, creates a slightly safe area around it,
-- It no LONGER generates the resources though as that is now handled in a delayed event!
2019-03-19 16:46:31 -04:00
function SetupAndClearSpawnAreas ( surface , chunkArea )
for name , spawn in pairs ( global.uniqueSpawns ) do
2019-03-11 09:29:00 -04:00
2019-04-10 13:59:57 -04:00
-- Create a bunch of useful area and position variables
local landArea = GetAreaAroundPos ( spawn.pos , global.ocfg . spawn_config.gen_settings . land_area_tiles + CHUNK_SIZE )
local safeArea = GetAreaAroundPos ( spawn.pos , global.ocfg . spawn_config.safe_area . safe_radius )
local warningArea = GetAreaAroundPos ( spawn.pos , global.ocfg . spawn_config.safe_area . warn_radius )
local reducedArea = GetAreaAroundPos ( spawn.pos , global.ocfg . spawn_config.safe_area . danger_radius )
local chunkAreaCenter = { x = chunkArea.left_top . x + ( CHUNK_SIZE / 2 ) ,
y = chunkArea.left_top . y + ( CHUNK_SIZE / 2 ) }
local spawnPosOffset = { x = spawn.pos . x + global.ocfg . spawn_config.gen_settings . land_area_tiles ,
y = spawn.pos . y + global.ocfg . spawn_config.gen_settings . land_area_tiles }
-- Make chunks near a spawn safe by removing enemies
if CheckIfInArea ( chunkAreaCenter , safeArea ) then
RemoveAliensInArea ( surface , chunkArea )
2019-07-24 06:09:55 -05:00
2019-04-10 13:59:57 -04:00
-- Create a warning area with heavily reduced enemies
elseif CheckIfInArea ( chunkAreaCenter , warningArea ) then
ReduceAliensInArea ( surface , chunkArea , global.ocfg . 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
elseif CheckIfInArea ( chunkAreaCenter , reducedArea ) then
ReduceAliensInArea ( surface , chunkArea , global.ocfg . spawn_config.safe_area . danger_reduction )
-- DowngradeWormsInArea(surface, chunkArea, 50, 100, 100)
RemoveWormsInArea ( surface , chunkArea , false , false , true , true ) -- remove all huge/behemoth worms.
end
2019-03-11 09:29:00 -04:00
2019-04-10 13:59:57 -04:00
if ( not spawn.vanilla ) then
2019-03-19 16:46:31 -04:00
-- 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
2019-03-11 09:29:00 -04:00
2019-03-19 16:46:31 -04:00
-- Remove trees/resources inside the spawn area
2019-04-10 13:59:57 -04:00
RemoveInCircle ( surface , chunkArea , " tree " , spawn.pos , global.ocfg . spawn_config.gen_settings . land_area_tiles )
RemoveInCircle ( surface , chunkArea , " resource " , spawn.pos , global.ocfg . spawn_config.gen_settings . land_area_tiles + 5 )
RemoveInCircle ( surface , chunkArea , " cliff " , spawn.pos , global.ocfg . spawn_config.gen_settings . land_area_tiles + 5 )
2019-07-24 06:09:55 -05:00
RemoveDecorationsArea ( surface , chunkArea )
local fill_tile = " grass-1 "
if ( global.ocfg . locked_build_areas ) then
fill_tile = global.ocfg . locked_build_area_tile
end
2019-03-19 16:46:31 -04:00
2019-04-10 13:59:57 -04:00
if ( global.ocfg . spawn_config.gen_settings . tree_circle ) then
2019-07-24 06:09:55 -05:00
CreateCropCircle ( surface , spawn.pos , chunkArea , global.ocfg . spawn_config.gen_settings . land_area_tiles , fill_tile )
2019-03-19 16:46:31 -04:00
end
2019-04-10 13:59:57 -04:00
if ( global.ocfg . spawn_config.gen_settings . tree_octagon ) then
2019-07-24 06:09:55 -05:00
CreateCropOctagon ( surface , spawn.pos , chunkArea , global.ocfg . spawn_config.gen_settings . land_area_tiles , fill_tile )
2019-03-19 16:46:31 -04:00
end
2019-04-10 13:59:57 -04:00
if ( global.ocfg . spawn_config.gen_settings . moat_choice_enabled ) then
2019-03-19 16:46:31 -04:00
if ( spawn.moat ) then
2019-07-24 06:09:55 -05:00
CreateMoat ( surface , spawn.pos , chunkArea , global.ocfg . spawn_config.gen_settings . land_area_tiles , fill_tile )
2019-03-19 16:46:31 -04:00
end
2019-03-11 09:29:00 -04:00
end
end
end
end
end
2019-04-28 14:41:37 -04:00
-- Same as GetClosestPosFromTable but specific to global.uniqueSpawns
function GetClosestUniqueSpawn ( pos )
local closest_dist = nil
local closest_key = nil
for k , s in pairs ( global.uniqueSpawns ) do
local new_dist = getDistance ( pos , s.pos )
if ( closest_dist == nil ) then
closest_dist = new_dist
closest_key = k
elseif ( closest_dist > new_dist ) then
closest_dist = new_dist
closest_key = k
end
end
if ( closest_key == nil ) then
log ( " GetClosestUniqueSpawn ERROR - None found? " )
return nil
end
return global.uniqueSpawns [ closest_key ]
end
2019-03-11 09:29:00 -04:00
-- 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.
function ModifyEnemySpawnsNearPlayerStartingAreas ( event )
2019-03-15 08:50:42 -04:00
if ( not event.entity or not ( event.entity . force.name == " enemy " ) or not event.entity . position ) then
2019-03-26 16:56:59 -04:00
log ( " ModifyBiterSpawns - Unexpected use. " )
2019-03-11 09:29:00 -04:00
return
end
local enemy_pos = event.entity . position
local surface = event.entity . surface
local enemy_name = event.entity . name
2019-04-28 14:41:37 -04:00
local closest_spawn = GetClosestUniqueSpawn ( enemy_pos )
if ( closest_spawn == nil ) then
log ( " GetClosestUniqueSpawn ERROR - None found? " )
return
end
-- No enemies inside safe radius!
if ( getDistance ( enemy_pos , closest_spawn.pos ) < global.ocfg . spawn_config.safe_area . safe_radius ) then
event.entity . destroy ( )
-- Warn distance is all SMALL only.
2019-04-28 14:58:59 -04:00
elseif ( getDistance ( enemy_pos , closest_spawn.pos ) < global.ocfg . spawn_config.safe_area . warn_radius ) then
2019-04-28 14:41:37 -04:00
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 }
-- log("Downgraded biter close to spawn.")
elseif ( ( enemy_name == " big-spitter " ) or ( enemy_name == " behemoth-spitter " ) or ( enemy_name == " medium-spitter " ) ) then
event.entity . destroy ( )
surface.create_entity { name = " small-spitter " , position = enemy_pos , force = game.forces . enemy }
-- log("Downgraded spitter close to spawn.")
elseif ( ( enemy_name == " big-worm-turret " ) or ( enemy_name == " behemoth-worm-turret " ) or ( enemy_name == " medium-worm-turret " ) ) then
event.entity . destroy ( )
surface.create_entity { name = " small-worm-turret " , position = enemy_pos , force = game.forces . enemy }
-- log("Downgraded worm close to spawn.")
end
-- Danger distance is MEDIUM max.
elseif ( getDistance ( enemy_pos , closest_spawn.pos ) < 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 }
-- log("Downgraded biter further from spawn.")
elseif ( ( enemy_name == " big-spitter " ) or ( enemy_name == " behemoth-spitter " ) ) then
event.entity . destroy ( )
surface.create_entity { name = " medium-spitter " , position = enemy_pos , force = game.forces . enemy }
-- log("Downgraded spitter further from spawn
elseif ( ( enemy_name == " big-worm-turret " ) or ( enemy_name == " behemoth-worm-turret " ) ) then
event.entity . destroy ( )
surface.create_entity { name = " medium-worm-turret " , position = enemy_pos , force = game.forces . enemy }
-- log("Downgraded worm further from spawn.")
2019-03-11 09:29:00 -04:00
end
end
end
2019-03-01 21:07:24 -05:00
--------------------------------------------------------------------------------
-- NON-EVENT RELATED FUNCTIONS
--------------------------------------------------------------------------------
2019-03-11 09:29:00 -04:00
-- Generate the basic starter resource around a given location.
function GenerateStartingResources ( surface , pos )
2019-04-10 13:59:57 -04:00
local rand_settings = global.ocfg . spawn_config.resource_rand_pos_settings
2019-03-11 09:29:00 -04:00
2019-03-15 16:09:14 -04:00
-- Generate all resource tile patches
if ( not rand_settings.enabled ) then
2019-04-10 13:59:57 -04:00
for t_name , t_data in pairs ( global.ocfg . spawn_config.resource_tiles ) do
2019-03-15 16:09:14 -04:00
local pos = { x = pos.x + t_data.x_offset , y = pos.y + t_data.y_offset }
GenerateResourcePatch ( surface , t_name , t_data.size , pos , t_data.amount )
end
else
2019-03-15 16:47:38 -04:00
-- Create list of resource tiles
local r_list = { }
2019-04-10 13:59:57 -04:00
for k , _ in pairs ( global.ocfg . spawn_config.resource_tiles ) do
if ( k ~= " " ) then
table.insert ( r_list , k )
end
2019-03-15 16:47:38 -04:00
end
local shuffled_list = FYShuffle ( r_list )
-- This places resources in a semi-circle
-- Tweak in config.lua
local angle_offset = rand_settings.angle_offset
2019-04-10 13:59:57 -04:00
local num_resources = TableLength ( global.ocfg . spawn_config.resource_tiles )
2019-03-15 16:47:38 -04:00
local theta = ( ( rand_settings.angle_final - rand_settings.angle_offset ) / num_resources ) ;
2019-03-15 16:09:14 -04:00
local count = 0
2019-03-11 09:29:00 -04:00
2019-03-15 16:47:38 -04:00
for _ , k_name in pairs ( shuffled_list ) do
local angle = ( theta * count ) + angle_offset ;
2019-03-11 09:29:00 -04:00
2019-03-15 16:47:38 -04:00
local tx = ( rand_settings.radius * math.cos ( angle ) ) + pos.x
local ty = ( rand_settings.radius * math.sin ( angle ) ) + pos.y
2019-03-11 09:29:00 -04:00
2019-03-15 16:09:14 -04:00
local pos = { x = math.floor ( tx ) , y = math.floor ( ty ) }
2019-04-10 13:59:57 -04:00
GenerateResourcePatch ( surface , k_name , global.ocfg . spawn_config.resource_tiles [ k_name ] . size , pos , global.ocfg . spawn_config.resource_tiles [ k_name ] . amount )
2019-03-15 16:09:14 -04:00
count = count + 1
end
end
-- Generate special resource patches (oil)
2019-04-10 13:59:57 -04:00
for p_name , p_data in pairs ( global.ocfg . spawn_config.resource_patches ) do
2019-03-15 16:09:14 -04:00
local oil_patch_x = pos.x + p_data.x_offset_start
local oil_patch_y = pos.y + p_data.y_offset_start
for i = 1 , p_data.num_patches do
surface.create_entity ( { name = p_name , amount = p_data.amount ,
position = { oil_patch_x , oil_patch_y } } )
oil_patch_x = oil_patch_x + p_data.x_offset_next
oil_patch_y = oil_patch_y + p_data.y_offset_next
end
end
2019-03-11 09:29:00 -04:00
end
2019-03-01 21:07:24 -05:00
-- Add a spawn to the shared spawn global
-- Used for tracking which players are assigned to it, where it is and if
-- it is open for new players to join
function CreateNewSharedSpawn ( player )
global.sharedSpawns [ player.name ] = { openAccess = true ,
position = global.playerSpawns [ player.name ] ,
players = { } }
end
function TransferOwnershipOfSharedSpawn ( prevOwnerName , newOwnerName )
-- Transfer the shared spawn global
global.sharedSpawns [ newOwnerName ] = global.sharedSpawns [ prevOwnerName ]
global.sharedSpawns [ newOwnerName ] . openAccess = false
global.sharedSpawns [ prevOwnerName ] = nil
-- Transfer the unique spawn global
global.uniqueSpawns [ newOwnerName ] = global.uniqueSpawns [ prevOwnerName ]
global.uniqueSpawns [ prevOwnerName ] = nil
game.players [ newOwnerName ] . print ( " You have been given ownership of this base! " )
end
-- Returns the number of players currently online at the shared spawn
function GetOnlinePlayersAtSharedSpawn ( ownerName )
if ( global.sharedSpawns [ ownerName ] ~= nil ) then
-- Does not count base owner
local count = 0
-- For each player in the shared spawn, check if online and add to count.
for _ , player in pairs ( game.connected_players ) do
if ( ownerName == player.name ) then
count = count + 1
end
for _ , playerName in pairs ( global.sharedSpawns [ ownerName ] . players ) do
2019-07-24 06:09:55 -05:00
2019-03-01 21:07:24 -05:00
if ( playerName == player.name ) then
count = count + 1
end
end
end
return count
else
return 0
end
end
-- 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.
function GetNumberOfAvailableSharedSpawns ( )
local count = 0
for ownerName , sharedSpawn in pairs ( global.sharedSpawns ) do
if ( sharedSpawn.openAccess and
( game.players [ ownerName ] ~= nil ) and
game.players [ ownerName ] . connected ) then
2019-04-10 20:40:34 -04:00
if ( ( global.ocfg . max_players_shared_spawn == 0 ) or
2019-06-07 22:52:14 -05:00
( # global.sharedSpawns [ ownerName ] . players < global.ocfg . max_players_shared_spawn ) ) then
2019-03-01 21:07:24 -05:00
count = count + 1
end
end
end
return count
end
-- Initializes the globals used to track the special spawn and player
-- status information
function InitSpawnGlobalsAndForces ( )
2019-07-24 06:09:55 -05:00
2019-03-13 21:06:08 -04:00
-- 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.
2019-03-01 21:07:24 -05:00
if ( global.playerSpawns == nil ) then
global.playerSpawns = { }
end
2019-03-13 21:06:08 -04:00
-- This is the most important table. It is a list of all the unique spawn points.
-- This is what chunk generation checks against.
2019-03-19 16:46:31 -04:00
-- Each entry looks like this: {pos={x,y},moat=bool,vanilla=bool}
2019-03-01 21:07:24 -05:00
if ( global.uniqueSpawns == nil ) then
global.uniqueSpawns = { }
end
2019-03-13 21:06:08 -04:00
2019-03-19 16:46:31 -04:00
-- List of available vanilla spawns
2019-03-15 16:15:01 -04:00
if ( global.vanillaSpawns == nil ) then
global.vanillaSpawns = { }
end
2019-03-13 21:06:08 -04:00
-- This keeps a list of any player that has shared their base.
-- Each entry contains information about if it's open, spawn pos, and players in the group.
2019-03-01 21:07:24 -05:00
if ( global.sharedSpawns == nil ) then
global.sharedSpawns = { }
end
2019-03-13 21:06:08 -04:00
-- This seems to be unused right now, but I had plans to re-use spawn points in the past.
-- if (global.unusedSpawns == nil) then
-- global.unusedSpawns = {}
-- end
-- Each player has an option to change their respawn which has a cooldown when used.
-- Other similar abilities/functions that require cooldowns could be added here.
2019-03-01 21:07:24 -05:00
if ( global.playerCooldowns == nil ) then
global.playerCooldowns = { }
end
2019-03-13 21:06:08 -04:00
-- List of players in the "waiting room" for a buddy spawn.
-- They show up in the list to select when doing a buddy spawn.
2019-03-01 21:07:24 -05:00
if ( global.waitingBuddies == nil ) then
global.waitingBuddies = { }
end
2019-03-13 21:06:08 -04:00
-- Players who have made a spawn choice get put into this list while waiting.
-- An on_tick event checks when it expires and then places down the base resources, and teleports the player.
-- Go look at DelayedSpawnOnTick() for more info.
2019-03-01 21:07:24 -05:00
if ( global.delayedSpawns == nil ) then
global.delayedSpawns = { }
end
2019-03-13 21:06:08 -04:00
-- This is what I use to communicate a buddy spawn request between the buddies.
-- This contains information of who is asking, and what options were selected.
2019-03-01 21:07:24 -05:00
if ( global.buddySpawnOptions == nil ) then
global.buddySpawnOptions = { }
end
2019-04-10 13:59:57 -04:00
-- Silo info
if ( global.siloPosition == nil ) then
global.siloPosition = { }
2019-03-13 21:06:08 -04:00
end
2019-04-10 21:47:18 -04:00
-- Name a new force to be the default force.
-- This is what any new player is assigned to when they join, even before they spawn.
local main_force = CreateForce ( global.ocfg . main_force )
main_force.set_spawn_position ( { x = 0 , y = 0 } , GAME_SURFACE_NAME )
2019-03-01 21:07:24 -05:00
end
function DoesPlayerHaveCustomSpawn ( player )
for name , spawnPos in pairs ( global.playerSpawns ) do
if ( player.name == name ) then
return true
end
end
return false
end
function ChangePlayerSpawn ( player , pos )
global.playerSpawns [ player.name ] = pos
global.playerCooldowns [ player.name ] = { setRespawn = game.tick }
end
2019-03-19 16:46:31 -04:00
function QueuePlayerForDelayedSpawn ( playerName , spawn , moatEnabled , vanillaSpawn )
2019-07-24 06:09:55 -05:00
2019-03-01 21:07:24 -05:00
-- If we get a valid spawn point, setup the area
2019-03-20 16:02:50 -04:00
if ( ( spawn.x ~= 0 ) or ( spawn.y ~= 0 ) ) then
2019-03-19 16:46:31 -04:00
global.uniqueSpawns [ playerName ] = { pos = spawn , moat = moatEnabled , vanilla = vanillaSpawn }
2019-03-01 21:07:24 -05:00
2019-04-10 13:59:57 -04:00
local delay_spawn_seconds = 5 * ( math.ceil ( global.ocfg . spawn_config.gen_settings . land_area_tiles / CHUNK_SIZE ) )
2019-03-01 21:07:24 -05:00
2019-03-20 16:02:50 -04:00
game.players [ playerName ] . print ( " Generating your spawn now, please wait for at least " .. delay_spawn_seconds .. " seconds... " )
2019-03-01 21:07:24 -05:00
game.players [ playerName ] . surface.request_to_generate_chunks ( spawn , 4 )
delayedTick = game.tick + delay_spawn_seconds * TICKS_PER_SECOND
2019-03-19 16:46:31 -04:00
table.insert ( global.delayedSpawns , { playerName = playerName , pos = spawn , moat = moatEnabled , vanilla = vanillaSpawn , delayedTick = delayedTick } )
2019-03-01 21:07:24 -05:00
DisplayPleaseWaitForSpawnDialog ( game.players [ playerName ] , delay_spawn_seconds )
2019-07-24 06:09:55 -05:00
else
2019-03-26 16:56:59 -04:00
log ( " THIS SHOULD NOT EVER HAPPEN! Spawn failed! " )
2019-03-01 21:07:24 -05:00
SendBroadcastMsg ( " ERROR!! Failed to create spawn point for: " .. playerName )
end
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.
function DelayedSpawnOnTick ( )
if ( ( game.tick % ( 30 ) ) == 1 ) then
if ( ( global.delayedSpawns ~= nil ) and ( # global.delayedSpawns > 0 ) ) then
for i =# global.delayedSpawns , 1 , - 1 do
delayedSpawn = global.delayedSpawns [ i ]
if ( delayedSpawn.delayedTick < game.tick ) then
-- TODO, add check here for if chunks around spawn are generated surface.is_chunk_generated(chunkPos)
if ( game.players [ delayedSpawn.playerName ] ~= nil ) then
2019-03-19 16:46:31 -04:00
SendPlayerToNewSpawnAndCreateIt ( delayedSpawn )
2019-03-01 21:07:24 -05:00
end
table.remove ( global.delayedSpawns , i )
end
end
end
end
end
2019-03-19 16:46:31 -04:00
function SendPlayerToNewSpawnAndCreateIt ( delayedSpawn )
2019-03-01 21:07:24 -05:00
2019-03-11 09:29:00 -04:00
-- DOUBLE CHECK and make sure the area is super safe.
2019-04-10 13:59:57 -04:00
ClearNearbyEnemies ( delayedSpawn.pos , global.ocfg . spawn_config.safe_area . safe_radius , game.surfaces [ GAME_SURFACE_NAME ] )
2019-03-19 16:46:31 -04:00
if ( not delayedSpawn.vanilla ) then
-- Create the spawn resources here
2019-04-10 13:59:57 -04:00
local water_data = global.ocfg . spawn_config.water
2019-03-19 16:46:31 -04:00
CreateWaterStrip ( game.surfaces [ GAME_SURFACE_NAME ] ,
{ x = delayedSpawn.pos . x + water_data.x_offset , y = delayedSpawn.pos . y + water_data.y_offset } ,
water_data.length )
CreateWaterStrip ( game.surfaces [ GAME_SURFACE_NAME ] ,
{ x = delayedSpawn.pos . x + water_data.x_offset , y = delayedSpawn.pos . y + water_data.y_offset + 1 } ,
water_data.length )
GenerateStartingResources ( game.surfaces [ GAME_SURFACE_NAME ] , delayedSpawn.pos )
end
2019-03-01 21:07:24 -05:00
-- Send the player to that position
2019-03-19 16:46:31 -04:00
local player = game.players [ delayedSpawn.playerName ]
player.teleport ( delayedSpawn.pos , GAME_SURFACE_NAME )
GivePlayerStarterItems ( game.players [ delayedSpawn.playerName ] )
2019-03-01 21:07:24 -05:00
-- Chart the area.
2019-04-10 13:59:57 -04:00
ChartArea ( player.force , delayedSpawn.pos , math.ceil ( global.ocfg . spawn_config.gen_settings . land_area_tiles / CHUNK_SIZE ) , player.surface )
2019-03-01 21:07:24 -05:00
2019-03-19 16:46:31 -04:00
if ( player.gui . center.wait_for_spawn_dialog ~= nil ) then
player.gui . center.wait_for_spawn_dialog . destroy ( )
2019-03-01 21:07:24 -05:00
end
end
function SendPlayerToSpawn ( player )
if ( DoesPlayerHaveCustomSpawn ( player ) ) then
player.teleport ( global.playerSpawns [ player.name ] , GAME_SURFACE_NAME )
else
2019-04-10 20:40:34 -04:00
player.teleport ( game.forces [ global.ocfg . main_force ] . get_spawn_position ( GAME_SURFACE_NAME ) , GAME_SURFACE_NAME )
2019-03-01 21:07:24 -05:00
end
end
function SendPlayerToRandomSpawn ( player )
local numSpawns = TableLength ( global.uniqueSpawns )
local rndSpawn = math.random ( 0 , numSpawns )
local counter = 0
if ( rndSpawn == 0 ) then
2019-04-10 20:40:34 -04:00
player.teleport ( game.forces [ global.ocfg . main_force ] . get_spawn_position ( GAME_SURFACE_NAME ) , GAME_SURFACE_NAME )
2019-03-01 21:07:24 -05:00
else
counter = counter + 1
for name , spawn in pairs ( global.uniqueSpawns ) do
if ( counter == rndSpawn ) then
player.teleport ( spawn.pos )
break
end
counter = counter + 1
2019-07-24 06:09:55 -05:00
end
2019-03-01 21:07:24 -05:00
end
end
2019-04-10 21:47:18 -04:00
function CreateForce ( force_name )
2019-03-01 21:07:24 -05:00
local newForce = nil
2019-07-24 06:09:55 -05:00
2019-03-01 21:07:24 -05:00
-- Check if force already exists
2019-04-10 21:47:18 -04:00
if ( game.forces [ force_name ] ~= nil ) then
2019-03-26 16:56:59 -04:00
log ( " Force already exists! " )
2019-04-10 21:47:18 -04:00
return game.forces [ global.ocfg . main_force ]
2019-03-01 21:07:24 -05:00
2019-04-10 21:47:18 -04:00
-- Create a new force
2019-03-01 21:07:24 -05:00
elseif ( TableLength ( game.forces ) < MAX_FORCES ) then
2019-04-10 21:47:18 -04:00
newForce = game.create_force ( force_name )
2019-04-10 20:40:34 -04:00
if global.ocfg . enable_shared_team_vision then
2019-03-01 21:07:24 -05:00
newForce.share_chart = true
end
2019-04-10 20:40:34 -04:00
if global.ocfg . enable_research_queue then
2019-03-13 21:06:08 -04:00
newForce.research_queue_enabled = true
end
2019-03-01 21:07:24 -05:00
-- Chart silo areas if necessary
2019-04-10 20:40:34 -04:00
if global.ocfg . frontier_rocket_silo and global.ocfg . frontier_silo_vision then
2019-03-01 21:07:24 -05:00
ChartRocketSiloAreas ( game.surfaces [ GAME_SURFACE_NAME ] , newForce )
end
SetCeaseFireBetweenAllForces ( )
SetFriendlyBetweenAllForces ( )
2019-04-28 22:25:18 -05:00
newForce.friendly_fire = false
2019-03-01 21:07:24 -05:00
if ( ENABLE_ANTI_GRIEFING ) then
AntiGriefing ( newForce )
2019-04-10 21:57:25 -04:00
end
2019-06-07 23:07:00 -05:00
if global.ocfg . lock_goodies_rocket_launch and not global.satellite_sent then
2019-04-10 21:57:25 -04:00
DisableTech ( newForce , " atomic-bomb " )
2019-05-03 16:52:38 -05:00
DisableTech ( newForce , " power-armor-mk2 " )
2019-04-10 21:57:25 -04:00
DisableTech ( newForce , " artillery " )
end
2019-04-10 21:47:18 -04:00
else
log ( " TOO MANY FORCES!!! - CreateForce() " )
end
return newForce
end
function CreatePlayerCustomForce ( player )
local newForce = CreateForce ( player.name )
player.force = newForce
if ( newForce.name == player.name ) then
SendBroadcastMsg ( player.name .. " has started their own team! " )
2019-03-01 21:07:24 -05:00
else
player.print ( " Sorry, no new teams can be created. You were assigned to the default team instead. " )
end
return newForce
end
2019-03-19 16:46:31 -04:00
-- 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 = { }
-- 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 )
if ( sqrt_count < 1 ) then
2019-03-26 16:56:59 -04:00
log ( " CreateVanillaSpawns less than 1!! " )
2019-03-19 16:46:31 -04:00
return
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
table.insert ( points , { x = i * spacing , y = j * spacing } )
table.insert ( global.vanillaSpawns , { x = i * spacing , y = j * spacing } )
end
end
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
2019-03-20 16:02:50 -04:00
-- 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
2019-07-24 06:09:55 -05:00
2019-03-20 16:02:50 -04:00
-- Check if chunks nearby are not generated.
local chunk_pos = GetChunkPosFromTilePos ( v )
2019-04-28 21:17:39 -04:00
if IsChunkAreaUngenerated ( chunk_pos , CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS + 15 , surface ) then
2019-03-20 16:02:50 -04:00
-- 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 )
2019-07-24 06:09:55 -05:00
2019-03-20 16:02:50 -04:00
-- 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
2019-03-28 20:01:24 -04:00
best_distance = new_distance
2019-03-20 16:02:50 -04:00
end
end
2019-07-24 06:09:55 -05:00
2019-03-20 16:02:50 -04:00
-- If it's not a valid spawn anymore, let's remove it.
else
2019-03-20 20:18:41 -04:00
log ( " Removing vanilla spawn due to chunks generated: x= " .. v.x .. " ,y= " .. v.y )
2019-03-20 16:02:50 -04:00
table.remove ( global.vanillaSpawns , k )
2019-07-24 06:09:55 -05:00
end
2019-03-20 16:02:50 -04:00
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
2019-03-26 16:56:59 -04:00
log ( " Found unused vanilla spawn: x= " .. spawn_pos.x .. " ,y= " .. spawn_pos.y )
2019-03-20 16:02:50 -04:00
return spawn_pos
end
function ValidateVanillaSpawns ( surface )
for k , v in pairs ( global.vanillaSpawns ) do
2019-07-24 06:09:55 -05:00
2019-03-20 16:02:50 -04:00
-- Check if chunks nearby are not generated.
local chunk_pos = GetChunkPosFromTilePos ( v )
2019-04-28 21:17:39 -04:00
if not IsChunkAreaUngenerated ( chunk_pos , CHECK_SPAWN_UNGENERATED_CHUNKS_RADIUS + 15 , surface ) then
2019-03-20 20:18:41 -04:00
log ( " Removing vanilla spawn due to chunks generated: x= " .. v.x .. " ,y= " .. v.y )
2019-03-20 16:02:50 -04:00
table.remove ( global.vanillaSpawns , k )
2019-07-24 06:09:55 -05:00
end
2019-03-20 16:02:50 -04:00
end
end
2019-03-20 20:18:41 -04:00