-- separate_spawns.lua -- Nov 2016 -- -- Code that handles everything regarding giving each player a separate spawn -- Includes the GUI stuff -------------------------------------------------------------------------------- -- 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. function SeparateSpawnsPlayerCreated(event) local player = game.players[event.player_index] player.force = MAIN_FORCE 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 -- 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. SetupAndClearSpawnAreas(surface, chunkArea, global.uniqueSpawns) end -- Call this if a player leaves the game -- Still seems to have a bug. function FindUnusedSpawns(event) local player = game.players[event.player_index] if (player.online_time < MIN_ONLINE_TIME) then DropGravestoneChests(player) -- Clear out global variables for that player if (global.playerSpawns[player.name] ~= nil) then global.playerSpawns[player.name] = nil end -- Transfer or remove a shared spawn if player is owner if (global.sharedSpawns[player.name] ~= nil) then 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 _,otherSpawnPos in pairs(global.uniqueSpawns) do if (getDistance(spawnPos, otherSpawnPos) < (CHUNK_SIZE*10)) then nearOtherSpawn = true end end if (ENABLE_ABANDONED_BASE_REMOVAL and not nearOtherSpawn) then global.uniqueSpawns[player.name] = nil SendBroadcastMsg(player.name .. "'s base was marked for immediate clean up because they left within "..MIN_ONLINE_TIME_IN_MINUTES.." minutes of joining.") OarcRegrowthMarkForRemoval(spawnPos, 10) global.chunk_regrow.force_removal_flag = game.tick else table.insert(global.unusedSpawns, global.uniqueSpawns[player.name]) global.uniqueSpawns[player.name] = nil SendBroadcastMsg(player.name .. " base was freed up because they left within "..MIN_ONLINE_TIME_IN_MINUTES.." minutes of joining.") end 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 if ((#player.force.players <= 1) and (player.force.name ~= MAIN_FORCE)) then game.merge_forces(player.force, MAIN_FORCE) end -- Remove the character completely game.remove_offline_players({player}) end end -------------------------------------------------------------------------------- -- NON-EVENT RELATED FUNCTIONS -------------------------------------------------------------------------------- -- 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 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) then if (GetOnlinePlayersAtSharedSpawn(ownerName) < MAX_ONLINE_PLAYERS_AT_SHARED_SPAWN) then count = count+1 end end end return count end -- Initializes the globals used to track the special spawn and player -- status information function InitSpawnGlobalsAndForces() -- Containes an array of all player spawns -- A secondary array tracks whether the character will respawn there. if (global.playerSpawns == nil) then global.playerSpawns = {} end if (global.uniqueSpawns == nil) then global.uniqueSpawns = {} end if (global.sharedSpawns == nil) then global.sharedSpawns = {} end if (global.unusedSpawns == nil) then global.unusedSpawns = {} end if (global.playerCooldowns == nil) then global.playerCooldowns = {} end if (global.waitingBuddies == nil) then global.waitingBuddies = {} end if (global.delayedSpawns == nil) then global.delayedSpawns = {} end if (global.buddySpawnOptions == nil) then global.buddySpawnOptions = {} end game.create_force(MAIN_FORCE) game.forces[MAIN_FORCE].set_spawn_position(game.forces["player"].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) if ENABLE_SHARED_TEAM_VISION then game.forces[MAIN_FORCE].share_chart = true end SetCeaseFireBetweenAllForces() SetFriendlyBetweenAllForces() -- AntiGriefing(game.forces[MAIN_FORCE]) 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 function QueuePlayerForDelayedSpawn(player, spawn, moatEnabled) -- If we get a valid spawn point, setup the area if ((spawn.x ~= 0) and (spawn.y ~= 0)) then global.uniqueSpawns[player.name] = {pos=spawn,moat=moatEnabled} player.print("Generating your spawn now, please wait 10 seconds...") player.surface.request_to_generate_chunks(spawn, 4) delayedTick = game.tick + 10*TICKS_PER_SECOND table.insert(global.delayedSpawns, {player=player, spawn=spawn, moatEnabled=moatEnabled, delayedTick=delayedTick}) else DebugPrint("THIS SHOULD NOT EVER HAPPEN! Spawn failed!") SendBroadcastMsg("ERROR!! Failed to create spawn point for: " .. player.name) 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 SendPlayerToNewSpawnAndCreateIt(delayedSpawn.player, delayedSpawn.spawn, delayedSpawn.moatEnabled) table.remove(global.delayedSpawns, i) end end end end end function SendPlayerToNewSpawnAndCreateIt(player, spawn, moatEnabled) -- Make sure the area is super safe. ClearNearbyEnemies(spawn, SAFE_AREA_TILE_DIST, game.surfaces[GAME_SURFACE_NAME]) -- Create the spawn resources here CreateWaterStrip(game.surfaces[GAME_SURFACE_NAME], {x=spawn.x+WATER_SPAWN_OFFSET_X, y=spawn.y+WATER_SPAWN_OFFSET_Y}, WATER_SPAWN_LENGTH) CreateWaterStrip(game.surfaces[GAME_SURFACE_NAME], {x=spawn.x+WATER_SPAWN_OFFSET_X, y=spawn.y+WATER_SPAWN_OFFSET_Y+1}, WATER_SPAWN_LENGTH) GenerateStartingResources(surface, spawn) -- Send the player to that position player.teleport(spawn, GAME_SURFACE_NAME) GivePlayerStarterItems(player) end function SendPlayerToSpawn(player) if (DoesPlayerHaveCustomSpawn(player)) then player.teleport(global.playerSpawns[player.name], GAME_SURFACE_NAME) else player.teleport(game.forces[MAIN_FORCE].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) end end function SendPlayerToRandomSpawn(player) local numSpawns = TableLength(global.uniqueSpawns) local rndSpawn = math.random(0,numSpawns) local counter = 0 if (rndSpawn == 0) then player.teleport(game.forces[MAIN_FORCE].get_spawn_position(GAME_SURFACE_NAME), GAME_SURFACE_NAME) 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 end end end function CreatePlayerCustomForce(player) local newForce = nil -- Check if force already exists if (game.forces[player.name] ~= nil) then DebugPrint("Force already exists!") player.force = game.forces[player.name] return game.forces[player.name] -- Create a new force using the player's name elseif (TableLength(game.forces) < MAX_FORCES) then newForce = game.create_force(player.name) if ENABLE_SHARED_TEAM_VISION then newForce.share_chart = true end player.force = newForce SetCeaseFireBetweenAllForces() SetFriendlyBetweenAllForces() SendBroadcastMsg(player.name.." has started their own team!") else player.force = MAIN_FORCE player.print("Sorry, no new teams can be created. You were assigned to the default team instead.") end return newForce end -- For each force, if it's a valid force, chart the chunk that all active players -- are in. -- I have no idea how compute intensive this function is. If it starts to lag the game -- we'll have to figure out how to change it. -- function ShareVisionBetweenPlayers() -- if ((game.tick % 10) == 0) then -- for _,force in pairs(game.forces) do -- if (force ~= nil) then -- if ((force.name ~= enemy) and -- (force.name ~= neutral) and -- (force.name ~= player)) then -- for _,player in pairs(game.connected_players) do -- force.chart(GAME_SURFACE_NAME, -- {{player.position.x-(2*CHUNK_SIZE), -- player.position.y-(2*CHUNK_SIZE)}, -- {player.position.x+(2*CHUNK_SIZE), -- player.position.y+(2*CHUNK_SIZE)}}) -- end -- end -- end -- end -- end -- end -- For each force, if it's a valid force, chart the chunk that was just scanned -- for all forces. -- I have no idea how compute intensive this function is. If it starts to lag the game -- we'll have to figure out how to change it. -- function ShareRadarBetweenForces(event) -- for _,force in pairs(game.forces) do -- if (force ~= nil) then -- if ((force.name ~= enemy) and -- (force.name ~= neutral) and -- (force.name ~= player)) then -- for _,player in pairs(game.connected_players) do -- force.chart(GAME_SURFACE_NAME, -- {{event.chunk_position.x*CHUNK_SIZE, -- event.chunk_position.y*CHUNK_SIZE}, -- {event.chunk_position.x*CHUNK_SIZE, -- event.chunk_position.y*CHUNK_SIZE}}) -- end -- end -- end -- end -- end