-- Copyright (C) 2022 veden -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . if ChunkPropertyUtilsG then return ChunkPropertyUtilsG end local ChunkPropertyUtils = {} -- local Universe local Position -- local Constants = require("Constants") local MathUtils = require("MathUtils") -- Constants local DURATION_ACTIVE_NEST = Constants.DURATION_ACTIVE_NEST local PLAYER_GENERATOR_PERSISTANCE = Constants.PLAYER_GENERATOR_PERSISTANCE local PLAYER_PHEROMONE_GENERATOR_AMOUNT = Constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT local COOLDOWN_DRAIN = Constants.COOLDOWN_DRAIN local RAIDING_MINIMUM_BASE_THRESHOLD = Constants.RAIDING_MINIMUM_BASE_THRESHOLD local PLAYER_PHEROMONE = Constants.PLAYER_PHEROMONE local BASE_PHEROMONE = Constants.BASE_PHEROMONE local MOVEMENT_GENERATOR_PERSISTANCE = Constants.MOVEMENT_GENERATOR_PERSISTANCE local CHUNK_ALL_DIRECTIONS = Constants.CHUNK_ALL_DIRECTIONS local MAGIC_MAXIMUM_NUMBER = Constants.MAGIC_MAXIMUM_NUMBER -- imported functions local activateMap local euclideanDistancePoints = MathUtils.euclideanDistancePoints local tableSize = table_size local mMin = math.min -- module code local function getActiveTick(chunk) return chunk.activeTick or 0 end local function registerActiveSpawner(chunk, register, activeType, tick) local chunkId = chunk.id if Universe[register][chunkId] then return end chunk.activeTick = tick + DURATION_ACTIVE_NEST local base = chunk.base base[activeType] = base[activeType] + 1 Universe[register][chunkId] = chunk end local function unregisterActiveSpawner(chunk, register, iterator, activeType) local chunkId = chunk.id if not Universe[register][chunkId] then return end chunk.activeTick = nil local base = chunk.base base[activeType] = base[activeType] - 1 Universe[register][chunkId] = nil if (Universe[iterator] == chunkId) then Universe[iterator] = nil end end local function registerActiveNest(chunk, tick) registerActiveSpawner(chunk, "chunkToActiveNest", "activeNests", tick) end local function registerActiveRaidNest(chunk, tick) registerActiveSpawner(chunk, "chunkToActiveRaidNest", "activeRaidNests", tick) end local function unregisterActiveNest(chunk) unregisterActiveSpawner( chunk, "chunkToActiveNest", "processActiveSpawnerIterator", "activeNests" ) end local function unregisterActiveRaidNest(chunk) unregisterActiveSpawner( chunk, "chunkToActiveRaidNest", "processActiveRaidSpawnerIterator", "activeRaidNests" ) end local function addEnemyStructure(chunk, unitNumber, ids, counts, register) activateMap(chunk.map) local chunkId = chunk.id local entityIds = chunk[ids] if not entityIds then entityIds = {} chunk[ids] = entityIds end if entityIds[unitNumber] then return false end entityIds[unitNumber] = true chunk[counts] = (chunk[counts] or 0) + 1 if register then Universe[register][chunkId] = chunk end return true end local function removeEnemyStructure(chunk, unitNumber, ids, counts, register) local chunkId = chunk.id local entityIds = chunk[ids] if not (entityIds and entityIds[unitNumber]) then return false end entityIds[unitNumber] = nil chunk[counts] = chunk[counts] - 1 if chunk[counts] > 0 then return true end chunk[ids] = nil chunk[counts] = nil if register then Universe[register][chunkId] = nil end if (ids == "nestIds") then unregisterActiveNest(chunk) unregisterActiveRaidNest(chunk) if (Universe.processMigrationIterator == chunkId) then Universe.processMigrationIterator = nil end if (Universe.processNestIterator == chunkId) then Universe.processNestIterator = nil end end return true end function ChunkPropertyUtils.addTurretCount(chunk, unitNumber) return addEnemyStructure(chunk, unitNumber, "turretIds", "turretCount") end function ChunkPropertyUtils.removeTurretCount(chunk, unitNumber) return removeEnemyStructure(chunk, unitNumber, "turretIds", "turretCount") end function ChunkPropertyUtils.addUtilitiesCount(chunk, unitNumber) return addEnemyStructure(chunk, unitNumber, "utilityIds", "utilityCount", "chunkToUtilities") end function ChunkPropertyUtils.removeUtilitiesCount(chunk, unitNumber) return removeEnemyStructure(chunk, unitNumber, "utilityIds", "utilityCount", "chunkToUtilities") end function ChunkPropertyUtils.addHiveCount(chunk, unitNumber) return addEnemyStructure(chunk, unitNumber, "hiveIds", "hiveCount", "chunkToHives") end function ChunkPropertyUtils.removeHiveCount(chunk, unitNumber) return removeEnemyStructure(chunk, unitNumber, "hiveIds", "hiveCount", "chunkToHives") end function ChunkPropertyUtils.addNestCount(chunk, unitNumber) return addEnemyStructure(chunk, unitNumber, "nestIds", "nestCount", "chunkToNests") end function ChunkPropertyUtils.removeNestCount(chunk, unitNumber) return removeEnemyStructure(chunk, unitNumber, "nestIds", "nestCount", "chunkToNests") end function ChunkPropertyUtils.isActiveNest(chunk) return Universe.chunkToActiveNest[chunk.id] ~= nil end function ChunkPropertyUtils.isActiveRaidNest(chunk) return Universe.chunkToActiveRaidNest[chunk.id] ~= nil end function ChunkPropertyUtils.addBaseResourceChunk(base, chunk) if not chunk.resourceGenerator then return end base.resourceChunkCount = base.resourceChunkCount + 1 base.resourceChunks[chunk.id] = true end function ChunkPropertyUtils.removeBaseResourceChunk(base, chunk) if not base.resourceChunks[chunk.id] then return end base.resourceChunkCount = base.resourceChunkCount - 1 base.resourceChunks[chunk.id] = nil end function ChunkPropertyUtils.removeChunkBase(chunk, base) if not chunk.base then return end base.chunkCount = base.chunkCount - 1 base.totalX = base.totalX - chunk.x base.totalY = base.totalY - chunk.y base.x = base.totalX / base.chunkCount base.y = base.totalY / base.chunkCount chunk.base = nil end function ChunkPropertyUtils.setChunkBase(chunk, base) if chunk.base then return end base.chunkCount = base.chunkCount + 1 base.totalX = base.totalX + chunk.x base.totalY = base.totalY + chunk.y base.x = base.totalX / base.chunkCount base.y = base.totalY / base.chunkCount chunk.base = base end function ChunkPropertyUtils.getEnemyStructureCount(chunk) return (chunk.nestCount or 0) + (chunk.turretCount or 0) + (chunk.utilityCount or 0) + (chunk.hiveCount or 0) end function ChunkPropertyUtils.setDrainPylons(map, entity1, entity2) local pair = {entity1, entity2} map.drainPylons[entity1.unit_number] = pair map.drainPylons[entity2.unit_number] = pair end function ChunkPropertyUtils.removeDrainPylons(map, unitNumber) local pair = map.drainPylons[unitNumber] if pair then local target = pair[1] local pole = pair[2] if target.unit_number == unitNumber then map.drainPylons[unitNumber] = nil if pole.valid then map.drainPylons[pole.unit_number] = nil pole.destroy() end elseif (pole.unit_number == unitNumber) then map.drainPylons[unitNumber] = nil if target.valid then map.drainPylons[target.unit_number] = nil target.die() end end end end function ChunkPropertyUtils.getDrainPylonPair(map, unitNumber) return map.drainPylons[unitNumber] end function ChunkPropertyUtils.isDrained(chunk, tick) local drainTick = Universe.chunkToDrained[chunk.id] if not drainTick then return false end return (tick - drainTick) < COOLDOWN_DRAIN end function ChunkPropertyUtils.setDrainedTick(chunk, tick) Universe.chunkToDrained[chunk.id] = tick end function ChunkPropertyUtils.getRetreatTick(chunk) return Universe.chunkToRetreats[chunk.id] or 0 end function ChunkPropertyUtils.getRallyTick(chunk) return Universe.chunkToRallys[chunk.id] or 0 end function ChunkPropertyUtils.setRallyTick(chunk, tick) Universe.chunkToRallys[chunk.id] = tick end function ChunkPropertyUtils.setRetreatTick(chunk, tick) Universe.chunkToRetreats[chunk.id] = tick end function ChunkPropertyUtils.setResourceGenerator(chunk, resourceGenerator) if (resourceGenerator <= 0) then chunk.resourceGenerator = nil else chunk.resourceGenerator = resourceGenerator end end function ChunkPropertyUtils.getResourceGenerator(chunk) return chunk.resourceGenerator or 0 end function ChunkPropertyUtils.addResourceGenerator(chunk, delta) chunk.resourceGenerator = (chunk.resourceGenerator or 0) + delta end function ChunkPropertyUtils.getPassable(chunk) return chunk.passable or CHUNK_ALL_DIRECTIONS end function ChunkPropertyUtils.setPassable(chunk, value) if (value == CHUNK_ALL_DIRECTIONS) then chunk.passable = nil else chunk.passable = value end end function ChunkPropertyUtils.getPathRating(chunk) return chunk.pathRating or 1 end function ChunkPropertyUtils.setPathRating(chunk, value) if (value == 1) then chunk.pathRating = nil else chunk.pathRating = value end end function ChunkPropertyUtils.getDeathGeneratorRating(chunk) return 1 + (chunk.deathGenerator or 0) end function ChunkPropertyUtils.getCombinedDeathGeneratorRating(chunk) local amount = 1 + ((chunk.deathGenerator or 0) + (chunk.permanentDeathGenerator or 0)) if (amount > 1) then return 1 elseif (amount <= 0) then return 0.001 else return amount end end function ChunkPropertyUtils.getDeathGenerator(chunk) return chunk.deathGenerator or 0 end function ChunkPropertyUtils.getPermanentDeathGeneratorRating(chunk) return 1 + (chunk.permanentDeathGenerator or 0) end function ChunkPropertyUtils.getCombinedDeathGenerator(chunk) local amount = (chunk.deathGenerator or 0) + (chunk.permanentDeathGenerator or 0) if (amount > 1) then return 1 elseif (amount < -1) then return -1 else return amount end end function ChunkPropertyUtils.addPermanentDeathGenerator(chunk, amount) local adjustedAmount = (amount * 0.25) + (chunk.permanentDeathGenerator or 0) if (adjustedAmount > 0.75) then chunk.permanentDeathGenerator = 0.75 elseif (adjustedAmount < -0.75) then chunk.permanentDeathGenerator = -0.75 else chunk.permanentDeathGenerator = adjustedAmount end end function ChunkPropertyUtils.addDeathGenerator(chunk, value) local currentAmount = (chunk.deathGenerator or 0) + value if (currentAmount > 1) then chunk.deathGenerator = 1 elseif (currentAmount < -1) then chunk.deathGenerator = -1 else chunk.deathGenerator = currentAmount end end function ChunkPropertyUtils.setDeathGenerator(chunk, value) if (value > 1) then chunk.deathGenerator = 1 elseif (value < -1) then chunk.deathGenerator = -1 else chunk.deathGenerator = value end end function ChunkPropertyUtils.addVictoryGenerator(chunk, value) local cToV = Universe.chunkToVictory local chunkId = chunk.id if not cToV[chunkId] then cToV[chunkId] = { chunk = chunk, v = 0 } end cToV[chunkId].v = cToV[chunkId].v + value end function ChunkPropertyUtils.decayDeathGenerator(chunk) local gen = chunk.deathGenerator if gen then local v = gen * MOVEMENT_GENERATOR_PERSISTANCE if (v < 0.0001) and (v > -0.0001) then chunk.deathGenerator = nil else chunk.deathGenerator = v end end end function ChunkPropertyUtils.decayPlayerGenerator(chunk) local gen = chunk.playerGenerator if gen then local v = gen * PLAYER_GENERATOR_PERSISTANCE if (v < 0.0001) and (v > -0.0001) then chunk.playerGenerator = nil else chunk.playerGenerator = v end end end function ChunkPropertyUtils.addPlayerGenerator(chunk, playerMaxGenerator) local value = chunk.playerGenerator if value then chunk.playerGenerator = mMin( value + PLAYER_PHEROMONE_GENERATOR_AMOUNT, playerMaxGenerator ) else chunk.playerGenerator = PLAYER_PHEROMONE_GENERATOR_AMOUNT end end function ChunkPropertyUtils.getPlayerGenerator(chunk) return chunk.playerGenerator or 0 end function ChunkPropertyUtils.getPlayerBaseGenerator(chunk) return chunk.playerBaseGenerator or 0 end function ChunkPropertyUtils.addSquadToChunk(chunk, squad) if (chunk ~= -1) and ( (squad.chunk == -1) or (squad.chunk.id ~= chunk.id) ) then ChunkPropertyUtils.removeSquadFromChunk(squad) local squads = chunk.squads if not squads then squads = {} chunk.squads = squads end squads[squad.groupNumber] = squad squad.chunk = chunk end end function ChunkPropertyUtils.removeSquadFromChunk(squad) local chunk = squad.chunk if (chunk ~= -1) then local squads = chunk.squads if squads then squads[squad.groupNumber] = nil if (tableSize(squads) == 0) then chunk.squads = nil end end end end function ChunkPropertyUtils.setPlayerBaseGenerator(chunk, playerBaseGenerator) if (playerBaseGenerator <= 0) then chunk.playerBaseGenerator = nil return 0 else chunk.playerBaseGenerator = playerBaseGenerator return playerBaseGenerator end end function ChunkPropertyUtils.addPlayerBaseGenerator(chunk, playerBaseGenerator) local amount = (chunk.playerBaseGenerator or 0) + playerBaseGenerator if amount <= 0 then chunk.playerBaseGenerator = nil return 0 else chunk.playerBaseGenerator = amount return amount end end function ChunkPropertyUtils.findNearbyBase(chunk) local foundBase = chunk.base if foundBase then return foundBase end return ChunkPropertyUtils.findNearbyBaseByPosition( chunk.map, chunk.x, chunk.y ) end function ChunkPropertyUtils.findNearbyBaseByPosition(map, x, y) local foundBase local closest = MAGIC_MAXIMUM_NUMBER for _, base in pairs(map.bases) do local distance = euclideanDistancePoints(base.x, base.y, x, y) if (distance <= base.distanceThreshold) and (distance < closest) then closest = distance foundBase = base end end return foundBase end function ChunkPropertyUtils.processNestActiveness(chunk, tick) if getActiveTick(chunk) > tick then return end if chunk.nestCount then local surface = chunk.map.surface if Universe.attackUsePlayer and (chunk[PLAYER_PHEROMONE] > Universe.attackPlayerThreshold) then registerActiveNest(chunk, tick) elseif (chunk[BASE_PHEROMONE] > 0) then local getPollution = surface.get_pollution if (getPollution(chunk) > 0) then registerActiveNest(chunk, tick) else local x = chunk.x local y = chunk.y local pollutionThreshold = Universe.pollutionDiffuseMinimum Position.x = x + 32 Position.y = y if (getPollution(Position) > pollutionThreshold) then registerActiveNest(chunk, tick) else Position.x = x - 32 if (getPollution(Position) > pollutionThreshold) then registerActiveNest(chunk, tick) else Position.x = x Position.y = y - 32 if (getPollution(Position) > pollutionThreshold) then registerActiveNest(chunk, tick) else Position.y = y + 32 if (getPollution(Position) > pollutionThreshold) then registerActiveNest(chunk, tick) else unregisterActiveNest(chunk) if (chunk[BASE_PHEROMONE] > RAIDING_MINIMUM_BASE_THRESHOLD) then registerActiveRaidNest(chunk, tick) else unregisterActiveRaidNest(chunk) end end end end end end else unregisterActiveNest(chunk) unregisterActiveRaidNest(chunk) end else unregisterActiveNest(chunk) unregisterActiveRaidNest(chunk) end end function ChunkPropertyUtils.init(universe, mapUtils) Universe = universe activateMap = mapUtils.activateMap if universe.chunkPropertyUtilsQueries then Position = universe.chunkPropertyUtilsQueries.position end end ChunkPropertyUtilsG = ChunkPropertyUtils return ChunkPropertyUtils