-- regrowth_map.lua -- July 2017 -- -- Code tracks all chunks generated and allows for deleting inactive chunks -- Relies on some changes to RSO to provide random resource locations the next -- time the land is regenerated. -- -- Basic rules of regrowth: -- 1. Area around player is safe for quite a large distance. -- 2. Rocket silo won't be deleted. - PERMANENT -- 3. Chunks with pollution won't be deleted. -- 4. Chunks with railways won't be deleted. -- 5. 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. -- 6. Chunks timeout after 1 hour-ish, configurable -- 7. For now, oarc spawns are deletion safe as well, but only immediate area. -- Generic Utility Includes require("locale/oarc_utils") -- Default timeout of generated chunks REGROWTH_TIMEOUT_TICKS = TICKS_PER_HOUR -- We can't delete chunks regularly without causing lag. -- So we should save them up to delete them. REGROWTH_CLEANING_INTERVAL_TICKS = REGROWTH_TIMEOUT_TICKS -- Not used right now. -- It takes a radar 7 hours and 20 minutes to scan it's whole area completely -- So I will bump the refresh time of blocks up by 8 hours -- RADAR_COMPLETE_SCAN_TICKS = TICKS_PER_HOUR*8 -- Additional bonus time for certain things: -- REFRESH_BONUS_RADAR = RADAR_COMPLETE_SCAN_TICKS -- Init globals and set player join area to be off limits. function OarcRegrowthInit() global.chunk_regrow = {} global.chunk_regrow.map = {} global.chunk_regrow.removal_list = {} global.chunk_regrow.rso_region_roll_counter = 0 global.chunk_regrow.player_refresh_index = 1 global.chunk_regrow.min_x = 0 global.chunk_regrow.max_x = 0 global.chunk_regrow.x_index = 0 global.chunk_regrow.min_y = 0 global.chunk_regrow.max_y = 0 global.chunk_regrow.y_index = 0 global.chunk_regrow.force_removal_flag = 0 OarcRegrowthOffLimits({x=0,y=0}, 10) end function GetChunkTopLeft(pos) return {x=pos.x-(pos.x % 32), y=pos.y-(pos.y % 32)} end function GetChunkCoordsFromPos(pos) return {x=math.floor(pos.x/32), y=math.floor(pos.y/32)} end -- This complicated function checks that if a chunk function CheckChunkEmpty(pos) chunkPos = GetChunkCoordsFromPos(pos) search_top_left = {x=chunkPos.x*32, y=chunkPos.y*32} search_area = {search_top_left, {x=search_top_left.x+32,y=search_top_left.y+32}} total = 0 for f,_ in pairs(game.forces) do if f ~= "neutral" and f ~= "enemy" then entities = game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area = search_area, force=f} total = total + #entities if (#entities > 0) then for _,e in pairs(entities) do if ((e.type == "player") or (e.type == "car") or (e.type == "logistic-robot") or (e.type == "construction-robot")) then total = total - 1 end end end end end -- A destroyed entity is still found during the event check. return (total == 1) end -- game.surfaces[GAME_SURFACE_NAME].find_entities_filtered{area = {game.player.position, {game.player.position.x+32, game.player.position-`32}}, type= "resource"} -- If an entity is mined or destroyed, then check if the chunk -- is empty. If it's empty, reset the refresh timer. function OarcRegrowthCheckChunkEmpty(event) if ((event.entity.force ~= nil) and (event.entity.force ~= "neutral") and (event.entity.force ~= "enemy")) then if CheckChunkEmpty(event.entity.position) then DebugPrint("Resetting chunk timer."..event.entity.position.x.." "..event.entity.position.y) OarcRegrowthForceRefreshChunk(event.entity.position, 0) end end 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 OarcRegrowthChunkGenerate(pos) c_pos = GetChunkCoordsFromPos(pos) -- If this is the first chunk in that row: if (global.chunk_regrow.map[c_pos.x] == nil) then global.chunk_regrow.map[c_pos.x] = {} end -- Confirm the chunk doesn't already have a value set: if (global.chunk_regrow.map[c_pos.x][c_pos.y] == nil) then global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick end -- Store min/max values for x/y dimensions: if (c_pos.x < global.chunk_regrow.min_x) then global.chunk_regrow.min_x = c_pos.x end if (c_pos.x > global.chunk_regrow.max_x) then global.chunk_regrow.max_x = c_pos.x end if (c_pos.y < global.chunk_regrow.min_y) then global.chunk_regrow.min_y = c_pos.y end if (c_pos.y > global.chunk_regrow.max_y) then global.chunk_regrow.max_y = c_pos.y end end -- Mark an area for immediate forced removal function OarcRegrowthMarkForRemoval(pos, chunk_radius) local c_pos = GetChunkCoordsFromPos(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.chunk_regrow.map[x] == nil) then global.chunk_regrow.map[x] = {} end global.chunk_regrow.map[x][y] = nil table.insert(global.chunk_regrow.removal_list, {x=x,y=y}) end end end -- Marks a chunk a position that won't ever be deleted. function OarcRegrowthOffLimitsChunk(pos) local c_pos = GetChunkCoordsFromPos(pos) if (global.chunk_regrow.map[c_pos.x] == nil) then global.chunk_regrow.map[c_pos.y] = {} end global.chunk_regrow.map[c_pos.x][c_pos.y] = -1 end -- Marks a safe area around a position that won't ever be deleted. function OarcRegrowthOffLimits(pos, chunk_radius) local c_pos = GetChunkCoordsFromPos(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.chunk_regrow.map[x] == nil) then global.chunk_regrow.map[x] = {} end global.chunk_regrow.map[x][y] = -1 end end end -- Refreshes timers on a chunk containing position function OarcRegrowthRefreshChunk(pos, bonus_time) local c_pos = GetChunkCoordsFromPos(pos) if (global.chunk_regrow.map[c_pos.x] == nil) then global.chunk_regrow.map[c_pos.y] = {} end if (global.chunk_regrow.map[c_pos.x][c_pos.y] ~= -1) then global.chunk_regrow.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(pos, bonus_time) local c_pos = GetChunkCoordsFromPos(pos) if (global.chunk_regrow.map[c_pos.x] == nil) then global.chunk_regrow.map[c_pos.y] = {} end global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick + bonus_time end -- Refreshes timers on all chunks around a certain area function OarcRegrowthRefreshArea(pos, chunk_radius, bonus_time) local c_pos = GetChunkCoordsFromPos(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.chunk_regrow.map[x] == nil) then global.chunk_regrow.map[x] = {} end if (global.chunk_regrow.map[x][y] ~= -1) then global.chunk_regrow.map[x][y] = game.tick + bonus_time end end end end -- Refreshes timers on all chunks near an ACTIVE radar function OarcRegrowthSectorScan(event) OarcRegrowthRefreshArea(event.radar.position, 14, 0) OarcRegrowthRefreshChunk(event.chunk_position, 0) end -- Refresh all chunks near a single player. Cyles through all connected players. function OarcRegrowthRefreshPlayerArea() global.chunk_regrow.player_refresh_index = global.chunk_regrow.player_refresh_index + 1 if (global.chunk_regrow.player_refresh_index > #game.connected_players) then global.chunk_regrow.player_refresh_index = 1 end if (game.connected_players[global.chunk_regrow.player_refresh_index]) then OarcRegrowthRefreshArea(game.connected_players[global.chunk_regrow.player_refresh_index].position, 4, 0) end end -- Check each chunk in the 2d array for a timeout value function OarcRegrowthCheckArray() -- Increment X if (global.chunk_regrow.x_index > global.chunk_regrow.max_x) then global.chunk_regrow.x_index = global.chunk_regrow.min_x -- Increment Y if (global.chunk_regrow.y_index > global.chunk_regrow.max_y) then global.chunk_regrow.y_index = global.chunk_regrow.min_y DebugPrint("Finished checking regrowth array."..global.chunk_regrow.min_x.." "..global.chunk_regrow.max_x.." "..global.chunk_regrow.min_y.." "..global.chunk_regrow.max_y) else global.chunk_regrow.y_index = global.chunk_regrow.y_index + 1 end else global.chunk_regrow.x_index = global.chunk_regrow.x_index + 1 end -- Check row exists, otherwise make one. if (global.chunk_regrow.map[global.chunk_regrow.x_index] == nil) then global.chunk_regrow.map[global.chunk_regrow.x_index] = {} end -- If the chunk has timed out, add it to the removal list local c_timer = global.chunk_regrow.map[global.chunk_regrow.x_index][global.chunk_regrow.y_index] if ((c_timer ~= nil) and (c_timer ~= -1) and ((c_timer+REGROWTH_TIMEOUT_TICKS) < game.tick)) then -- Check chunk actually exists if (game.surfaces[GAME_SURFACE_NAME].is_chunk_generated({x=(global.chunk_regrow.x_index), y=(global.chunk_regrow.y_index)})) then table.insert(global.chunk_regrow.removal_list, {x=global.chunk_regrow.x_index, y=global.chunk_regrow.y_index}) global.chunk_regrow.map[global.chunk_regrow.x_index][global.chunk_regrow.y_index] = nil end end end -- Remove all chunks at same time to reduce impact to FPS/UPS function OarcRegrowthRemoveAllChunks() while (#global.chunk_regrow.removal_list > 0) do local c_pos = table.remove(global.chunk_regrow.removal_list) local c_timer = global.chunk_regrow.map[c_pos.x][c_pos.y] -- Confirm chunk is still expired if (c_timer == nil) then -- Check for pollution if (game.surfaces[GAME_SURFACE_NAME].get_pollution({c_pos.x*32,c_pos.y*32}) > 0) then global.chunk_regrow.map[c_pos.x][c_pos.y] = game.tick -- Else delete the chunk else game.surfaces[GAME_SURFACE_NAME].delete_chunk(c_pos) global.chunk_regrow.map[c_pos.x][c_pos.y] = nil end else -- DebugPrint("Chunk no longer expired?") 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 OarcRegrowthOnTick(event) -- 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 OarcRegrowthRefreshPlayerArea() end -- Every tick, check a few points in the 2d array -- According to /measured-command this shouldn't take more -- than 0.1ms on average for i=1,20 do OarcRegrowthCheckArray() end -- Send a broadcast warning before it happens. if ((game.tick % REGROWTH_CLEANING_INTERVAL_TICKS) == REGROWTH_CLEANING_INTERVAL_TICKS-601) then if (#global.chunk_regrow.removal_list > 100) then SendBroadcastMsg("Map cleanup in 10 seconds...") end end -- Delete all listed chunks if ((game.tick % REGROWTH_CLEANING_INTERVAL_TICKS) == REGROWTH_CLEANING_INTERVAL_TICKS-1) then if (#global.chunk_regrow.removal_list > 100) then OarcRegrowthRemoveAllChunks() SendBroadcastMsg("Map cleanup done...") end end -- Catch force remove flag if (game.tick == global.chunk_regrow.force_removal_flag+60) then SendBroadcastMsg("Map cleanup in 10 seconds...") end if (game.tick == global.chunk_regrow.force_removal_flag+660) then OarcRegrowthRemoveAllChunks() SendBroadcastMsg("Map cleanup done...") end end