1
0
mirror of https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git synced 2024-12-14 10:23:17 +02:00
FactorioScenarioMultiplayer.../separate_spawns.lua

423 lines
16 KiB
Lua

-- 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)
global.playerCooldowns[player.name] = {setRespawn=event.tick}
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
if surface.name ~= "nauvis" then return end
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.
for name,spawnPos in pairs(global.playerSpawns) do
local landArea = {left_top=
{x=spawnPos.x-ENFORCE_LAND_AREA_TILE_DIST,
y=spawnPos.y-ENFORCE_LAND_AREA_TILE_DIST},
right_bottom=
{x=spawnPos.x+ENFORCE_LAND_AREA_TILE_DIST,
y=spawnPos.y+ENFORCE_LAND_AREA_TILE_DIST}}
local safeArea = {left_top=
{x=spawnPos.x-SAFE_AREA_TILE_DIST,
y=spawnPos.y-SAFE_AREA_TILE_DIST},
right_bottom=
{x=spawnPos.x+SAFE_AREA_TILE_DIST,
y=spawnPos.y+SAFE_AREA_TILE_DIST}}
local warningArea = {left_top=
{x=spawnPos.x-WARNING_AREA_TILE_DIST,
y=spawnPos.y-WARNING_AREA_TILE_DIST},
right_bottom=
{x=spawnPos.x+WARNING_AREA_TILE_DIST,
y=spawnPos.y+WARNING_AREA_TILE_DIST}}
local chunkAreaCenter = {x=chunkArea.left_top.x+(CHUNK_SIZE/2),
y=chunkArea.left_top.y+(CHUNK_SIZE/2)}
-- Make chunks near a spawn safe by removing enemies
if CheckIfInArea(chunkAreaCenter,safeArea) then
for _, entity in pairs(surface.find_entities_filtered{area = chunkArea, force = "enemy"}) do
entity.destroy()
end
-- Create a warning area with reduced enemies
elseif CheckIfInArea(chunkAreaCenter,warningArea) then
local counter = 0
for _, entity in pairs(surface.find_entities_filtered{area = chunkArea, force = "enemy"}) do
if ((counter % WARN_AREA_REDUCTION_RATIO) ~= 0) then
entity.destroy()
end
counter = counter + 1
end
-- Remove all big and huge worms
for _, entity in pairs(surface.find_entities_filtered{area = chunkArea, name = "medium-worm-turret"}) do
entity.destroy()
end
for _, entity in pairs(surface.find_entities_filtered{area = chunkArea, name = "big-worm-turret"}) do
entity.destroy()
end
end
-- Fill in any water to make sure we have guaranteed land mass at the spawn point.
if CheckIfInArea(chunkAreaCenter,landArea) then
-- remove trees in the immediate areas?
for key, entity in pairs(surface.find_entities_filtered({area=chunkArea, type= "tree"})) do
if ((spawnPos.x - entity.position.x)^2 + (spawnPos.y - entity.position.y)^2 < ENFORCE_LAND_AREA_TILE_DIST^2) then
entity.destroy()
end
end
CreateCropCircle(surface, spawnPos, chunkArea, ENFORCE_LAND_AREA_TILE_DIST)
end
-- Provide a guaranteed spot of water to use for power generation
-- A desert biome will shrink the water area!!
if CheckIfInArea(spawnPos,chunkArea) then
local waterTiles = {{name = "water", position ={spawnPos.x+0,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+1,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+2,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+3,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+4,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+5,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+6,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+7,spawnPos.y-30}},
{name = "water", position ={spawnPos.x+8,spawnPos.y-30}}}
-- DebugPrint("Setting water tiles in this chunk! " .. chunkArea.left_top.x .. "," .. chunkArea.left_top.y)
surface.set_tiles(waterTiles)
end
end
end
-- Call this if a player leaves the game
-- Seems to be susceptiable to causing desyncs...
function FindUnusedSpawns(event)
local player = game.players[event.player_index]
if (player.online_time < MIN_ONLINE_TIME) then
-- TODO dump items into a chest.
-- Clear out global variables for that player???
if (global.playerSpawns[player.name] ~= nil) then
global.playerSpawns[player.name] = nil
end
-- If a uniqueSpawn was created for the player, mark it as unused.
if (global.uniqueSpawns[player.name] ~= nil) then
table.insert(global.unusedSpawns, global.uniqueSpawns[player.name])
global.uniqueSpawns[player.name] = nil
SendBroadcastMsg(player.name .. " base was freed up because they left within 5 minutes of joining.")
end
-- Remove from shared spawns
if (global.sharedSpawns[player.name] ~= nil) then
global.sharedSpawns[player.name] = nil
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 the character completely
game.remove_offline_players({player})
end
end
function CreateNewSharedSpawn(player)
global.sharedSpawns[player.name] = {openAccess=true,
position=global.playerSpawns[player.name],
players={}}
end
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
--------------------------------------------------------------------------------
-- NON-EVENT RELATED FUNCTIONS
-- These should be local functions where possible!
--------------------------------------------------------------------------------
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
game.create_force(MAIN_FORCE)
game.forces[MAIN_FORCE].set_spawn_position(game.forces["player"].get_spawn_position("nauvis"), "nauvis")
SetCeaseFireBetweenAllForces()
end
function GenerateStartingResources(player)
local surface = player.surface
-- Generate stone
local stonePos = {x=player.position.x+START_RESOURCE_STONE_POS_X,
y=player.position.y+START_RESOURCE_STONE_POS_Y}
-- Generate coal
local coalPos = {x=player.position.x+START_RESOURCE_COAL_POS_X,
y=player.position.y+START_RESOURCE_COAL_POS_Y}
-- Generate copper ore
local copperOrePos = {x=player.position.x+START_RESOURCE_COPPER_POS_X,
y=player.position.y+START_RESOURCE_COPPER_POS_Y}
-- Generate iron ore
local ironOrePos = {x=player.position.x+START_RESOURCE_IRON_POS_X,
y=player.position.y+START_RESOURCE_IRON_POS_Y}
-- Tree generation is taken care of in chunk generation
-- Generate oil patches
surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT,
position={player.position.x+START_RESOURCE_OIL_POS_X, player.position.y+START_RESOURCE_OIL_POS_Y-2}})
surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT,
position={player.position.x+START_RESOURCE_OIL_POS_X, player.position.y+START_RESOURCE_OIL_POS_Y+2}})
local midPoint = math.floor(START_RESOURCE_STONE_SIZE/2)
for y=0, START_RESOURCE_STONE_SIZE do
for x=0, START_RESOURCE_STONE_SIZE do
if (((x-midPoint)^2 + (y-midPoint)^2 < midPoint^2) or not ENABLE_RESOURCE_SHAPE_CIRCLE) then
surface.create_entity({name="stone", amount=START_STONE_AMOUNT,
position={stonePos.x+x, stonePos.y+y}})
end
end
end
local midPoint = math.floor(START_RESOURCE_COAL_SIZE/2)
for y=0, START_RESOURCE_COAL_SIZE do
for x=0, START_RESOURCE_COAL_SIZE do
if (((x-midPoint)^2 + (y-midPoint)^2 < midPoint^2) or not ENABLE_RESOURCE_SHAPE_CIRCLE) then
surface.create_entity({name="coal", amount=START_COAL_AMOUNT,
position={coalPos.x+x, coalPos.y+y}})
end
end
end
local midPoint = math.floor(START_RESOURCE_COPPER_SIZE/2)
for y=0, START_RESOURCE_COPPER_SIZE do
for x=0, START_RESOURCE_COPPER_SIZE do
if (((x-midPoint)^2 + (y-midPoint)^2 < midPoint^2) or not ENABLE_RESOURCE_SHAPE_CIRCLE) then
surface.create_entity({name="copper-ore", amount=START_COPPER_AMOUNT,
position={copperOrePos.x+x, copperOrePos.y+y}})
end
end
end
local midPoint = math.floor(START_RESOURCE_IRON_SIZE/2)
for y=0, START_RESOURCE_IRON_SIZE do
for x=0, START_RESOURCE_IRON_SIZE do
if (((x-midPoint)^2 + (y-midPoint)^2 < midPoint^2) or not ENABLE_RESOURCE_SHAPE_CIRCLE) then
surface.create_entity({name="iron-ore", amount=START_IRON_AMOUNT,
position={ironOrePos.x+x, ironOrePos.y+y}})
end
end
end
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
end
function SendPlayerToNewSpawnAndCreateIt(player, spawn)
-- Send the player to that position
player.teleport(spawn)
GivePlayerStarterItems(player)
ChartArea(player.force, player.position, 4)
-- If we get a valid spawn point, setup the area
if ((spawn.x ~= 0) and (spawn.y ~= 0)) then
global.uniqueSpawns[player.name] = spawn
GenerateStartingResources(player)
ClearNearbyEnemies(player, SAFE_AREA_TILE_DIST)
else
DebugPrint("THIS SHOULD NOT EVER HAPPEN! Spawn failed!")
SendBroadcastMsg("Failed to create spawn point for: " .. player.name)
end
end
function SendPlayerToSpawn(player)
if (DoesPlayerHaveCustomSpawn(player)) then
player.teleport(global.playerSpawns[player.name])
else
player.teleport(game.forces[MAIN_FORCE].get_spawn_position("nauvis"))
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("nauvis"))
else
counter = counter + 1
for name,spawnPos in pairs(global.uniqueSpawns) do
if (counter == rndSpawn) then
player.teleport(spawnPos)
break
end
counter = counter + 1
end
end
end
--------------------------------------------------------------------------------
-- UNUSED CODE
-- Either didn't work, or not used or not tested....
--------------------------------------------------------------------------------
-- local tick_counter = 0
-- function ShareVision(event)
-- if (tick_counter > (TICKS_PER_SECOND*30)) then
-- ShareVisionForAllForces()
-- tick_counter = 0
-- end
-- tick_counter = tick_counter + 1
-- end
-- function CreatePlayerCustomForce(player)
-- local newForce = nil
-- -- Check if force already exists
-- if (game.forces[player.name] ~= nil) then
-- 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)
-- player.force = newForce
-- SetCeaseFireBetweenAllForces()
-- 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