1
0
mirror of https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git synced 2024-12-04 09:43:00 +02:00

Refined new spawn search logic. (Using random vector search.)

This commit is contained in:
Oarcinae 2024-09-15 13:50:07 -04:00
parent e6d64fb8d8
commit dc27eab5e7
6 changed files with 147 additions and 103 deletions

View File

@ -22,9 +22,8 @@ BACKLOG:
- Refresh players in admin controls when dropdown is clicked
- Think through player flow for Space Age for creating new unique spawns on other surfaces and respawning:
- Create a function to create secondary uniqueSpawns for the same player
- Move "buddy" info to unique_spawns as well.
- Default to selecting SELF in admin controls player dropdown?
- In spawn controls, add a note if spawn is full (and maybe disable the shared spawn checkbox?)
@ -32,7 +31,8 @@ BACKLOG:
- If dead when resetting spawn... possibly delay the opening of the welcome GUI or block spawning until character is spawned?
- Shared electricity (proper)
- Shared items (proper)
------------------------------------------------------------------------------------------------------------------------
@ -114,4 +114,6 @@ Other Ideas, Not Committed:
- "uniqueSpawns" should have a "primary" flag and be indexed by surface FIRST
- Make respawn locations first be indexed by player, then surface
- Offline protection re-implement!
- Resolve regrowth issue with radars and confirm that when we mark chunks for removal, they can be refreshed still. (trace logic!)
- Resolve regrowth issue with radars and confirm that when we mark chunks for removal, they can be refreshed still. (trace logic!)
- Move "buddy" info to unique_spawns as well.
- Fix search vector to use more variable vector'ing, always normalize vector, and then ensure the other reliant functions work still.

View File

@ -172,4 +172,15 @@ function RecreateOarcGui(player)
end
InitOarcGuiTabs(player)
end
function SetNauvisChunksGenerated()
local nauvis = game.surfaces["nauvis"]
for x = -100, 100, 1 do
for y = -100, 100, 1 do
nauvis.set_chunk_generated_status({x=x, y=y}, defines.chunk_generated_status.entities)
end
end
end

View File

@ -614,14 +614,18 @@ function RandomNegPos()
end
end
---Create a random direction vector to look in (values are integers between -3 and 3)
---Create a random direction vector to look in, returns normalized vector
---@return MapPosition
function GetRandomVector()
local randVec = {x=0,y=0}
while ((randVec.x == 0) and (randVec.y == 0)) do
randVec.x = math.random(-3,3)
randVec.y = math.random(-3,3)
randVec.x = math.random() * 2 - 1
randVec.y = math.random() * 2 - 1
end
-- Normalize the vector
local magnitude = math.sqrt((randVec.x^2) + (randVec.y^2))
randVec.x = randVec.x / magnitude
randVec.y = randVec.y / magnitude
log("direction: x=" .. randVec.x .. ", y=" .. randVec.y)
return randVec
end
@ -667,94 +671,125 @@ function ClearNearbyEnemies(pos, safeDist, surface)
end
end
---Function to find coordinates of ungenerated map area in a given direction starting from the center of the map
---@param directionVec MapPosition
---@param surface LuaSurface
---@return MapPosition
function FindMapEdge(directionVec, surface)
local position = {x=0,y=0}
local chunkPos = {x=0,y=0}
-- ---Function to find coordinates of ungenerated map area in a given direction starting from the center of the map
-- ---@param direction_vector MapPosition
-- ---@param surface LuaSurface
-- ---@return MapPosition
-- function FindMapEdge(direction_vector, surface)
-- local position = {x=0,y=0}
-- local chunk_position = {x=0,y=0}
-- Keep checking chunks in the direction of the vector
while(true) do
-- -- Keep checking chunks in the direction of the vector
-- while(true) do
-- Set some absolute limits.
if ((math.abs(chunkPos.x) > 1000) or (math.abs(chunkPos.y) > 1000)) then
break
-- -- Set some absolute limits.
-- if ((math.abs(chunk_position.x) > 1000) or (math.abs(chunk_position.y) > 1000)) then
-- break
-- If chunk is already generated, keep looking
elseif (surface.is_chunk_generated(chunkPos)) then
chunkPos.x = chunkPos.x + directionVec.x
chunkPos.y = chunkPos.y + directionVec.y
-- -- If chunk is already generated, keep looking
-- elseif (surface.is_chunk_generated(chunk_position)) then
-- chunk_position.x = chunk_position.x + direction_vector.x
-- chunk_position.y = chunk_position.y + direction_vector.y
-- Found a possible ungenerated area
else
-- -- Found a possible ungenerated area
-- else
chunkPos.x = chunkPos.x + directionVec.x
chunkPos.y = chunkPos.y + directionVec.y
-- chunk_position.x = chunk_position.x + direction_vector.x
-- chunk_position.y = chunk_position.y + direction_vector.y
-- Check there are no generated chunks in a 10x10 area.
if IsChunkAreaUngenerated(chunkPos, 10, surface) then
position.x = (chunkPos.x*CHUNK_SIZE) + (CHUNK_SIZE/2)
position.y = (chunkPos.y*CHUNK_SIZE) + (CHUNK_SIZE/2)
break
end
end
end
-- -- Check there are no generated chunks in a 10x10 area.
-- if IsChunkAreaUngenerated(chunk_position, 10, surface) then
-- position.x = (chunk_position.x*CHUNK_SIZE) + (CHUNK_SIZE/2)
-- position.y = (chunk_position.y*CHUNK_SIZE) + (CHUNK_SIZE/2)
-- break
-- end
-- end
-- end
-- log("spawn: x=" .. position.x .. ", y=" .. position.y)
return position
end
-- -- log("spawn: x=" .. position.x .. ", y=" .. position.y)
-- return position
-- end
---Pick a random direction, go at least the minimum distance, and start looking for ungenerated chunks
---We try a few times (hardcoded) and then try a different random direction if we fail (up to max_tries)
---@param surface LuaSurface
---@param minimum_distance number
---@param minimum_distance_chunks number Distance in chunks to start looking for ungenerated chunks
---@param max_tries integer Maximum number of tries to find a spawn point
---@return MapPosition
function FindUngeneratedCoordinates(surface, minimum_distance)
function FindUngeneratedCoordinates(surface, minimum_distance_chunks, max_tries)
--- Get a random vector, figure out how many times to multiply it to get the minimum distance
local direction_vector = GetRandomVector()
local direction_mag = math.sqrt((direction_vector.x^2) + (direction_vector.y^2))
local direction_multiple = math.ceil(minimum_distance / direction_mag)
local start_distance_tiles = minimum_distance_chunks * CHUNK_SIZE
local final_position = {x=0,y=0}
local tries_remaining = max_tries - 1
-- Starting search position
local search_pos = {
x=direction_vector.x * direction_multiple,
y=direction_vector.y * direction_multiple
x=direction_vector.x * start_distance_tiles,
y=direction_vector.y * start_distance_tiles
}
local MAX_CHUNK_DISTANCE = 1000
-- We check up to THIS many times, each jump moves out by minimum_distance_to_existing_chunks
local jumps_count = 3
local minimum_distance_to_existing_chunks = global.ocfg.gameplay.minimum_distance_to_existing_chunks
-- Keep checking chunks in the direction of the vector, assumes this terminates...
while(true) do
-- Set some absolute limits.
if ((math.abs(search_pos.x) > MAX_CHUNK_DISTANCE) or (math.abs(search_pos.y) > MAX_CHUNK_DISTANCE)) then
error("FindUngeneratedCoordinates - Hit max chunk distance!")
break
local chunk_position = GetChunkPosFromTilePos(search_pos)
-- If chunk is already generated, keep looking
elseif (surface.is_chunk_generated(search_pos)) then
search_pos.x = search_pos.x + direction_vector.x
search_pos.y = search_pos.y + direction_vector.y
if (jumps_count <= 0) then
-- Found a possible ungenerated area
else
search_pos.x = search_pos.x + direction_vector.x
search_pos.y = search_pos.y + direction_vector.y
-- Check there are no generated chunks in a 10x10 area.
if IsChunkAreaUngenerated(search_pos, 10, surface) then
final_position.x = (search_pos.x*CHUNK_SIZE) + (CHUNK_SIZE/2)
final_position.y = (search_pos.y*CHUNK_SIZE) + (CHUNK_SIZE/2)
if (tries_remaining > 0) then
return FindUngeneratedCoordinates(surface, minimum_distance_chunks, tries_remaining)
else
log("WARNING - FindUngeneratedCoordinates - Hit max distance!")
break
end
-- If chunk is already generated, keep looking further out
elseif (surface.is_chunk_generated(chunk_position)) then
-- For debugging, ping the map
-- SendBroadcastMsg("GENERATED: " .. GetGPStext(surface.name, {x=chunk_position.x*32, y=chunk_position.y*32}))
-- Move out a bit more to give some space and then check the surrounding area
search_pos.x = search_pos.x + (direction_vector.x * CHUNK_SIZE * minimum_distance_to_existing_chunks)
search_pos.y = search_pos.y + (direction_vector.y * CHUNK_SIZE * minimum_distance_to_existing_chunks)
-- Found a possible ungenerated area
elseif IsChunkAreaUngenerated(chunk_position, minimum_distance_to_existing_chunks, surface) then
-- For debugging, ping the map
-- SendBroadcastMsg("SUCCESS: " .. GetGPStext(surface.name, {x=chunk_position.x*32, y=chunk_position.y*32}))
-- Place the spawn in the center of a chunk
final_position.x = (chunk_position.x * CHUNK_SIZE) + (CHUNK_SIZE/2)
final_position.y = (chunk_position.y * CHUNK_SIZE) + (CHUNK_SIZE/2)
break
-- The area around the chunk is not clear, keep looking
else
-- For debugging, ping the map
-- SendBroadcastMsg("NOT CLEAR: " .. GetGPStext(surface.name, {x=chunk_position.x*32, y=chunk_position.y*32}))
-- Move out a bit more to give some space and then check the surrounding area
search_pos.x = search_pos.x + (direction_vector.x * CHUNK_SIZE * minimum_distance_to_existing_chunks)
search_pos.y = search_pos.y + (direction_vector.y * CHUNK_SIZE * minimum_distance_to_existing_chunks)
end
jumps_count = jumps_count - 1
end
if (final_position.x == 0 and final_position.y == 0) then
log("WARNING! FindUngeneratedCoordinates - Failed to find a spawn point!")
end
-- log("spawn: x=" .. position.x .. ", y=" .. position.y)
return final_position
end

View File

@ -1255,9 +1255,9 @@ function CreatePlayerCustomForce(player)
player.force = newForce
if (newForce.name == player.name) then
SendBroadcastMsg(player.name .. " has started their own team!") -- TODO: Localize
SendBroadcastMsg({ "oarc-player-started-own-team", player.name })
else
player.print("Sorry, no new teams can be created. You were assigned to the default team instead.") -- TODO: Localize
player.print({ "oarc-player-no-new-teams-sorry" })
end
return newForce

View File

@ -858,28 +858,26 @@ function PrimarySpawnRequest(player)
-- Cache some useful variables
local surface = game.surfaces[spawn_choices.surface]
-- Find coordinates of a good place to spawn
local spawn_position = FindUngeneratedCoordinates(surface, spawn_choices.distance, 3)
-- If that fails, just throw a warning and don't spawn them. They can try again.
if ((spawn_position.x == 0) and (spawn_position.y == 0)) then
player.print({ "oarc-no-ungenerated-land-error" })
return
end
-- Create a new force for player if they choose that radio button
if spawn_choices.team ~= SPAWN_TEAM_CHOICE.join_main_team then
CreatePlayerCustomForce(player)
end
-- Find coordinates of a good place to spawn
local newSpawn = { x = 0, y = 0 }
-- TODO: Rewrite this function to make use of spawnChoices.distance!!
newSpawn = FindUngeneratedCoordinates(surface, spawn_choices.distance)
-- If that fails, find a random map edge in a rand direction.
if ((newSpawn.x == 0) and (newSpawn.y == 0)) then
newSpawn = FindMapEdge(GetRandomVector(), surface)
log("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y)
end
-- Create that player's spawn in the global vars
ChangePlayerRespawn(player.name, spawn_choices.surface, newSpawn)
ChangePlayerRespawn(player.name, spawn_choices.surface, spawn_position)
-- Send the player there
QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, newSpawn, spawn_choices.moat, true, nil)
SendBroadcastMsg({ "oarc-player-is-joining-far", player.name, spawn_choices.surface })
QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, spawn_position, spawn_choices.moat, true, nil)
SendBroadcastMsg({ "oarc-player-is-joining", player.name, spawn_choices.surface })
-- Unlock spawn control gui tab
SetOarcGuiTabEnabled(player, OARC_SPAWN_CTRL_TAB_NAME, true)
@ -1101,16 +1099,16 @@ function AcceptBuddyRequest(player, requesting_buddy_name)
---@type OarcSpawnChoices
local spawn_choices = global.spawn_choices[requesting_buddy_name]
local requesting_buddy = game.players[requesting_buddy_name]
local surface = game.surfaces[spawn_choices.surface]
if (requesting_buddy.gui.screen.buddy_wait_menu ~= nil) then
requesting_buddy.gui.screen.buddy_wait_menu.destroy()
end
if (player.gui.screen.buddy_request_menu ~= nil) then
player.gui.screen.buddy_request_menu.destroy()
end
-- Find coordinates of a good place to spawn
local spawn_position = FindUngeneratedCoordinates(surface, spawn_choices.distance, 3)
-- Create a new spawn point
local newSpawn = { x = 0, y = 0 }
-- If that fails, just throw a warning and don't spawn them. They can try again.
if ((spawn_position.x == 0) and (spawn_position.y == 0)) then
player.print({ "oarc-no-ungenerated-land-error" })
return
end
-- Create a new force for the combined players if they chose that option
if spawn_choices.buddy_team then
@ -1122,15 +1120,12 @@ function AcceptBuddyRequest(player, requesting_buddy_name)
CreatePlayerCustomForce(requesting_buddy)
end
local surface = game.surfaces[spawn_choices.surface]
-- Find coordinates of a good place to spawn
newSpawn = FindUngeneratedCoordinates(surface, spawn_choices.distance)
-- If that fails, find a random map edge in a rand direction.
if ((newSpawn.x == 0) and (newSpawn.x == 0)) then
newSpawn = FindMapEdge(GetRandomVector(), surface)
log("Resorting to find map edge! x=" .. newSpawn.x .. ",y=" .. newSpawn.y)
-- Destroy GUIs
if (requesting_buddy.gui.screen.buddy_wait_menu ~= nil) then
requesting_buddy.gui.screen.buddy_wait_menu.destroy()
end
if (player.gui.screen.buddy_request_menu ~= nil) then
player.gui.screen.buddy_request_menu.destroy()
end
-- Create that spawn in the global vars
@ -1140,12 +1135,12 @@ function AcceptBuddyRequest(player, requesting_buddy_name)
if (spawn_choices.moat) then
x_offset = x_offset + 10
end
buddySpawn = { x = newSpawn.x + x_offset, y = newSpawn.y }
ChangePlayerRespawn(player.name, spawn_choices.surface, newSpawn)
buddySpawn = { x = spawn_position.x + x_offset, y = spawn_position.y }
ChangePlayerRespawn(player.name, spawn_choices.surface, spawn_position)
ChangePlayerRespawn(requesting_buddy_name, spawn_choices.surface, buddySpawn)
-- Send the player there
QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, newSpawn, spawn_choices.moat, true, requesting_buddy_name)
QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, spawn_position, spawn_choices.moat, true, requesting_buddy_name)
QueuePlayerForDelayedSpawn(requesting_buddy_name, spawn_choices.surface, buddySpawn, spawn_choices.moat, true, player.name)
SendBroadcastMsg(requesting_buddy_name .. " and " .. player.name .. " are joining the game together!")

View File

@ -61,9 +61,10 @@ oarc-buddy-spawn-info=The buddy system requires 2 players in this menu at the sa
oarc-max-players-shared-spawn=You can allow up to __1__ other players to join you.
oarc-spawn-dist-notes=Spawn distance is in CHUNKS from the center of the map.\nExpect a fight to reach other players.
oarc-player-is-joining-main-force=__1__ is joining the main force on __2__!
oarc-player-is-joining-near=__1__ is joining the game from a distance on __2__!
oarc-player-is-joining-far=__1__ is joining the game from a great distance on __2__!
oarc-player-is-joining=__1__ is joining the game on __2__!
oarc-player-started-own-team=__1__ has started their own team!
oarc-player-no-new-teams-sorry=Sorry, no new teams can be created. You were assigned to the default team instead.
oarc-no-ungenerated-land-error=[color=red]Failed to find ungenerated land to spawn on! Please try again. If you see this message multiple times, it could be a settings or mod conflict issue.[/color]
oarc-please-wait=PLEASE WAIT WHILE YOUR SPAWN POINT IS GENERATED!
oarc-player-left-early=Player (__1__) left the game before __2__ minutes of play time. Their spawn area has been marked for cleanup.