1
0
mirror of https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git synced 2025-01-10 00:28:22 +02:00
FactorioScenarioMultiplayer.../lib/regrowth_map.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