mirror of
https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git
synced 2024-12-12 10:13:58 +02:00
1006 lines
38 KiB
Lua
Executable File
1006 lines
38 KiB
Lua
Executable File
-- Oarc's Separated Spawn Scenario
|
|
-- I wanted to create a scenario that allows you to spawn in separate locations
|
|
-- Additionally, it allows you to either join the main force or go it alone.
|
|
-- All teams(forces) are always neutral to each other (ceasefire mode).
|
|
--
|
|
-- Each spawn location has some basic starter resources enforced, except for
|
|
-- the main default 0,0 starting location
|
|
--
|
|
-- Around each spawn area, a safe area is created and then a reduced alien areaas
|
|
-- as well. See config options for settings
|
|
--
|
|
-- When someone dies, they are given the option to join the default team
|
|
-- if they were not already.
|
|
|
|
|
|
-- TODO
|
|
-- Anti griefer? Drop all items on leaving?
|
|
-- Better long term goal like frontier
|
|
-- Better interaction with other forces?
|
|
|
|
-- config options
|
|
|
|
-- Near spawn option is on the edge of generated chunks
|
|
-- On a large map, this may be quite far from spawn.
|
|
local MIN_CHUNK_SPAWN_DIST = 2
|
|
|
|
-- Far spawn options is this number of chunks past generated area
|
|
local FAR_CHUNK_SPAWN_DIST = 10
|
|
|
|
local FAR_MIN_DIST = 1000^2
|
|
local FAR_MAX_DIST = 6000^2
|
|
|
|
-- Start resource amountsmm
|
|
local START_IRON_AMOUNT = 1500
|
|
local START_COPPER_AMOUNT = 1000
|
|
local START_STONE_AMOUNT = 1500
|
|
local START_COAL_AMOUNT = 1500
|
|
local START_OIL_AMOUNT = 20000
|
|
|
|
-- Safe area has no aliens
|
|
-- +/- this in x and y direction
|
|
local SAFE_AREA_TILE_DIST = 250
|
|
|
|
-- Warning area has reduced aliens
|
|
-- +/- this in x and y direction
|
|
local WARNING_AREA_TILE_DIST = 500
|
|
|
|
-- 1 : X (spawners alive : spawners destroyed) in this area
|
|
local WARN_AREA_REDUCTION_RATIO = 15
|
|
|
|
-- Create a circle of land area for the spawn
|
|
local ENFORCE_LAND_AREA_TILE_DIST = 40
|
|
local ENFORCE_LAND_AREA_TILE_DIST_SQUARED = ENFORCE_LAND_AREA_TILE_DIST^2
|
|
|
|
-- Main force is what default players join
|
|
local MAIN_FORCE = "main_force"
|
|
local ENABLE_OTHER_TEAMS = false
|
|
|
|
-- Disable enemy expansion
|
|
local ENEMY_EXPANSION = false
|
|
|
|
-- Divide the alien factors by this number to reduce it (or multiply if < 1)
|
|
local ENEMY_POLLUTION_FACTOR_DIVISOR = 10
|
|
local ENEMY_DESTROY_FACTOR_DIVISOR = 1
|
|
|
|
-- Useful constants
|
|
local CHUNK_SIZE = 32
|
|
local MAX_FORCES = 64
|
|
|
|
|
|
-- Print debug only to me while testing.
|
|
-- Should remove this if you are hosting it yourself.
|
|
local function DebugPrint(msg)
|
|
if ((game.players["Oarc"] ~= nil) and (global.oarcDebugEnabled)) then
|
|
game.players["Oarc"].print("DEBUG: " .. msg)
|
|
end
|
|
end
|
|
|
|
|
|
-- Simple function to get total number of items in table
|
|
function TableLength(T)
|
|
local count = 0
|
|
for _ in pairs(T) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
-- Give player these default items.
|
|
local function GivePlayerItems(player)
|
|
player.insert{name="pistol", count=1}
|
|
player.insert{name="firearm-magazine", count=10}
|
|
end
|
|
|
|
-- Additional starter only items
|
|
local function GivePlayerStarterItems(player)
|
|
GivePlayerItems(player)
|
|
player.insert{name="iron-plate", count=8}
|
|
player.insert{name="burner-mining-drill", count = 1}
|
|
player.insert{name="stone-furnace", count = 1}
|
|
end
|
|
|
|
-- Check if given position is in area bounding box
|
|
local function CheckIfInArea(point, area)
|
|
if ((point.x > area.left_top.x) and (point.x < area.right_bottom.x)) then
|
|
if ((point.y > area.left_top.y) and (point.y < area.right_bottom.y)) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Ceasefire
|
|
-- All forces are always neutral
|
|
local function SetCeaseFireBetweenAllForces()
|
|
for name,team in pairs(game.forces) do
|
|
if name ~= "neutral" and name ~= "enemy" then
|
|
for x,y in pairs(game.forces) do
|
|
if x ~= "neutral" and x ~= "enemy" then
|
|
team.set_cease_fire(x,true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Broadcast messages
|
|
local function SendBroadcastMsg(msg)
|
|
for name,player in pairs(game.players) do
|
|
player.print(msg)
|
|
end
|
|
end
|
|
|
|
|
|
local function CreateNewSpawnCoordinates(spawn_distance, recursion_max)
|
|
local position = {x=0,y=0}
|
|
local chunkPos = {x=0,y=0}
|
|
local randVec = {x=0,y=0}
|
|
|
|
-- Create a random direction vector to look in
|
|
while ((randVec.x == 0) and (randVec.y == 0))
|
|
do
|
|
randVec.x = math.random(-3,3)
|
|
randVec.y = math.random(-3,3)
|
|
end
|
|
DebugPrint("direction: x=" .. randVec.x .. ", y=" .. randVec.y)
|
|
|
|
while(true)
|
|
do
|
|
|
|
-- 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 (game.surfaces["nauvis"].is_chunk_generated(chunkPos)) then
|
|
chunkPos.x = chunkPos.x + randVec.x*spawn_distance
|
|
chunkPos.y = chunkPos.y + randVec.y*spawn_distance
|
|
|
|
-- Found a possible ungenerated area
|
|
else
|
|
|
|
chunkPos.x = chunkPos.x + (randVec.x*spawn_distance)
|
|
chunkPos.y = chunkPos.y + (randVec.y*spawn_distance)
|
|
|
|
-- If it's still ungenerated even further out, use that position.
|
|
if (not game.surfaces["nauvis"].is_chunk_generated(chunkPos)) then
|
|
position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2)
|
|
position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Very dangerous and stupid recursive call.
|
|
if (spawn_distance == FAR_CHUNK_SPAWN_DIST) then
|
|
local distSqrd = position.x^2 + position.y^2
|
|
if ((distSqrd < FAR_MIN_DIST) or (distSqrd > FAR_MAX_DIST)) then
|
|
if (recursion_max == 0) then
|
|
return {x=0,y=0}
|
|
else
|
|
DebugPrint("spawn: x=" .. position.x .. ", y=" .. position.y)
|
|
return CreateNewSpawnCoordinates(spawn_distance, recursion_max-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
DebugPrint("spawn: x=" .. position.x .. ", y=" .. position.y)
|
|
return position
|
|
end
|
|
|
|
local my_label_style = {
|
|
minimal_width = 450,
|
|
maximal_width = 450,
|
|
font_color = {r=1,g=1,b=1}
|
|
}
|
|
|
|
local my_note_style = {
|
|
minimal_width = 450,
|
|
maximal_height = 10,
|
|
font = "default-small-semibold",
|
|
font_color = {r=1,g=0.5,b=0.5}
|
|
}
|
|
|
|
local my_warning_style = {
|
|
minimal_width = 450,
|
|
maximal_width = 450,
|
|
font_color = {r=1,g=0.1,b=0.1}
|
|
}
|
|
|
|
function ApplyStyle (guiIn, styleIn)
|
|
for k,v in pairs(styleIn) do
|
|
guiIn.style[k]=v
|
|
end
|
|
end
|
|
|
|
function DisplaySpawnOptions(player)
|
|
player.gui.center.add{name = "spawn_opts",
|
|
type = "frame",
|
|
direction = "vertical",
|
|
caption="Spawn Options"}
|
|
local spawnGui = player.gui.center.spawn_opts
|
|
|
|
spawnGui.style.maximal_width = 450
|
|
spawnGui.style.maximal_height = 650
|
|
|
|
spawnGui.add{name = "warning_lbl1", type = "label",
|
|
caption="CHOOSE CAREFULLY - YOU ONLY GET ONE CUSTOM SPAWN POINT!"}
|
|
spawnGui.add{name = "warning_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(spawnGui.warning_lbl1, my_warning_style)
|
|
ApplyStyle(spawnGui.warning_spacer, my_label_style)
|
|
|
|
spawnGui.add{name = "normal_spawn",
|
|
type = "button",
|
|
caption="Default Spawn"}
|
|
spawnGui.add{name = "normal_spawn_lbl1", type = "label",
|
|
caption="This is the default spawn behavior of a vanilla game."}
|
|
spawnGui.add{name = "normal_spawn_lbl2", type = "label",
|
|
caption="You join the default team in the center of the map."}
|
|
spawnGui.add{name = "normal_spawn_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(spawnGui.normal_spawn_lbl1, my_label_style)
|
|
ApplyStyle(spawnGui.normal_spawn_lbl2, my_label_style)
|
|
ApplyStyle(spawnGui.normal_spawn_spacer, my_label_style)
|
|
|
|
spawnGui.add{name = "isolated_spawn",
|
|
type = "button",
|
|
caption="Isolated Spawn"}
|
|
spawnGui.add{name = "isolated_spawn_far",
|
|
type = "button",
|
|
caption="Isolated Spawn (Far Away)"}
|
|
spawnGui.add{name = "isolated_spawn_lbl1", type = "label",
|
|
caption="You are spawned in a new area, with starting resources."}
|
|
spawnGui.add{name = "isolated_spawn_lbl2", type = "label",
|
|
caption="You will still be part of the default team."}
|
|
spawnGui.add{name = "isolated_spawn_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(spawnGui.isolated_spawn_lbl1, my_label_style)
|
|
ApplyStyle(spawnGui.isolated_spawn_lbl2, my_label_style)
|
|
ApplyStyle(spawnGui.isolated_spawn_spacer, my_label_style)
|
|
|
|
|
|
if (ENABLE_OTHER_TEAMS) then
|
|
spawnGui.add{name = "new_force",
|
|
type = "button",
|
|
caption="Separate Team"}
|
|
spawnGui.add{name = "new_force_far",
|
|
type = "button",
|
|
caption="Separate Team (Far Away)"}
|
|
spawnGui.add{name = "new_force_lbl1", type = "label",
|
|
caption="You are spawned in a new area, with starting resources."}
|
|
spawnGui.add{name = "new_force_lbl2", type = "label",
|
|
caption="You will be on your own team. (No shared vision or research with others.)"}
|
|
spawnGui.add{name = "new_force_lbl3", type = "label",
|
|
caption="Do not choose this option if you are new to the game!"}
|
|
spawnGui.add{name = "new_force_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(spawnGui.new_force_lbl1, my_label_style)
|
|
ApplyStyle(spawnGui.new_force_lbl2, my_warning_style)
|
|
ApplyStyle(spawnGui.new_force_lbl3, my_warning_style)
|
|
ApplyStyle(spawnGui.new_force_spacer, my_label_style)
|
|
|
|
|
|
|
|
spawnGui.add{name = "note_lbl1", type = "label",
|
|
caption="All members of a team share map vision and research."}
|
|
spawnGui.add{name = "note_lbl2", type = "label",
|
|
caption="To talk to someone on a different team, you need to use /s to shout."}
|
|
spawnGui.add{name = "note_lbl3", type = "label",
|
|
caption="All teams are neutral. This is still a cooperative PvE game... NOT PVP!"}
|
|
ApplyStyle(spawnGui.note_lbl1, my_note_style)
|
|
ApplyStyle(spawnGui.note_lbl2, my_note_style)
|
|
ApplyStyle(spawnGui.note_lbl3, my_note_style)
|
|
end
|
|
spawnGui.add{name = "note_lbl4", type = "label",
|
|
caption="Far away spawn is between 1000-6000 distance units away from the center of the map."}
|
|
spawnGui.add{name = "note_lbl5", type = "label",
|
|
caption="Isolated spawns are dangerous! You will have to fight to reach other players."}
|
|
spawnGui.add{name = "note_lbl6", type = "label",
|
|
caption="You can change your spawn options when you die."}
|
|
|
|
ApplyStyle(spawnGui.note_lbl4, my_note_style)
|
|
ApplyStyle(spawnGui.note_lbl5, my_note_style)
|
|
end
|
|
|
|
function DisplayRespawnContinueOption(player)
|
|
|
|
player.gui.center.add{name = "respawn_continue_opts",
|
|
type = "frame",
|
|
direction = "vertical",
|
|
caption="Respawn Options"}
|
|
local respawnGui = player.gui.center.respawn_continue_opts
|
|
|
|
respawnGui.style.maximal_width = 450
|
|
respawnGui.style.maximal_height = 550
|
|
|
|
respawnGui.add{name = "respawn_continue",
|
|
type = "button",
|
|
caption="Continue"}
|
|
respawnGui.add{name = "respawn_continue_lbl1", type = "label",
|
|
caption="Continue at your current spawn location."}
|
|
respawnGui.add{name = "respawn_continue_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(respawnGui.respawn_continue_lbl1, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_continue_spacer, my_label_style)
|
|
|
|
respawnGui.add{name = "respawn_change",
|
|
type = "button",
|
|
caption="Change Spawn"}
|
|
respawnGui.add{name = "respawn_change_lbl1", type = "label",
|
|
caption="Allow you to change your spawn and team."}
|
|
respawnGui.add{name = "respawn_change_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(respawnGui.respawn_change_lbl1, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_change_spacer, my_label_style)
|
|
end
|
|
|
|
|
|
function DisplayRespawnOptions(player)
|
|
|
|
player.gui.center.add{name = "respawn_opts",
|
|
type = "frame",
|
|
direction = "vertical",
|
|
caption="Respawn Options"}
|
|
local respawnGui = player.gui.center.respawn_opts
|
|
|
|
respawnGui.style.maximal_width = 450
|
|
respawnGui.style.maximal_height = 750
|
|
|
|
-- Basically a cancel button to avoid choosing a different spawn.
|
|
respawnGui.add{name = "respawn_continue",
|
|
type = "button",
|
|
caption="Cancel"}
|
|
respawnGui.add{name = "respawn_continue_lbl1", type = "label",
|
|
caption="Continue with current spawn."}
|
|
respawnGui.add{name = "respawn_continue_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(respawnGui.respawn_continue_lbl1, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_continue_spacer, my_label_style)
|
|
|
|
respawnGui.add{name = "respawn_mainforce",
|
|
type = "button",
|
|
caption="Use Default Spawn"}
|
|
respawnGui.add{name = "respawn_mainforce_lbl1", type = "label",
|
|
caption="This will join the default team."}
|
|
respawnGui.add{name = "respawn_mainforce_lbl2", type = "label",
|
|
caption="If you are on another team all your research will be lost!"}
|
|
respawnGui.add{name = "respawn_mainforce_lbl3", type = "label",
|
|
caption="You will spawn at the default spawn point in the center."}
|
|
respawnGui.add{name = "respawn_mainforce_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(respawnGui.respawn_mainforce_lbl1, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_mainforce_lbl2, my_warning_style)
|
|
ApplyStyle(respawnGui.respawn_mainforce_lbl3, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_mainforce_spacer, my_label_style)
|
|
|
|
|
|
respawnGui.add{name = "respawn_custom_spawn",
|
|
type = "button",
|
|
caption="Custom Spawn"}
|
|
respawnGui.add{name = "respawn_custom_lbl1", type = "label",
|
|
caption="This will join the default team."}
|
|
respawnGui.add{name = "respawn_custom_lbl2", type = "label",
|
|
caption="If you are on another team all your research will be lost!"}
|
|
respawnGui.add{name = "respawn_custom_lbl3", type = "label",
|
|
caption="You will spawn at your previous custom spawn point."}
|
|
respawnGui.add{name = "respawn_custom_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(respawnGui.respawn_custom_lbl1, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_custom_lbl2, my_warning_style)
|
|
ApplyStyle(respawnGui.respawn_custom_lbl3, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_custom_spacer, my_label_style)
|
|
|
|
if (ENABLE_OTHER_TEAMS) then
|
|
respawnGui.add{name = "respawn_custom_team",
|
|
type = "button",
|
|
caption="Custom Team Spawn"}
|
|
respawnGui.add{name = "respawn_custom_team_lbl1", type = "label",
|
|
caption="This will join your own custom team."}
|
|
respawnGui.add{name = "respawn_custom_team_lbl2", type = "label",
|
|
caption="You will have your own map vision and research tree. Use /s to talk to others."}
|
|
respawnGui.add{name = "respawn_custom_team_lbl3", type = "label",
|
|
caption="You will spawn at your previous custom spawn point."}
|
|
respawnGui.add{name = "respawn_custom_team_spacer", type = "label",
|
|
caption=" "}
|
|
ApplyStyle(respawnGui.respawn_custom_team_lbl1, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_custom_team_lbl2, my_warning_style)
|
|
ApplyStyle(respawnGui.respawn_custom_team_lbl3, my_label_style)
|
|
ApplyStyle(respawnGui.respawn_custom_team_spacer, my_label_style)
|
|
end
|
|
|
|
if (global.enableRespawnSurprise == true) then
|
|
respawnGui.add{name = "respawn_surpise",
|
|
type = "button",
|
|
caption="Surprise me!"}
|
|
end
|
|
|
|
respawnGui.add{name = "respawn_note1", type = "label",
|
|
caption="You cannot generate new custom spawn points."}
|
|
ApplyStyle(respawnGui.respawn_note1, my_note_style)
|
|
end
|
|
|
|
function GenerateStartingResources(player)
|
|
local surface = player.surface
|
|
|
|
-- Generate stone
|
|
local stonePos = {x=player.position.x-25,
|
|
y=player.position.y-31}
|
|
|
|
-- Generate coal
|
|
local coalPos = {x=player.position.x-25,
|
|
y=player.position.y-16}
|
|
|
|
-- Generate copper ore
|
|
local copperOrePos = {x=player.position.x-25,
|
|
y=player.position.y+0}
|
|
|
|
-- Generate iron ore
|
|
local ironOrePos = {x=player.position.x-25,
|
|
y=player.position.y+15}
|
|
|
|
-- 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-30, player.position.y-2}})
|
|
surface.create_entity({name="crude-oil", amount=START_OIL_AMOUNT,
|
|
position={player.position.x-30, player.position.y+2}})
|
|
|
|
for y=0, 15 do
|
|
for x=0, 15 do
|
|
if ((x-7)^2 + (y - 7)^2 < 7^2) then
|
|
surface.create_entity({name="iron-ore", amount=START_IRON_AMOUNT,
|
|
position={ironOrePos.x+x, ironOrePos.y+y}})
|
|
surface.create_entity({name="copper-ore", amount=START_COPPER_AMOUNT,
|
|
position={copperOrePos.x+x, copperOrePos.y+y}})
|
|
surface.create_entity({name="stone", amount=START_STONE_AMOUNT,
|
|
position={stonePos.x+x, stonePos.y+y}})
|
|
surface.create_entity({name="coal", amount=START_COAL_AMOUNT,
|
|
position={coalPos.x+x, coalPos.y+y}})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function RemoveDecorationsArea(surface, area )
|
|
for _, entity in pairs(surface.find_entities_filtered{area = area, type="decorative"}) do
|
|
entity.destroy()
|
|
end
|
|
end
|
|
|
|
local function ClearNearbyEnemies(player)
|
|
local safeArea = {left_top=
|
|
{x=player.position.x-SAFE_AREA_TILE_DIST,
|
|
y=player.position.y-SAFE_AREA_TILE_DIST},
|
|
right_bottom=
|
|
{x=player.position.x+SAFE_AREA_TILE_DIST,
|
|
y=player.position.y+SAFE_AREA_TILE_DIST}}
|
|
|
|
for _, entity in pairs(player.surface.find_entities_filtered{area = safeArea, force = "enemy"}) do
|
|
entity.destroy()
|
|
end
|
|
end
|
|
|
|
|
|
local function DoesPlayerHaveCustomSpawn(player)
|
|
for name,spawnPos in pairs(global.playerSpawns) do
|
|
if (player.name == name) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function DoesPlayerHaveActiveCustomSpawn(player)
|
|
if (DoesPlayerHaveCustomSpawn(player)) then
|
|
return global.activePlayerSpawns[player.name]
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function ActivatePlayerCustomSpawn(player, value)
|
|
for name,_ in pairs(global.playerSpawns) do
|
|
if (player.name == name) then
|
|
global.activePlayerSpawns[player.name] = value
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local 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
|
|
|
|
local function SendPlayerToNewSpawnAndCreateIt(player, spawn)
|
|
-- Send the player to that position
|
|
player.teleport(spawn)
|
|
GivePlayerStarterItems(player)
|
|
|
|
-- If we get a valid spawn point, setup the area
|
|
if (spawn ~= {x=0,y=0}) then
|
|
GenerateStartingResources(player)
|
|
ClearNearbyEnemies(player)
|
|
end
|
|
end
|
|
|
|
local function SendPlayerToActiveSpawn(player)
|
|
if (DoesPlayerHaveActiveCustomSpawn(player)) then
|
|
player.teleport(global.playerSpawns[player.name])
|
|
else
|
|
player.teleport(game.forces[MAIN_FORCE].get_spawn_position("nauvis"))
|
|
end
|
|
end
|
|
|
|
local function SendPlayerToRandomSpawn(player)
|
|
local numSpawns = TableLength(global.playerSpawns)
|
|
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.playerSpawns) do
|
|
if (counter == rndSpawn) then
|
|
player.teleport(spawnPos)
|
|
break
|
|
end
|
|
counter = counter + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- 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.
|
|
script.on_event(defines.events.on_player_created, function(event)
|
|
local player = game.players[event.player_index]
|
|
player.force = MAIN_FORCE
|
|
DisplaySpawnOptions(player)
|
|
player.print("Welcome to Oarc's server! Now with oil spots, better respawn menus and gravestone chests!")
|
|
end)
|
|
|
|
|
|
-- Create the appropriate force & spawn when player selects their choice
|
|
script.on_event(defines.events.on_gui_click, function (event)
|
|
|
|
local player = game.players[event.player_index]
|
|
local buttonClicked = event.element.name
|
|
|
|
-- Only clear gui if a valid button was clicked!!
|
|
if ((buttonClicked == "normal_spawn") or
|
|
(buttonClicked == "isolated_spawn") or
|
|
(buttonClicked == "isolated_spawn_far") or
|
|
(buttonClicked == "new_force") or
|
|
(buttonClicked == "new_force_far") or
|
|
(buttonClicked == "respawn_continue") or
|
|
(buttonClicked == "respawn_change") or
|
|
(buttonClicked == "respawn_custom_team") or
|
|
(buttonClicked == "respawn_custom_spawn") or
|
|
(buttonClicked == "respawn_surpise") or
|
|
(buttonClicked == "respawn_mainforce")) then
|
|
|
|
|
|
|
|
if (player.gui.center.spawn_opts ~= nil) then
|
|
player.gui.center.spawn_opts.destroy()
|
|
end
|
|
if (player.gui.center.respawn_opts ~= nil) then
|
|
player.gui.center.respawn_opts.destroy()
|
|
end
|
|
if (player.gui.center.respawn_continue_opts ~= nil) then
|
|
player.gui.center.respawn_continue_opts.destroy()
|
|
end
|
|
end
|
|
|
|
-- In this option, the vanilla spawn is used and the player is
|
|
-- part of the main force.
|
|
if (buttonClicked == "normal_spawn") then
|
|
player.force = MAIN_FORCE
|
|
GivePlayerStarterItems(player)
|
|
SendBroadcastMsg(player.name .. " joined the main force!")
|
|
|
|
-- In this option, the player gets a separate spawn point
|
|
-- but is still part of the main force.
|
|
elseif ((buttonClicked == "isolated_spawn") or (buttonClicked == "isolated_spawn_far")) then
|
|
player.force = MAIN_FORCE
|
|
|
|
-- Create a new spawn point
|
|
local newSpawn = {}
|
|
if (buttonClicked == "isolated_spawn_far") then
|
|
newSpawn = CreateNewSpawnCoordinates(FAR_CHUNK_SPAWN_DIST, 20)
|
|
else
|
|
newSpawn = CreateNewSpawnCoordinates(MIN_CHUNK_SPAWN_DIST, 20)
|
|
end
|
|
global.playerSpawns[player.name] = newSpawn
|
|
global.activePlayerSpawns[player.name] = true
|
|
|
|
SendPlayerToNewSpawnAndCreateIt(player, newSpawn)
|
|
if (buttonClicked == "isolated_spawn") then
|
|
SendBroadcastMsg(player.name .. " joined the main force from a distance!")
|
|
elseif (buttonClicked == "isolated_spawn_far") then
|
|
SendBroadcastMsg(player.name .. " joined the main force from a great distance!")
|
|
end
|
|
|
|
-- In this option, the player is given a new force and a
|
|
-- separate spawn point
|
|
elseif ((buttonClicked == "new_force") or (buttonClicked == "new_force_far")) then
|
|
|
|
-- Create a new force using the player's name
|
|
local newForce = CreatePlayerCustomForce(player)
|
|
|
|
-- Create a new spawn point
|
|
local newSpawn = {}
|
|
if (buttonClicked == "new_force_far") then
|
|
newSpawn = CreateNewSpawnCoordinates(FAR_CHUNK_SPAWN_DIST, 20)
|
|
else
|
|
newSpawn = CreateNewSpawnCoordinates(MIN_CHUNK_SPAWN_DIST, 20)
|
|
end
|
|
global.playerSpawns[player.name] = newSpawn
|
|
global.activePlayerSpawns[player.name] = true
|
|
|
|
-- Set the new spawn point
|
|
if (newForce ~= nil) then
|
|
newForce.set_spawn_position(newSpawn, "nauvis")
|
|
end
|
|
|
|
SendPlayerToNewSpawnAndCreateIt(player, newSpawn)
|
|
SendBroadcastMsg(player.name .. " is going it alone!")
|
|
|
|
-- Continue to respawn on your own team at that location
|
|
elseif (buttonClicked == "respawn_continue") then
|
|
GivePlayerItems(player)
|
|
|
|
|
|
-- If changing your spawn behavior
|
|
elseif (buttonClicked == "respawn_change") then
|
|
if (DoesPlayerHaveCustomSpawn(player)) then
|
|
DisplayRespawnOptions(player)
|
|
else
|
|
DisplaySpawnOptions(player)
|
|
end
|
|
|
|
-- Respawn with the main force in the default location
|
|
elseif (buttonClicked == "respawn_mainforce") then
|
|
|
|
-- Remove custom force if it exists
|
|
if (player.force.name ~= MAIN_FORCE) then
|
|
game.merge_forces(player.name, MAIN_FORCE)
|
|
end
|
|
|
|
-- Deactivate the stored spawn point
|
|
ActivatePlayerCustomSpawn(player, false)
|
|
player.teleport(player.force.get_spawn_position("nauvis"))
|
|
GivePlayerStarterItems(player)
|
|
SendBroadcastMsg(player.name .. " is returning to base!")
|
|
|
|
-- Respawn in your already generated custom spawn area on the main team
|
|
elseif (buttonClicked == "respawn_custom_spawn") then
|
|
|
|
-- Remove custom force if it exists
|
|
if (player.force.name ~= MAIN_FORCE) then
|
|
game.merge_forces(player.name, MAIN_FORCE)
|
|
end
|
|
|
|
-- Activate the stored spawn point
|
|
ActivatePlayerCustomSpawn(player, true)
|
|
SendPlayerToActiveSpawn(player)
|
|
GivePlayerStarterItems(player)
|
|
SendBroadcastMsg(player.name .. " is returning to their outpost!")
|
|
|
|
-- Respawn in your already generated custom spawn area but on your own
|
|
-- force. This force is created new if it doesn't exist.
|
|
elseif (buttonClicked == "respawn_custom_team") then
|
|
|
|
-- Create a new force using the player's name
|
|
local newForce = CreatePlayerCustomForce(player)
|
|
|
|
-- Set the new spawn point
|
|
if (newForce ~= nil) then
|
|
newForce.set_spawn_position(global.playerSpawns[player.name], "nauvis")
|
|
end
|
|
|
|
-- Activate the stored spawn point
|
|
ActivatePlayerCustomSpawn(player, true)
|
|
SendPlayerToActiveSpawn(player)
|
|
GivePlayerStarterItems(player)
|
|
SendBroadcastMsg(player.name .. " is returning to their outpost alone!")
|
|
|
|
-- lol wut
|
|
elseif (buttonClicked == "respawn_surpise") then
|
|
|
|
-- Remove custom force if it exists
|
|
if (player.force.name ~= MAIN_FORCE) then
|
|
game.merge_forces(player.name, MAIN_FORCE)
|
|
end
|
|
|
|
-- Activate the stored spawn point
|
|
SendPlayerToRandomSpawn(player)
|
|
GivePlayerStarterItems(player)
|
|
SendBroadcastMsg(player.name .. " is surprised!")
|
|
|
|
end
|
|
end)
|
|
|
|
-- local testFlag = false
|
|
|
|
-- Check if the player has a different spawn point than the default one
|
|
-- Make sure to give the default starting items
|
|
script.on_event(defines.events.on_player_respawned, function(event)
|
|
local player = game.players[event.player_index]
|
|
|
|
-- local testSpawn
|
|
-- if testFlag then
|
|
-- testSpawn = CreateNewSpawnCoordinates(FAR_CHUNK_SPAWN_DIST, 20)
|
|
-- else
|
|
-- testSpawn = CreateNewSpawnCoordinates(MIN_CHUNK_SPAWN_DIST, 20)
|
|
-- end
|
|
-- testFlag = not testFlag
|
|
-- player.teleport(testSpawn)
|
|
-- DebugPrint("Test Spawn: " .. testSpawn.x .. "," .. testSpawn.y)
|
|
|
|
|
|
-- If a player has an active spawn, use it.
|
|
if (DoesPlayerHaveActiveCustomSpawn(player)) then
|
|
player.teleport(global.playerSpawns[player.name])
|
|
end
|
|
|
|
-- Display the respawn continue option
|
|
DisplayRespawnContinueOption(player)
|
|
end)
|
|
|
|
|
|
-- New spawn area tile generation and enemy clearing must go here
|
|
script.on_event(defines.events.on_chunk_generated, function(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
|
|
|
|
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
|
|
|
|
-- This ( X^2 + Y^2 ) is used to calculate if something
|
|
-- is inside a circle area.
|
|
local distVar = math.floor((spawnPos.x - i)^2 + (spawnPos.y - j)^2)
|
|
|
|
-- Fill in all unexpected water in a circle
|
|
if (distVar < ENFORCE_LAND_AREA_TILE_DIST_SQUARED) then
|
|
if (surface.get_tile(i,j).collides_with("water-tile")) then
|
|
table.insert(dirtTiles, {name = "grass", position ={i,j}})
|
|
end
|
|
end
|
|
|
|
-- Create a circle of trees around the spawn point.
|
|
if ((distVar < ENFORCE_LAND_AREA_TILE_DIST_SQUARED-200) and
|
|
(distVar > ENFORCE_LAND_AREA_TILE_DIST_SQUARED-260)) then
|
|
surface.create_entity({name="tree-01", amount=1, position={i, j}})
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
surface.set_tiles(dirtTiles)
|
|
end
|
|
|
|
-- Provide a guaranteed spot of water to use for power generation
|
|
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
|
|
|
|
-- Remove decor to save on file size
|
|
RemoveDecorationsArea(surface, chunkArea)
|
|
end)
|
|
|
|
script.on_init(function(event)
|
|
|
|
-- Containes an array of all player spawns
|
|
-- A secondary array tracks whether the character will respawn there.
|
|
if (global.playerSpawns == nil) then
|
|
global.playerSpawns = {}
|
|
global.activePlayerSpawns = {}
|
|
end
|
|
|
|
-- Adjust alien params
|
|
game.map_settings.enemy_evolution.time_factor=0
|
|
game.map_settings.enemy_evolution.destroy_factor = game.map_settings.enemy_evolution.destroy_factor / ENEMY_DESTROY_FACTOR_DIVISOR
|
|
game.map_settings.enemy_evolution.pollution_factor = game.map_settings.enemy_evolution.pollution_factor / ENEMY_POLLUTION_FACTOR_DIVISOR
|
|
game.map_settings.enemy_expansion.enabled = ENEMY_EXPANSION
|
|
|
|
game.create_force(MAIN_FORCE)
|
|
game.forces[MAIN_FORCE].set_spawn_position(game.forces["player"].get_spawn_position("nauvis"), "nauvis")
|
|
SetCeaseFireBetweenAllForces()
|
|
end)
|
|
|
|
|
|
-- Freeplay rocket launch info
|
|
script.on_event(defines.events.on_rocket_launched, function(event)
|
|
local force = event.rocket.force
|
|
if event.rocket.get_item_count("satellite") == 0 then
|
|
for index, player in pairs(force.players) do
|
|
player.print("You launched the rocket, but you didn't put a satellite inside.")
|
|
end
|
|
return
|
|
end
|
|
|
|
if not global.satellite_sent then
|
|
global.satellite_sent = {}
|
|
end
|
|
|
|
if global.satellite_sent[force.name] then
|
|
global.satellite_sent[force.name] = global.satellite_sent[force.name] + 1
|
|
else
|
|
game.set_game_state{game_finished=true, player_won=true, can_continue=true}
|
|
global.satellite_sent[force.name] = 1
|
|
end
|
|
|
|
for index, player in pairs(force.players) do
|
|
if player.gui.left.rocket_score then
|
|
player.gui.left.rocket_score.rocket_count.caption = tostring(global.satellite_sent[force.name])
|
|
else
|
|
local frame = player.gui.left.add{name = "rocket_score", type = "frame", direction = "horizontal", caption="Score"}
|
|
frame.add{name="rocket_count_label", type = "label", caption={"", "Satellites launched", ":"}}
|
|
frame.add{name="rocket_count", type = "label", caption=tostring(global.satellite_sent[force.name])}
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Return steel chest entity (or nil)
|
|
local function DropEmptySteelChest(player)
|
|
local pos = player.surface.find_non_colliding_position("steel-chest", player.position, 15, 1)
|
|
if not pos then
|
|
return nil
|
|
end
|
|
local grave = player.surface.create_entity{name="steel-chest", position=pos, force="neutral"}
|
|
return grave
|
|
end
|
|
|
|
-- My modified version of gravestone. Makes sure you get all items even if
|
|
-- it requires multiple steel chests.
|
|
script.on_event(defines.events.on_player_died, function(event)
|
|
|
|
local player = game.players[event.player_index]
|
|
|
|
-- Create the 1st chest to fill
|
|
local grave = DropEmptySteelChest(player)
|
|
if (grave == nil) then
|
|
player.print("Not able to place a chest nearby! All items lost!")
|
|
return
|
|
end
|
|
|
|
-- Init the inventroy space counter
|
|
local grave_inv = grave.get_inventory(defines.inventory.chest)
|
|
local count = 0
|
|
|
|
-- Loop through a players different inventories
|
|
-- Put it all into the chest
|
|
-- If the chest is full, create a new chest.
|
|
for i, id in ipairs{
|
|
defines.inventory.player_armor,
|
|
defines.inventory.player_main,
|
|
defines.inventory.player_quickbar,
|
|
defines.inventory.player_guns,
|
|
defines.inventory.player_ammo,
|
|
defines.inventory.player_tools,
|
|
defines.inventory.player_trash} do
|
|
local inv = player.get_inventory(id)
|
|
for j = 1, #inv do
|
|
if inv[j].valid_for_read then
|
|
count = count + 1
|
|
|
|
grave_inv[count].set_stack(inv[j])
|
|
|
|
if (count == #grave_inv) then
|
|
count = 0
|
|
grave = DropEmptySteelChest(player)
|
|
if (grave == nil) then
|
|
player.print("Not able to place a chest nearby! Some items lost!")
|
|
return
|
|
end
|
|
grave_inv = grave.get_inventory(defines.inventory.chest)
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
player.print("Successfully dropped your items into a chest! Go get them quick!")
|
|
|
|
end)
|