mirror of
https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git
synced 2025-01-10 00:28:22 +02:00
382 lines
13 KiB
Lua
382 lines
13 KiB
Lua
-- regrowth_map.lua
|
|
-- Sep 2019
|
|
-- REVERTED BACK TO SOFT MOD
|
|
|
|
-- Code tracks all chunks generated and allows for deleting of inactive chunks.
|
|
--
|
|
-- Basic rules of regrowth:
|
|
-- 1. Area around player is safe for quite a large distance.
|
|
-- 2. Chunks with pollution won't be deleted.
|
|
-- 3. Chunks with any player buildings won't be deleted.
|
|
-- 4. Anything within radar range won't be deleted, but radar MUST be active.
|
|
-- -- This works by refreshing all chunk timers within radar range using
|
|
-- the on_sector_scanned event.
|
|
-- 5. Chunks timeout after 1 hour-ish, configurable
|
|
|
|
require("lib/oarc_utils")
|
|
require("config")
|
|
|
|
REGROWTH_TIMEOUT_TICKS = TICKS_PER_HOUR
|
|
|
|
-- Init globals and set player join area to be off limits.
|
|
function RegrowthInit()
|
|
if (global.rg == nil) then
|
|
global.rg = {}
|
|
global.rg.surfaces_index = 1
|
|
global.rg.player_refresh_index = 1
|
|
global.rg.force_removal_flag = -1000
|
|
global.rg.active_surfaces = {}
|
|
end
|
|
end
|
|
|
|
function TriggerCleanup()
|
|
global.rg.force_removal_flag = game.tick
|
|
end
|
|
|
|
function ForceRemoveChunksCmd(cmd_table)
|
|
if (game.players[cmd_table.player_index].admin) then
|
|
TriggerCleanup()
|
|
end
|
|
end
|
|
|
|
function RegrowthAddSurface(s_index)
|
|
RegrowthInit()
|
|
|
|
if (global.rg[s_index] ~= nil) then
|
|
log("ERROR - Tried to add surface that was already added?")
|
|
return
|
|
end
|
|
|
|
log("Oarc Regrowth - ADD SURFACE " .. game.surfaces[s_index].name)
|
|
|
|
global.rg[s_index] = {}
|
|
table.insert(global.rg.active_surfaces, s_index)
|
|
|
|
global.rg[s_index].map = {}
|
|
global.rg[s_index].removal_list = {}
|
|
global.rg[s_index].min_x = 0
|
|
global.rg[s_index].max_x = 0
|
|
global.rg[s_index].x_index = 0
|
|
global.rg[s_index].min_y = 0
|
|
global.rg[s_index].max_y = 0
|
|
global.rg[s_index].y_index = 0
|
|
|
|
-- MarkAreaSafeGivenTilePos({x=0,y=0}, 10)
|
|
end
|
|
|
|
-- Adds new chunks to the global table to track them.
|
|
-- This should always be called first in the chunk generate sequence
|
|
-- (Compared to other RSO & Oarc related functions...)
|
|
function RegrowthChunkGenerate(event)
|
|
|
|
local s_index = event.surface.index
|
|
local c_pos = GetChunkPosFromTilePos(event.area.left_top)
|
|
|
|
-- Surface must be "added" first.
|
|
if (global.rg[s_index] == nil) then return end
|
|
|
|
-- If this is the first chunk in that row:
|
|
if (global.rg[s_index].map[c_pos.x] == nil) then
|
|
global.rg[s_index].map[c_pos.x] = {}
|
|
end
|
|
|
|
-- Confirm the chunk doesn't already have a value set:
|
|
if (global.rg[s_index].map[c_pos.x][c_pos.y] == nil) then
|
|
global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick
|
|
end
|
|
|
|
-- Store min/max values for x/y dimensions:
|
|
if (c_pos.x < global.rg[s_index].min_x) then
|
|
global.rg[s_index].min_x = c_pos.x
|
|
end
|
|
if (c_pos.x > global.rg[s_index].max_x) then
|
|
global.rg[s_index].max_x = c_pos.x
|
|
end
|
|
if (c_pos.y < global.rg[s_index].min_y) then
|
|
global.rg[s_index].min_y = c_pos.y
|
|
end
|
|
if (c_pos.y > global.rg[s_index].max_y) then
|
|
global.rg[s_index].max_y = c_pos.y
|
|
end
|
|
end
|
|
|
|
-- Mark an area for "immediate" forced removal
|
|
function MarkAreaForRemoval(s_index, pos, chunk_radius)
|
|
local c_pos = GetChunkPosFromTilePos(pos)
|
|
for i=-chunk_radius,chunk_radius do
|
|
for k=-chunk_radius,chunk_radius do
|
|
local x = c_pos.x+i
|
|
local y = c_pos.y+k
|
|
|
|
if (global.rg[s_index].map[x] == nil) then
|
|
global.rg[s_index].map[x] = {}
|
|
end
|
|
global.rg[s_index].map[x][y] = nil
|
|
table.insert(global.rg[s_index].removal_list,
|
|
{pos={x=x,y=y},force=true})
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Marks a chunk containing a position that won't ever be deleted.
|
|
function MarkChunkSafe(s_index, c_pos)
|
|
if (global.rg[s_index].map[c_pos.x] == nil) then
|
|
global.rg[s_index].map[c_pos.x] = {}
|
|
end
|
|
global.rg[s_index].map[c_pos.x][c_pos.y] = -1
|
|
end
|
|
|
|
-- Marks a safe area around a TILE position that won't ever be deleted.
|
|
function MarkAreaSafeGivenTilePos(s_index, pos, chunk_radius)
|
|
if (global.rg[s_index] == nil) then return end
|
|
|
|
local c_pos = GetChunkPosFromTilePos(pos)
|
|
MarkAreaSafeGivenChunkPos(s_index, c_pos, chunk_radius)
|
|
end
|
|
|
|
-- Marks a safe area around a CHUNK position that won't ever be deleted.
|
|
function MarkAreaSafeGivenChunkPos(s_index, c_pos, chunk_radius)
|
|
if (global.rg[s_index] == nil) then return end
|
|
|
|
for i=-chunk_radius,chunk_radius do
|
|
for j=-chunk_radius,chunk_radius do
|
|
MarkChunkSafe(s_index, {x=c_pos.x+i,y=c_pos.y+j})
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Refreshes timers on a chunk containing position
|
|
function RefreshChunkTimer(s_index, pos, bonus_time)
|
|
local c_pos = GetChunkPosFromTilePos(pos)
|
|
|
|
if (global.rg[s_index].map[c_pos.x] == nil) then
|
|
global.rg[s_index].map[c_pos.x] = {}
|
|
end
|
|
if (global.rg[s_index].map[c_pos.x][c_pos.y] ~= -1) then
|
|
global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick + bonus_time
|
|
end
|
|
end
|
|
|
|
-- Forcefully refreshes timers on a chunk containing position
|
|
-- Will overwrite -1 flag.
|
|
-- function OarcRegrowthForceRefreshChunk(s_index, pos, bonus_time)
|
|
-- local c_pos = GetChunkPosFromTilePos(pos)
|
|
|
|
-- if (global.rg[s_index].map[c_pos.x] == nil) then
|
|
-- global.rg[s_index].map[c_pos.x] = {}
|
|
-- end
|
|
-- global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick + bonus_time
|
|
-- end
|
|
|
|
-- Refreshes timers on all chunks around a certain area
|
|
function RefreshArea(s_index, pos, chunk_radius, bonus_time)
|
|
local c_pos = GetChunkPosFromTilePos(pos)
|
|
|
|
for i=-chunk_radius,chunk_radius do
|
|
for k=-chunk_radius,chunk_radius do
|
|
local x = c_pos.x+i
|
|
local y = c_pos.y+k
|
|
|
|
if (global.rg[s_index].map[x] == nil) then
|
|
global.rg[s_index].map[x] = {}
|
|
end
|
|
if (global.rg[s_index].map[x][y] ~= -1) then
|
|
global.rg[s_index].map[x][y] = game.tick + bonus_time
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Refreshes timers on all chunks near an ACTIVE radar
|
|
function RegrowthSectorScan(event)
|
|
local s_index = event.radar.surface.index
|
|
if (global.rg[s_index] == nil) then return end
|
|
|
|
RefreshArea(s_index, event.radar.position, 14, 0)
|
|
RefreshChunkTimer(s_index, event.chunk_position, 0)
|
|
end
|
|
|
|
-- Refresh all chunks near a single player. Cyles through all connected players.
|
|
function RefreshPlayerArea()
|
|
global.rg.player_refresh_index = global.rg.player_refresh_index + 1
|
|
if (global.rg.player_refresh_index > #game.connected_players) then
|
|
global.rg.player_refresh_index = 1
|
|
end
|
|
if (game.connected_players[global.rg.player_refresh_index]) then
|
|
local player = game.connected_players[global.rg.player_refresh_index]
|
|
if (not player.character) then return end
|
|
|
|
local s_index = player.character.surface.index
|
|
if (global.rg[s_index] == nil) then return end
|
|
|
|
RefreshArea(s_index, player.position, 4, 0)
|
|
end
|
|
end
|
|
|
|
-- Gets the next chunk the array map and checks to see if it has timed out.
|
|
-- Adds it to the removal list if it has.
|
|
function RegrowthSingleStepArray(s_index)
|
|
|
|
-- Increment X and reset when we hit the end.
|
|
if (global.rg[s_index].x_index > global.rg[s_index].max_x) then
|
|
global.rg[s_index].x_index = global.rg[s_index].min_x
|
|
|
|
-- Increment Y and reset when we hit the end.
|
|
if (global.rg[s_index].y_index > global.rg[s_index].max_y) then
|
|
global.rg[s_index].y_index = global.rg[s_index].min_y
|
|
-- log("Finished checking regrowth array. "..
|
|
-- game.surfaces[s_index].name.." "..
|
|
-- global.rg[s_index].min_x.." "..
|
|
-- global.rg[s_index].max_x.." "..
|
|
-- global.rg[s_index].min_y.." "..
|
|
-- global.rg[s_index].max_y)
|
|
else
|
|
global.rg[s_index].y_index = global.rg[s_index].y_index + 1
|
|
end
|
|
else
|
|
global.rg[s_index].x_index = global.rg[s_index].x_index + 1
|
|
end
|
|
|
|
local xidx = global.rg[s_index].x_index
|
|
local yidx = global.rg[s_index].y_index
|
|
|
|
if (not xidx or not yidx) then
|
|
log("ERROR - xidx or yidx is nil?")
|
|
end
|
|
|
|
-- Check row exists, otherwise make one.
|
|
if (global.rg[s_index].map[xidx] == nil) then
|
|
global.rg[s_index].map[xidx] = {}
|
|
end
|
|
|
|
-- If the chunk has timed out, add it to the removal list
|
|
local c_timer = global.rg[s_index].map[xidx][yidx]
|
|
if ((c_timer ~= nil) and (c_timer ~= -1) and
|
|
((c_timer + REGROWTH_TIMEOUT_TICKS) < game.tick)) then
|
|
|
|
-- Check chunk actually exists
|
|
if (game.surfaces[s_index].is_chunk_generated({x=xidx, y=yidx})) then
|
|
table.insert(global.rg[s_index].removal_list, {pos={x=xidx,
|
|
y=yidx},
|
|
force=false})
|
|
global.rg[s_index].map[xidx][yidx] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Remove all chunks at same time to reduce impact to FPS/UPS
|
|
function OarcRegrowthRemoveAllChunks()
|
|
for _,s_index in pairs(global.rg.active_surfaces) do
|
|
print(k,v)
|
|
|
|
while (#global.rg[s_index].removal_list > 0) do
|
|
local c_remove = table.remove(global.rg[s_index].removal_list)
|
|
local c_pos = c_remove.pos
|
|
local c_timer = global.rg[s_index].map[c_pos.x][c_pos.y]
|
|
|
|
if (game.surfaces[s_index] == nil) then
|
|
log("Error! game.surfaces[name] is nil?? WTF?")
|
|
return
|
|
end
|
|
|
|
-- Confirm chunk is still expired
|
|
if (c_timer == nil) then
|
|
|
|
-- If it is FORCE removal, then remove it regardless of pollution.
|
|
if (c_remove.force) then
|
|
game.surfaces[s_index].delete_chunk(c_pos)
|
|
global.rg[s_index].map[c_pos.x][c_pos.y] = nil
|
|
|
|
-- If it is a normal timeout removal, don't do it if there is pollution in the chunk.
|
|
elseif (game.surfaces[s_index].get_pollution({c_pos.x*32,c_pos.y*32}) > 0) then
|
|
global.rg[s_index].map[c_pos.x][c_pos.y] = game.tick
|
|
|
|
-- Else delete the chunk
|
|
else
|
|
game.surfaces[s_index].delete_chunk(c_pos)
|
|
global.rg[s_index].map[c_pos.x][c_pos.y] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- This is the main work function, it checks a single chunk in the list
|
|
-- per tick. It works according to the rules listed in the header of this
|
|
-- file.
|
|
function RegrowthOnTick()
|
|
|
|
if (#global.rg.active_surfaces == 0) then return end
|
|
|
|
-- Every half a second, refresh all chunks near a single player
|
|
-- Cyles through all players. Tick is offset by 2
|
|
if ((game.tick % (30)) == 2) then
|
|
RefreshPlayerArea()
|
|
end
|
|
|
|
-- Iterate through the active surfaces.
|
|
if (global.rg.surfaces_index > #global.rg.active_surfaces) then
|
|
global.rg.surfaces_index = 1
|
|
end
|
|
local s_index = global.rg.active_surfaces[global.rg.surfaces_index]
|
|
global.rg.surfaces_index = global.rg.surfaces_index+1
|
|
|
|
if (s_index == nil) then
|
|
log("ERROR - s_index = nil in OarcRegrowthOnTick?")
|
|
return
|
|
end
|
|
|
|
-- Every tick, check a few points in the 2d array of one of the active surfaces
|
|
-- According to /measured-command this shouldn't take more
|
|
-- than 0.1ms on average
|
|
for i=1,20 do
|
|
RegrowthSingleStepArray(s_index)
|
|
end
|
|
|
|
-- Allow enable/disable of auto cleanup, can change during runtime.
|
|
if (global.ocfg.enable_regrowth) then
|
|
|
|
local interval_ticks = REGROWTH_TIMEOUT_TICKS
|
|
-- Send a broadcast warning before it happens.
|
|
if ((game.tick % interval_ticks) == interval_ticks-601) then
|
|
if (#global.rg[s_index].removal_list > 100) then
|
|
SendBroadcastMsg("Map cleanup in 10 seconds... Unused and old map chunks will be deleted!")
|
|
end
|
|
end
|
|
|
|
-- Delete all listed chunks across all active surfaces
|
|
if ((game.tick % interval_ticks) == interval_ticks-1) then
|
|
if (#global.rg[s_index].removal_list > 100) then
|
|
OarcRegrowthRemoveAllChunks()
|
|
SendBroadcastMsg("Map cleanup done, sorry for your loss.")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- This function removes any chunks flagged but on demand.
|
|
-- Controlled by the global.rg.force_removal_flag
|
|
-- This function may be used outside of the normal regrowth modse.
|
|
function RegrowthForceRemovalOnTick()
|
|
-- Catch force remove flag
|
|
if (game.tick == global.rg.force_removal_flag+60) then
|
|
SendBroadcastMsg("Map cleanup (forced) in 10 seconds... Unused and old map chunks will be deleted!")
|
|
end
|
|
|
|
if (game.tick == global.rg.force_removal_flag+660) then
|
|
OarcRegrowthRemoveAllChunks()
|
|
SendBroadcastMsg("Map cleanup done, sorry for your loss.")
|
|
end
|
|
end
|
|
|
|
-- Broadcast messages to all connected players
|
|
function SendBroadcastMsg(msg)
|
|
for name,player in pairs(game.connected_players) do
|
|
player.print(msg)
|
|
end
|
|
end
|
|
|
|
-- Gets chunk position of a tile.
|
|
function GetChunkPosFromTilePos(tile_pos)
|
|
return {x=math.floor(tile_pos.x/32), y=math.floor(tile_pos.y/32)}
|
|
end |