From e143f4bbbed8ec6be26d921f8014c307ff35adcf Mon Sep 17 00:00:00 2001 From: Aaron Veden Date: Sat, 11 Mar 2023 20:51:13 -0800 Subject: [PATCH] FACTO-258: Consolidated most libraries --- changelog.txt | 2 + control.lua | 88 +- libs/AIAttackWave.lua | 418 ------- libs/AIPlanning.lua | 594 ---------- libs/AIPredicates.lua | 91 -- libs/BaseUtils.lua | 632 ++++++++++- libs/ChunkProcessor.lua | 241 ---- libs/ChunkUtils.lua | 8 +- libs/MapUtils.lua | 146 +++ libs/MovementUtils.lua | 308 ------ libs/PheromoneUtils.lua | 204 ---- libs/PlayerUtils.lua | 37 - libs/{MapProcessor.lua => Processor.lua} | 255 ++++- libs/QueryUtils.lua | 81 -- libs/Squad.lua | 1270 ++++++++++++++++++++++ libs/SquadAttack.lua | 480 -------- libs/SquadDefense.lua | 169 --- libs/StringUtils.lua | 60 - libs/UnitGroupUtils.lua | 178 --- libs/Upgrade.lua | 4 +- libs/Utils.lua | 146 +++ tests.lua | 4 +- 22 files changed, 2428 insertions(+), 2988 deletions(-) delete mode 100644 libs/AIAttackWave.lua delete mode 100644 libs/AIPlanning.lua delete mode 100644 libs/AIPredicates.lua delete mode 100644 libs/ChunkProcessor.lua delete mode 100644 libs/MovementUtils.lua delete mode 100644 libs/PheromoneUtils.lua delete mode 100644 libs/PlayerUtils.lua rename libs/{MapProcessor.lua => Processor.lua} (61%) delete mode 100644 libs/QueryUtils.lua create mode 100644 libs/Squad.lua delete mode 100644 libs/SquadAttack.lua delete mode 100644 libs/SquadDefense.lua delete mode 100644 libs/StringUtils.lua delete mode 100644 libs/UnitGroupUtils.lua create mode 100644 libs/Utils.lua diff --git a/changelog.txt b/changelog.txt index a5f69f0..4f4e09b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -63,6 +63,8 @@ Version: 3.2.0 - Reduced corpse and particle variation to optimize increased entity times due to high entity counts - Removed unneeded iterators - Moved map properties directly into chunk object + Framework: + - Consolidated most libraries --------------------------------------------------------------------------------------------------- Version: 3.1.2 diff --git a/control.lua b/control.lua index 2f6f51e..cd328d7 100644 --- a/control.lua +++ b/control.lua @@ -18,24 +18,15 @@ local ChunkPropertyUtils = require("libs/ChunkPropertyUtils") local UnitUtils = require("libs/UnitUtils") local BaseUtils = require("libs/BaseUtils") -local MapUtils = require("libs/MapUtils") local MathUtils = require("libs/MathUtils") -local UnitGroupUtils = require("libs/UnitGroupUtils") -local ChunkProcessor = require("libs/ChunkProcessor") -local MapProcessor = require("libs/MapProcessor") +local Processor = require("libs/Processor") local Constants = require("libs/Constants") -local PheromoneUtils = require("libs/PheromoneUtils") -local SquadDefense = require("libs/SquadDefense") -local SquadAttack = require("libs/SquadAttack") -local AiAttackWave = require("libs/AIAttackWave") -local AiPlanning = require("libs/AIPlanning") -local MovementUtils = require("libs/MovementUtils") +local MapUtils = require("libs/MapUtils") +local Squad = require("libs/Squad") local tests = require("tests") local ChunkUtils = require("libs/ChunkUtils") local Upgrade = require("libs/Upgrade") -local AiPredicates = require("libs/AIPredicates") -local stringUtils = require("libs/StringUtils") -local queryUtils = require("libs/QueryUtils") +local Utils = require("libs/Utils") -- Constants @@ -68,18 +59,18 @@ local SETTLE_CLOUD_WARMUP = Constants.SETTLE_CLOUD_WARMUP -- imported functions -local isMember = stringUtils.isMember -local split = stringUtils.split +local isMember = Utils.isMember +local split = Utils.split -local planning = AiPlanning.planning +local planning = BaseUtils.planning local addBasesToAllEnemyStructures = ChunkUtils.addBasesToAllEnemyStructures -local setPointAreaInQuery = queryUtils.setPointAreaInQuery +local setPointAreaInQuery = Utils.setPointAreaInQuery local nextMap = MapUtils.nextMap -local processClouds = MapProcessor.processClouds +local processClouds = Processor.processClouds local distortPosition = MathUtils.distortPosition local linearInterpolation = MathUtils.linearInterpolation @@ -88,26 +79,26 @@ local prepMap = Upgrade.prepMap local findBaseInitialAlignment = BaseUtils.findBaseInitialAlignment -local processBaseAIs = AiPlanning.processBaseAIs +local processBaseAIs = BaseUtils.processBaseAIs local registerEnemyBaseStructure = ChunkUtils.registerEnemyBaseStructure local queueGeneratedChunk = MapUtils.queueGeneratedChunk -local isRampantSetting = stringUtils.isRampantSetting +local isRampantSetting = Utils.isRampantSetting -local processPendingUpgrades = ChunkProcessor.processPendingUpgrades -local canMigrate = AiPredicates.canMigrate +local processPendingUpgrades = Processor.processPendingUpgrades +local canMigrate = BaseUtils.canMigrate -local squadDispatch = SquadAttack.squadDispatch +local squadDispatch = Squad.squadDispatch -local cleanUpMapTables = MapProcessor.cleanUpMapTables +local cleanUpMapTables = Processor.cleanUpMapTables local positionToChunkXY = MapUtils.positionToChunkXY -local processVengence = MapProcessor.processVengence -local processAttackWaves = MapProcessor.processAttackWaves +local processVengence = Processor.processVengence +local processAttackWaves = Processor.processAttackWaves -local disperseVictoryScent = PheromoneUtils.disperseVictoryScent +local disperseVictoryScent = MapUtils.disperseVictoryScent local getChunkByPosition = MapUtils.getChunkByPosition @@ -116,31 +107,31 @@ local getChunkByXY = MapUtils.getChunkByXY local entityForPassScan = ChunkUtils.entityForPassScan -local processPendingChunks = ChunkProcessor.processPendingChunks -local processScanChunks = ChunkProcessor.processScanChunks +local processPendingChunks = Processor.processPendingChunks +local processScanChunks = Processor.processScanChunks -local processMap = MapProcessor.processMap -local processPlayers = MapProcessor.processPlayers -local scanEnemyMap = MapProcessor.scanEnemyMap -local scanPlayerMap = MapProcessor.scanPlayerMap -local scanResourceMap = MapProcessor.scanResourceMap +local processMap = Processor.processMap +local processPlayers = Processor.processPlayers +local scanEnemyMap = Processor.scanEnemyMap +local scanPlayerMap = Processor.scanPlayerMap +local scanResourceMap = Processor.scanResourceMap -local processNests = MapProcessor.processNests +local processNests = Processor.processNests -local rallyUnits = AiAttackWave.rallyUnits +local rallyUnits = Squad.rallyUnits local recycleBases = BaseUtils.recycleBases -local deathScent = PheromoneUtils.deathScent -local victoryScent = PheromoneUtils.victoryScent +local deathScent = MapUtils.deathScent +local victoryScent = MapUtils.victoryScent -local createSquad = UnitGroupUtils.createSquad +local createSquad = Squad.createSquad local createBase = BaseUtils.createBase local findNearbyBaseByPosition = ChunkPropertyUtils.findNearbyBaseByPosition local findNearbyBase = ChunkPropertyUtils.findNearbyBase -local processActiveNests = MapProcessor.processActiveNests +local processActiveNests = Processor.processActiveNests local removeDrainPylons = ChunkPropertyUtils.removeDrainPylons local getDrainPylonPair = ChunkPropertyUtils.getDrainPylonPair @@ -151,7 +142,7 @@ local isDrained = ChunkPropertyUtils.isDrained local setDrainedTick = ChunkPropertyUtils.setDrainedTick local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating -local retreatUnits = SquadDefense.retreatUnits +local retreatUnits = Squad.retreatUnits local accountPlayerEntity = ChunkUtils.accountPlayerEntity local unregisterEnemyBaseStructure = ChunkUtils.unregisterEnemyBaseStructure @@ -160,7 +151,7 @@ local makeImmortalEntity = ChunkUtils.makeImmortalEntity local registerResource = ChunkUtils.registerResource local unregisterResource = ChunkUtils.unregisterResource -local cleanSquads = SquadAttack.cleanSquads +local cleanSquads = Squad.cleanSquads local queueUpgrade = BaseUtils.queueUpgrade @@ -221,20 +212,13 @@ local function hookEvents() end local function initializeLibraries() - MapProcessor.init(Universe) - AiPlanning.init(Universe) - AiAttackWave.init(Universe) - AiPredicates.init(Universe) BaseUtils.init(Universe) - ChunkProcessor.init(Universe) + Squad.init(Universe) + BaseUtils.init(Universe) + Processor.init(Universe) ChunkPropertyUtils.init(Universe) ChunkUtils.init(Universe) MapUtils.init(Universe) - MovementUtils.init(Universe) - PheromoneUtils.init(Universe) - SquadAttack.init(Universe) - SquadDefense.init(Universe) - UnitGroupUtils.init(Universe) end local function onLoad() diff --git a/libs/AIAttackWave.lua b/libs/AIAttackWave.lua deleted file mode 100644 index a429338..0000000 --- a/libs/AIAttackWave.lua +++ /dev/null @@ -1,418 +0,0 @@ --- 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 (aiAttackWaveG) then - return aiAttackWaveG -end -local aiAttackWave = {} - --- imports - -local Universe - --- - -local constants = require("Constants") -local mapUtils = require("MapUtils") -local chunkPropertyUtils = require("ChunkPropertyUtils") -local unitGroupUtils = require("UnitGroupUtils") -local movementUtils = require("MovementUtils") -local mathUtils = require("MathUtils") -local baseUtils = require("libs/BaseUtils") - --- constants - -local BASE_PHEROMONE = constants.BASE_PHEROMONE -local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE -local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE - -local AI_SQUAD_COST = constants.AI_SQUAD_COST -local AI_SETTLER_COST = constants.AI_SETTLER_COST -local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST - -local COOLDOWN_RALLY = constants.COOLDOWN_RALLY - -local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS - -local CHUNK_SIZE = constants.CHUNK_SIZE - -local RALLY_CRY_DISTANCE = constants.RALLY_CRY_DISTANCE - -local BASE_AI_STATE_SIEGE = constants.BASE_AI_STATE_SIEGE -local BASE_AI_STATE_ONSLAUGHT = constants.BASE_AI_STATE_ONSLAUGHT -local BASE_AI_STATE_RAIDING = constants.BASE_AI_STATE_RAIDING -local BASE_AI_STATE_AGGRESSIVE = constants.BASE_AI_STATE_AGGRESSIVE - --- imported functions - -local calculateKamikazeSettlerThreshold = unitGroupUtils.calculateKamikazeSettlerThreshold -local calculateKamikazeSquadThreshold = unitGroupUtils.calculateKamikazeSquadThreshold - -local positionFromDirectionAndChunk = mapUtils.positionFromDirectionAndChunk - -local getPassable = chunkPropertyUtils.getPassable -local isActiveRaidNest = chunkPropertyUtils.isActiveRaidNest -local isActiveNest = chunkPropertyUtils.isActiveNest -local getRallyTick = chunkPropertyUtils.getRallyTick -local setRallyTick = chunkPropertyUtils.setRallyTick - -local gaussianRandomRangeRG = mathUtils.gaussianRandomRangeRG - -local getNeighborChunks = mapUtils.getNeighborChunks -local getChunkByXY = mapUtils.getChunkByXY -local scoreNeighborsForFormation = movementUtils.scoreNeighborsForFormation -local scoreNeighborsForResource = movementUtils.scoreNeighborsForResource -local createSquad = unitGroupUtils.createSquad - -local modifyBaseUnitPoints = baseUtils.modifyBaseUnitPoints - -local mCeil = math.ceil - --- module code - -local function settlerWaveScaling() - return mCeil(gaussianRandomRangeRG(Universe.settlerWaveSize, - Universe.settlerWaveDeviation, - Universe.expansionMinSize, - Universe.expansionMaxSize, - Universe.random)) -end - -local function attackWaveScaling() - return mCeil(gaussianRandomRangeRG(Universe.attackWaveSize, - Universe.attackWaveDeviation, - 1, - Universe.attackWaveUpperBound, - Universe.random)) -end - -local function attackWaveValidCandidate(chunk, base) - if isActiveNest(chunk) then - return true - end - if (base.stateAI == BASE_AI_STATE_RAIDING) or - (base.stateAI == BASE_AI_STATE_SIEGE) or - (base.stateAI == BASE_AI_STATE_ONSLAUGHT) - then - return isActiveRaidNest(chunk) - end - return false -end - -local function scoreSettlerLocation(map, neighborChunk) - return neighborChunk[RESOURCE_PHEROMONE] + -neighborChunk[PLAYER_PHEROMONE] -end - -local function scoreSiegeSettlerLocation(map, neighborChunk) - return (neighborChunk[RESOURCE_PHEROMONE] + neighborChunk[BASE_PHEROMONE]) + -neighborChunk[PLAYER_PHEROMONE] -end - -local function scoreUnitGroupLocation(map, neighborChunk) - return neighborChunk[PLAYER_PHEROMONE] + neighborChunk[BASE_PHEROMONE] -end - -local function validSiegeSettlerLocation(map, neighborChunk) - return (getPassable(neighborChunk) == CHUNK_ALL_DIRECTIONS) and - (not neighborChunk.nestCount) -end - -local function validSettlerLocation(map, chunk, neighborChunk) - local chunkResource = chunk[RESOURCE_PHEROMONE] - return (getPassable(neighborChunk) == CHUNK_ALL_DIRECTIONS) and - (not neighborChunk.nestCount) and - (neighborChunk[RESOURCE_PHEROMONE] >= chunkResource) -end - -local function validUnitGroupLocation(map, neighborChunk) - return getPassable(neighborChunk) == CHUNK_ALL_DIRECTIONS and - (not neighborChunk.nestCount) -end - -local function visitPattern(o, cX, cY, distance) - local startX - local endX - local stepX - local startY - local endY - local stepY - if (o == 0) then - startX = cX - distance - endX = cX + distance - stepX = 32 - startY = cY - distance - endY = cY + distance - stepY = 32 - elseif (o == 1) then - startX = cX + distance - endX = cX - distance - stepX = -32 - startY = cY + distance - endY = cY - distance - stepY = -32 - elseif (o == 2) then - startX = cX - distance - endX = cX + distance - stepX = 32 - startY = cY + distance - endY = cY - distance - stepY = -32 - elseif (o == 3) then - startX = cX + distance - endX = cX - distance - stepX = -32 - startY = cY - distance - endY = cY + distance - stepY = 32 - end - return startX, endX, stepX, startY, endY, stepY -end - -function aiAttackWave.rallyUnits(chunk, tick, base) - if ((tick - getRallyTick(chunk) > COOLDOWN_RALLY) and (base.unitPoints >= AI_VENGENCE_SQUAD_COST)) then - setRallyTick(chunk, tick) - local cX = chunk.x - local cY = chunk.y - local startX, endX, stepX, startY, endY, stepY = visitPattern(tick % 4, cX, cY, RALLY_CRY_DISTANCE) - local vengenceQueue = Universe.vengenceQueue - local map = chunk.map - for x=startX, endX, stepX do - for y=startY, endY, stepY do - if (x ~= cX) and (y ~= cY) then - local rallyChunk = getChunkByXY(map, x, y) - if (rallyChunk ~= -1) and rallyChunk.nestCount then - vengenceQueue[rallyChunk.id] = rallyChunk - end - end - end - end - - return true - end -end - -function aiAttackWave.formSettlers(chunk, base) - if (Universe.builderCount < Universe.AI_MAX_BUILDER_COUNT) - and (base.sentExpansionGroups < base.maxExpansionGroups) - and ((base.unitPoints - AI_SETTLER_COST) > 0) - and (Universe.random() < Universe.formSquadThreshold) - then - local map = chunk.map - local surface = map.surface - local squadPath, squadDirection - if (base.stateAI == BASE_AI_STATE_SIEGE) then - squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), - validSiegeSettlerLocation, - scoreSiegeSettlerLocation, - map) - else - squadPath, squadDirection = scoreNeighborsForResource(chunk, - getNeighborChunks(map, chunk.x, chunk.y), - validSettlerLocation, - scoreSettlerLocation, - map) - end - - if (squadPath ~= -1) then - local squadPosition = surface.find_non_colliding_position("biter-spawner", - positionFromDirectionAndChunk(squadDirection, - chunk, - 0.98), - CHUNK_SIZE, - 4, - true) - if squadPosition then - local squad = createSquad(squadPosition, map, nil, true, base) - - local scaledWaveSize = settlerWaveScaling() - Universe.formGroupCommand.group = squad.group - Universe.formCommand.unit_count = scaledWaveSize - local foundUnits = surface.set_multi_command(Universe.formCommand) - if (foundUnits > 0) then - base.sentExpansionGroups = base.sentExpansionGroups + 1 - - squad.base = base - local kamikazeThreshold = calculateKamikazeSettlerThreshold(foundUnits) - if base.stateAI == BASE_AI_STATE_SIEGE then - kamikazeThreshold = kamikazeThreshold * 2.5 - end - squad.kamikaze = Universe.random() < kamikazeThreshold - - Universe.builderCount = Universe.builderCount + 1 - modifyBaseUnitPoints(base, -AI_SETTLER_COST, "Settler", squadPosition.x, squadPosition.y) - Universe.groupNumberToSquad[squad.groupNumber] = squad - else - if (squad.group.valid) then - squad.group.destroy() - end - end - end - end - end -end - -function aiAttackWave.formVengenceSquad(chunk, base) - if (Universe.squadCount < Universe.AI_MAX_SQUAD_COUNT) - and ((base.unitPoints - AI_VENGENCE_SQUAD_COST) > 0) - and (Universe.random() < Universe.formSquadThreshold) - then - if (chunk[BASE_PHEROMONE] < 0.0001) or (chunk[PLAYER_PHEROMONE] < 0.0001) then - return - end - local map = chunk.map - - local surface = map.surface - local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), - validUnitGroupLocation, - scoreUnitGroupLocation, - map) - if (squadPath ~= -1) then - local squadPosition = surface.find_non_colliding_position("biter-spawner", - positionFromDirectionAndChunk(squadDirection, - chunk, - 0.98), - CHUNK_SIZE, - 4, - true) - if squadPosition then - local squad = createSquad(squadPosition, map, nil, false, base) - - squad.rabid = Universe.random() < 0.03 - - local scaledWaveSize = attackWaveScaling() - Universe.formGroupCommand.group = squad.group - Universe.formCommand.unit_count = scaledWaveSize - local foundUnits = surface.set_multi_command(Universe.formCommand) - if (foundUnits > 0) then - squad.base = base - squad.kamikaze = Universe.random() < calculateKamikazeSquadThreshold(foundUnits) - Universe.groupNumberToSquad[squad.groupNumber] = squad - Universe.squadCount = Universe.squadCount + 1 - modifyBaseUnitPoints(base, -AI_VENGENCE_SQUAD_COST, "Vengence", squadPosition.x, squadPosition.y) - else - if (squad.group.valid) then - squad.group.destroy() - end - end - end - end - end -end - -function aiAttackWave.formVengenceSettler(chunk, base) - if (Universe.builderCount < Universe.AI_MAX_BUILDER_COUNT) - and (base.sentExpansionGroups < base.maxExpansionGroups) - and ((base.unitPoints - AI_VENGENCE_SQUAD_COST) > 0) - and (Universe.random() < Universe.formSquadThreshold) - then - local map = chunk.map - local surface = map.surface - local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), - validUnitGroupLocation, - scoreUnitGroupLocation, - map) - if (squadPath ~= -1) then - local squadPosition = surface.find_non_colliding_position("biter-spawner", - positionFromDirectionAndChunk(squadDirection, - chunk, - 0.98), - CHUNK_SIZE, - 4, - true) - if squadPosition then - local squad = createSquad(squadPosition, map, nil, true, base) - - squad.rabid = Universe.random() < 0.03 - - local scaledWaveSize = settlerWaveScaling() - Universe.formGroupCommand.group = squad.group - Universe.formCommand.unit_count = scaledWaveSize - local foundUnits = surface.set_multi_command(Universe.formCommand) - if (foundUnits > 0) then - base.sentExpansionGroups = base.sentExpansionGroups + 1 - - squad.base = base - squad.kamikaze = Universe.random() < calculateKamikazeSettlerThreshold(foundUnits) - Universe.groupNumberToSquad[squad.groupNumber] = squad - Universe.builderCount = Universe.builderCount + 1 - modifyBaseUnitPoints(base, -AI_VENGENCE_SQUAD_COST, "Vengence Settlers", squadPosition.x, squadPosition.y) - else - if (squad.group.valid) then - squad.group.destroy() - end - end - end - end - end -end - -function aiAttackWave.formSquads(chunk, base) - if (Universe.squadCount < Universe.AI_MAX_SQUAD_COUNT) - and attackWaveValidCandidate(chunk, base) - and ((base.unitPoints - AI_SQUAD_COST) > 0) - and (Universe.random() < Universe.formSquadThreshold) - then - if (chunk[BASE_PHEROMONE] < 0.0001) or (chunk[PLAYER_PHEROMONE] < 0.0001) then - return - end - - local map = chunk.map - local surface = map.surface - local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), - validUnitGroupLocation, - scoreUnitGroupLocation, - map) - if (squadPath ~= -1) then - local squadPosition = surface.find_non_colliding_position("biter-spawner", - positionFromDirectionAndChunk(squadDirection, - chunk, - 0.98), - CHUNK_SIZE, - 4, - true) - if squadPosition then - local squad = createSquad(squadPosition, map, nil, false, base) - - squad.rabid = Universe.random() < 0.03 - - local scaledWaveSize = attackWaveScaling() - Universe.formGroupCommand.group = squad.group - Universe.formCommand.unit_count = scaledWaveSize - local foundUnits = surface.set_multi_command(Universe.formCommand) - if (foundUnits > 0) then - squad.base = base - squad.kamikaze = Universe.random() < calculateKamikazeSquadThreshold(foundUnits) - Universe.squadCount = Universe.squadCount + 1 - Universe.groupNumberToSquad[squad.groupNumber] = squad - if (base.stateAI == BASE_AI_STATE_AGGRESSIVE) then - base.sentAggressiveGroups = base.sentAggressiveGroups + 1 - end - modifyBaseUnitPoints(base, -AI_SQUAD_COST, "Squad", squadPosition.x, squadPosition.y) - else - if (squad.group.valid) then - squad.group.destroy() - end - end - end - end - end -end - -function aiAttackWave.init(universe) - Universe = universe -end - -aiAttackWaveG = aiAttackWave -return aiAttackWave diff --git a/libs/AIPlanning.lua b/libs/AIPlanning.lua deleted file mode 100644 index ccf122a..0000000 --- a/libs/AIPlanning.lua +++ /dev/null @@ -1,594 +0,0 @@ --- 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 aiPlanningG then - return aiPlanningG -end -local aiPlanning = {} - --- - -local universe - --- imports - -local constants = require("Constants") -local mathUtils = require("MathUtils") -local baseUtils = require("BaseUtils") - --- constants - -local UNIT_DEATH_POINT_COST = constants.UNIT_DEATH_POINT_COST - -local BASE_PROCESS_INTERVAL = constants.BASE_PROCESS_INTERVAL - -local BASE_GENERATION_STATE_ACTIVE = constants.BASE_GENERATION_STATE_ACTIVE -local BASE_GENERATION_STATE_DORMANT = constants.BASE_GENERATION_STATE_DORMANT - -local BASE_GENERATION_MIN_STATE_DURATION = constants.BASE_GENERATION_MIN_STATE_DURATION -local BASE_GENERATION_MAX_STATE_DURATION = constants.BASE_GENERATION_MAX_STATE_DURATION - -local TEMPERAMENT_RANGE_MAX = constants.TEMPERAMENT_RANGE_MAX -local TEMPERAMENT_RANGE_MIN = constants.TEMPERAMENT_RANGE_MIN -local TEMPERAMENT_DIVIDER = constants.TEMPERAMENT_DIVIDER -local NO_RETREAT_BASE_PERCENT = constants.NO_RETREAT_BASE_PERCENT -local NO_RETREAT_EVOLUTION_BONUS_MAX = constants.NO_RETREAT_EVOLUTION_BONUS_MAX - -local AI_UNIT_REFUND = constants.AI_UNIT_REFUND - -local AI_MAX_POINTS = constants.AI_MAX_POINTS - -local BASE_RALLY_CHANCE = constants.BASE_RALLY_CHANCE -local BONUS_RALLY_CHANCE = constants.BONUS_RALLY_CHANCE - -local RETREAT_MOVEMENT_PHEROMONE_LEVEL_MIN = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL_MIN -local RETREAT_MOVEMENT_PHEROMONE_LEVEL_MAX = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL_MAX -local MINIMUM_AI_POINTS = constants.MINIMUM_AI_POINTS - -local ACTIVE_NESTS_PER_AGGRESSIVE_GROUPS = constants.ACTIVE_NESTS_PER_AGGRESSIVE_GROUPS -local ALL_NESTS_PER_EXPANSION_GROUPS = constants.ALL_NESTS_PER_EXPANSION_GROUPS - -local BASE_AI_STATE_PEACEFUL = constants.BASE_AI_STATE_PEACEFUL -local BASE_AI_STATE_AGGRESSIVE = constants.BASE_AI_STATE_AGGRESSIVE -local BASE_AI_STATE_RAIDING = constants.BASE_AI_STATE_RAIDING -local BASE_AI_STATE_MIGRATING = constants.BASE_AI_STATE_MIGRATING -local BASE_AI_STATE_ONSLAUGHT = constants.BASE_AI_STATE_ONSLAUGHT -local BASE_AI_STATE_SIEGE = constants.BASE_AI_STATE_SIEGE -local AI_POINT_GENERATOR_AMOUNT = constants.AI_POINT_GENERATOR_AMOUNT - -local BASE_AI_MIN_STATE_DURATION = constants.BASE_AI_MIN_STATE_DURATION -local BASE_AI_MAX_STATE_DURATION = constants.BASE_AI_MAX_STATE_DURATION - - --- imported functions - -local randomTickEvent = mathUtils.randomTickEvent -local randomTickDuration = mathUtils.randomTickDuration - -local upgradeBaseBasedOnDamage = baseUtils.upgradeBaseBasedOnDamage -local modifyBaseUnitPoints = baseUtils.modifyBaseUnitPoints -local modifyBaseSpecialPoints = baseUtils.modifyBaseSpecialPoints - -local linearInterpolation = mathUtils.linearInterpolation - -local mFloor = math.floor - -local mMax = math.max -local mMin = math.min - -local mCeil = math.ceil - --- module code - -local function getTimeStringFromTick(tick) - - local tickToSeconds = tick / 60 - - local days = mFloor(tickToSeconds / 86400) - local hours = mFloor((tickToSeconds % 86400) / 3600) - local minutes = mFloor((tickToSeconds % 3600) / 60) - local seconds = mFloor(tickToSeconds % 60) - return days .. "d " .. hours .. "h " .. minutes .. "m " .. seconds .. "s" -end - -function aiPlanning.planning(evolutionLevel) - universe.evolutionLevel = evolutionLevel - local maxPoints = mMax(AI_MAX_POINTS * evolutionLevel, MINIMUM_AI_POINTS) - universe.maxPoints = maxPoints - - local maxOverflowPoints = maxPoints * 3 - universe.maxOverflowPoints = maxOverflowPoints - - local attackWaveMaxSize = universe.attackWaveMaxSize - universe.retreatThreshold = linearInterpolation(evolutionLevel, - RETREAT_MOVEMENT_PHEROMONE_LEVEL_MIN, - RETREAT_MOVEMENT_PHEROMONE_LEVEL_MAX) - universe.rallyThreshold = BASE_RALLY_CHANCE + (evolutionLevel * BONUS_RALLY_CHANCE) - universe.formSquadThreshold = mMax((0.35 * evolutionLevel), 0.1) - - universe.attackWaveSize = attackWaveMaxSize * (evolutionLevel ^ 1.4) - universe.attackWaveDeviation = (universe.attackWaveSize * 0.333) - universe.attackWaveUpperBound = universe.attackWaveSize + (universe.attackWaveSize * 0.35) - - if (universe.attackWaveSize < 1) then - universe.attackWaveSize = 2 - universe.attackWaveDeviation = 1 - universe.attackWaveUpperBound = 3 - end - - universe.settlerWaveSize = linearInterpolation(evolutionLevel ^ 1.66667, - universe.expansionMinSize, - universe.expansionMaxSize) - universe.settlerWaveDeviation = (universe.settlerWaveSize * 0.33) - - universe.settlerCooldown = randomTickDuration(universe.random, - universe.expansionMinTime, - mFloor(linearInterpolation(evolutionLevel ^ 1.66667, - universe.expansionMaxTime, - universe.expansionMinTime))) - - universe.unitRefundAmount = AI_UNIT_REFUND * evolutionLevel - universe.kamikazeThreshold = NO_RETREAT_BASE_PERCENT + (evolutionLevel * NO_RETREAT_EVOLUTION_BONUS_MAX) -end - -local function processBase(base, tick) - - base.maxAggressiveGroups = mCeil(base.activeNests / ACTIVE_NESTS_PER_AGGRESSIVE_GROUPS) - base.maxExpansionGroups = mCeil((base.activeNests + base.activeRaidNests) / ALL_NESTS_PER_EXPANSION_GROUPS) - - if (base.stateAI == BASE_AI_STATE_MIGRATING or base.stateAI == BASE_AI_STATE_SIEGE) - and base.resetExpensionGroupsTick <= tick - then - base.resetExpensionGroupsTick = tick + universe.settlerCooldown - base.sentExpansionGroups = 0 - end - - local points = (AI_POINT_GENERATOR_AMOUNT * universe.random()) + - (base.activeNests * 0.144) + - (AI_POINT_GENERATOR_AMOUNT * mMax(universe.evolutionLevel ^ 2.5, 0.1)) - - if (base.temperament == 0) or (base.temperament == 1) then - points = points + 24 - elseif (base.temperament < 0.20) or (base.temperament > 0.80) then - points = points + 14.4 - elseif (base.temperament < 0.35) or (base.temperament > 0.65) then - points = points + 9.6 - elseif (base.temperament < 0.45) or (base.temperament > 0.55) then - points = points + 4.8 - end - - if (base.stateAI == BASE_AI_STATE_ONSLAUGHT) then - points = points * 2 - end - - points = points * universe.aiPointsScaler - - local currentPoints = base.unitPoints - - if (currentPoints <= 0) then - currentPoints = 0 - end - - if (currentPoints < universe.maxPoints) then - modifyBaseUnitPoints(base, points, "Logic Cycle", base.x, base.y) - end - - if (base.points < universe.maxPoints) then - modifyBaseSpecialPoints(base, (points * 0.75), "Logic Cycle", base.x, base.y) - end - - if universe.NEW_ENEMIES then - local deathThreshold = 0 - local evolutionLevel = universe.evolutionLevel - if (evolutionLevel < universe.minimumAdaptationEvolution) then - base.deathEvents = 0 - elseif (evolutionLevel < 0.5) then - deathThreshold = 17000 - elseif (evolutionLevel < 0.7) then - deathThreshold = 34000 - elseif (evolutionLevel < 0.9) then - deathThreshold = 60000 - else - deathThreshold = 100000 - end - - deathThreshold = universe.adaptationModifier * deathThreshold - if ((base.deathEvents > deathThreshold) and (universe.random() > 0.95)) then - if (base.mutations < universe.MAX_BASE_MUTATIONS) then - if upgradeBaseBasedOnDamage(base) then - base.mutations = base.mutations + 1 - end - elseif (base.mutations == universe.MAX_BASE_MUTATIONS) then - local roll = universe.random() - if (roll < 0.001) then - base.mutations = 0 - if (universe.printBaseAdaptation) then - game.print({"description.rampant--adaptationResetDebugMessage", - base.x, - base.y, - base.mutations, - universe.MAX_BASE_MUTATIONS}) - end - elseif (roll > 0.999) then - base.mutations = base.mutations + 1 - if (universe.printBaseAdaptation) then - game.print({"description.rampant--adaptationFrozenDebugMessage", - base.x, - base.y}) - end - end - end - base.damagedBy = {} - base.deathEvents = 0 - end - else - base.damagedBy = {} - base.deathEvents = 0 - end - - if (base.stateGenerationTick <= tick) then - local roll = universe.random() - if (roll < 0.85) then - base.stateGeneration = BASE_GENERATION_STATE_ACTIVE - else - base.stateGeneration = BASE_GENERATION_STATE_DORMANT - end - base.stateGenerationTick = randomTickEvent(universe.random, - tick, - BASE_GENERATION_MIN_STATE_DURATION, - BASE_GENERATION_MAX_STATE_DURATION) - end - - base.tick = tick -end - -local function temperamentPlanner(base, evolutionLevel) - local destroyPlayerBuildings = base.destroyPlayerBuildings - local lostEnemyUnits = base.lostEnemyUnits - local lostEnemyBuilding = base.lostEnemyBuilding - local rocketLaunched = base.rocketLaunched - local builtEnemyBuilding = base.builtEnemyBuilding - local ionCannonBlasts = base.ionCannonBlasts - local artilleryBlasts = base.artilleryBlasts - local activeNests = base.activeNests - local activeRaidNests = base.activeRaidNests - - local currentTemperament = base.temperamentScore - local delta = 0 - - if activeNests > 0 then - local val = (5.76 * activeNests) - delta = delta + val - else - delta = delta - 5.553792 - end - - if destroyPlayerBuildings > 0 then - if currentTemperament > 0 then - delta = delta - (5.553792 * destroyPlayerBuildings) - else - delta = delta + (5.553792 * destroyPlayerBuildings) - end - end - - if activeRaidNests > 0 then - local val = (0.2304 * activeRaidNests) - delta = delta - val - else - delta = delta - 3.84 - end - - if lostEnemyUnits > 0 then - local multipler - if evolutionLevel < 0.3 then - multipler = 0.083328 - elseif evolutionLevel < 0.5 then - multipler = 0.041472 - elseif evolutionLevel < 0.7 then - multipler = 0.020736 - elseif evolutionLevel < 0.9 then - multipler = 0.010368 - elseif evolutionLevel < 0.9 then - multipler = 0.005184 - else - multipler = 0.002592 - end - local val = (multipler * lostEnemyUnits) - if (currentTemperament > 0) then - delta = delta - val - else - delta = delta + val - end - end - - if lostEnemyBuilding > 0 then - local val = (0.576 * lostEnemyBuilding) - if (currentTemperament > 0) then - delta = delta - val - else - delta = delta + val - end - end - - if builtEnemyBuilding > 0 then - local val = (0.261952 * builtEnemyBuilding) - if (currentTemperament > 0) then - delta = delta - val - else - delta = delta + val - end - else - delta = delta - 2.777088 - end - - if (rocketLaunched > 0) then - local val = (27.76 * rocketLaunched) - delta = delta + val - end - - if (ionCannonBlasts > 0) then - local val = (13.924864 * ionCannonBlasts) - delta = delta + val - end - - if (artilleryBlasts > 0) then - local val = (13.924864 * artilleryBlasts) - delta = delta + val - end - - delta = delta * universe.temperamentRateModifier - base.temperamentScore = mMin(TEMPERAMENT_RANGE_MAX, mMax(TEMPERAMENT_RANGE_MIN, currentTemperament + delta)) - base.temperament = ((base.temperamentScore + TEMPERAMENT_RANGE_MAX) * TEMPERAMENT_DIVIDER) - - if universe.debugTemperament then - local strConsole = "Rampant Stats:\nbaseId:".. base.id .. ", aN:" .. base.activeNests .. - ", aRN:" .. base.activeRaidNests .. ", dPB:" .. - base.destroyPlayerBuildings .. ", lEU:" .. base.lostEnemyUnits .. ", lEB:" .. - base.lostEnemyBuilding .. ", rL:" .. base.rocketLaunched .. ", bEB:" .. - base.builtEnemyBuilding .. ", iCB:" .. base.ionCannonBlasts .. ", aB:" .. - base.artilleryBlasts .. ", temp:" .. base.temperament .. ", tempScore:" .. base.temperamentScore .. - ", points:" .. base.points .. ", unitPoints:" .. base.unitPoints .. ", state:" .. - constants.STATE_ENGLISH[base.stateAI] .. ", surface:" .. base.map.surface.index .. " [" .. - base.map.surface.name .. "]" .. ", aS:" .. universe.squadCount .. ", aB:" .. universe.builderCount .. - ", atkSize:" .. universe.attackWaveSize .. ", stlSize:" .. universe.settlerWaveSize .. - ", formGroup:" .. universe.formSquadThreshold .. ", sAgg:".. base.sentAggressiveGroups .. - ", mAgg:" .. base.maxAggressiveGroups .. ", baseState:" .. base.stateGeneration .. ", sE:" - .. base.sentExpansionGroups .. ", mE:" .. base.maxExpansionGroups - game.print(strConsole) - print(strConsole) - end -end - -local function processState(base, tick) - - if (base.stateAITick > tick) or not universe.awake then - if (not universe.awake) and (tick >= universe.initialPeaceTime) then - universe.awake = true - if universe.printAwakenMessage then - game.print({"description.rampant--planetHasAwoken"}) - end - else - return - end - end - local roll = universe.random() - if (base.temperament < 0.05) then -- 0 - 0.05 - if universe.enabledMigration then - if (roll < 0.30) then - base.stateAI = BASE_AI_STATE_MIGRATING - elseif (roll < 0.50) and universe.raidAIToggle then - base.stateAI = BASE_AI_STATE_RAIDING - elseif universe.siegeAIToggle then - base.stateAI = BASE_AI_STATE_SIEGE - else - base.stateAI = BASE_AI_STATE_MIGRATING - end - else - if universe.raidAIToggle then - if (roll < 0.70) then - base.stateAI = BASE_AI_STATE_RAIDING - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - end - elseif (base.temperament < 0.20) then -- 0.05 - 0.2 - if (universe.enabledMigration) then - if (roll < 0.4) then - base.stateAI = BASE_AI_STATE_MIGRATING - elseif (roll < 0.55) and universe.raidAIToggle then - base.stateAI = BASE_AI_STATE_RAIDING - elseif universe.siegeAIToggle then - base.stateAI = BASE_AI_STATE_SIEGE - else - base.stateAI = BASE_AI_STATE_MIGRATING - end - else - if universe.raidAIToggle then - if (roll < 0.40) then - base.stateAI = BASE_AI_STATE_AGGRESSIVE - else - base.stateAI = BASE_AI_STATE_RAIDING - end - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - end - elseif (base.temperament < 0.4) then -- 0.2 - 0.4 - if (universe.enabledMigration) then - if (roll < 0.2) and universe.raidAIToggle then - base.stateAI = BASE_AI_STATE_RAIDING - elseif (roll < 0.2) then - base.stateAI = BASE_AI_STATE_AGGRESSIVE - elseif (roll < 0.8) then - base.stateAI = BASE_AI_STATE_MIGRATING - elseif universe.peacefulAIToggle then - base.stateAI = BASE_AI_STATE_PEACEFUL - else - base.stateAI = BASE_AI_STATE_MIGRATING - end - else - if (roll < 0.3) then - base.stateAI = BASE_AI_STATE_AGGRESSIVE - elseif (roll < 0.6) and universe.raidAIToggle then - base.stateAI = BASE_AI_STATE_RAIDING - elseif (roll < 0.6) then - base.stateAI = BASE_AI_STATE_AGGRESSIVE - elseif universe.peacefulAIToggle then - base.stateAI = BASE_AI_STATE_PEACEFUL - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - end - elseif (base.temperament < 0.6) then -- 0.4 - 0.6 - if (roll < 0.4) then - base.stateAI = BASE_AI_STATE_AGGRESSIVE - elseif (roll < 0.5) and universe.raidAIToggle then - base.stateAI = BASE_AI_STATE_RAIDING - elseif (roll < 0.75) and universe.peacefulAIToggle then - base.stateAI = BASE_AI_STATE_PEACEFUL - else - if universe.enabledMigration then - base.stateAI = BASE_AI_STATE_MIGRATING - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - end - elseif (base.temperament < 0.8) then -- 0.6 - 0.8 - if (roll < 0.4) then - base.stateAI = BASE_AI_STATE_AGGRESSIVE - elseif (roll < 0.6) then - base.stateAI = BASE_AI_STATE_ONSLAUGHT - elseif (roll < 0.8) then - base.stateAI = BASE_AI_STATE_RAIDING - elseif universe.peacefulAIToggle then - base.stateAI = BASE_AI_STATE_PEACEFUL - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - elseif (base.temperament < 0.95) then -- 0.8 - 0.95 - if (universe.enabledMigration and universe.raidAIToggle) then - if (roll < 0.20) and universe.siegeAIToggle then - base.stateAI = BASE_AI_STATE_SIEGE - elseif (roll < 0.45) then - base.stateAI = BASE_AI_STATE_RAIDING - elseif (roll < 0.85) then - base.stateAI = BASE_AI_STATE_ONSLAUGHT - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - elseif (universe.enabledMigration) then - if (roll < 0.20) and universe.siegeAIToggle then - base.stateAI = BASE_AI_STATE_SIEGE - elseif (roll < 0.75) then - base.stateAI = BASE_AI_STATE_ONSLAUGHT - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - elseif (universe.raidAIToggle) then - if (roll < 0.45) then - base.stateAI = BASE_AI_STATE_ONSLAUGHT - elseif (roll < 0.75) then - base.stateAI = BASE_AI_STATE_RAIDING - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - else - if (roll < 0.65) then - base.stateAI = BASE_AI_STATE_ONSLAUGHT - else - base.stateAI = BASE_AI_STATE_AGGRESSIVE - end - end - else - if (universe.enabledMigration and universe.raidAIToggle) then - if (roll < 0.30) and universe.siegeAIToggle then - base.stateAI = BASE_AI_STATE_SIEGE - elseif (roll < 0.65) then - base.stateAI = BASE_AI_STATE_RAIDING - else - base.stateAI = BASE_AI_STATE_ONSLAUGHT - end - elseif (universe.enabledMigration) then - if (roll < 0.30) and universe.siegeAIToggle then - base.stateAI = BASE_AI_STATE_SIEGE - else - base.stateAI = BASE_AI_STATE_ONSLAUGHT - end - elseif (universe.raidAIToggle) then - if (roll < 0.45) then - base.stateAI = BASE_AI_STATE_ONSLAUGHT - else - base.stateAI = BASE_AI_STATE_RAIDING - end - else - base.stateAI = BASE_AI_STATE_ONSLAUGHT - end - end - - local remainingUnits = base.lostEnemyUnits % 20 - if remainingUnits ~= 0 then - modifyBaseUnitPoints(base, -(remainingUnits*UNIT_DEATH_POINT_COST), remainingUnits.." Units Lost") - end - - base.destroyPlayerBuildings = 0 - base.lostEnemyUnits = 0 - base.lostEnemyBuilding = 0 - base.rocketLaunched = 0 - base.builtEnemyBuilding = 0 - base.ionCannonBlasts = 0 - base.artilleryBlasts = 0 - - base.stateAITick = randomTickEvent(universe.random, tick, BASE_AI_MIN_STATE_DURATION, BASE_AI_MAX_STATE_DURATION) - - if universe.printAIStateChanges then - game.print(base.id .. ": AI is now: " .. constants.STATE_ENGLISH[base.stateAI] .. ", Next state change is in " - .. string.format("%.2f", (base.stateAITick - tick) / (60*60)) .. " minutes @ " .. - getTimeStringFromTick(base.stateAITick) .. " playtime") - end - -end - -function aiPlanning.processBaseAIs(tick) - local baseId = universe.processBaseAIIterator - local base - if not baseId then - baseId, base = next(universe.bases, nil) - else - base = universe.bases[baseId] - end - if not baseId then - universe.processBaseAIIterator = nil - return - else - universe.processBaseAIIterator = next(universe.bases, baseId) - if (tick - base.tick) <= BASE_PROCESS_INTERVAL then - return - end - temperamentPlanner(base, universe.evolutionLevel) - processState(base, tick) - processBase(base, tick) - end -end - -function aiPlanning.init(universeGlobal) - universe = universeGlobal -end - -aiPlanningG = aiPlanning -return aiPlanning diff --git a/libs/AIPredicates.lua b/libs/AIPredicates.lua deleted file mode 100644 index 76ffaa6..0000000 --- a/libs/AIPredicates.lua +++ /dev/null @@ -1,91 +0,0 @@ --- 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 (aiPredicatesG) then - return aiPredicatesG -end -local aiPredicates = {} - --- - -local universe - --- imports - -local constants = require("Constants") - --- constants - -local BASE_AI_STATE_RAIDING = constants.BASE_AI_STATE_RAIDING -local BASE_AI_STATE_AGGRESSIVE = constants.BASE_AI_STATE_AGGRESSIVE -local BASE_AI_STATE_MIGRATING = constants.BASE_AI_STATE_MIGRATING -local BASE_AI_STATE_SIEGE = constants.BASE_AI_STATE_SIEGE -local BASE_AI_STATE_ONSLAUGHT = constants.BASE_AI_STATE_ONSLAUGHT - --- imported functions - --- module code - -function aiPredicates.canAttack(map, base) - local isAggressive = ((base.stateAI == BASE_AI_STATE_AGGRESSIVE) - and (base.sentAggressiveGroups < base.maxAggressiveGroups)) - local isRaiding = (base.stateAI == BASE_AI_STATE_RAIDING) - local isOnslaught = (base.stateAI == BASE_AI_STATE_ONSLAUGHT) - local isRaidSieging = universe.raidAIToggle - and (base.stateAI == BASE_AI_STATE_SIEGE) - and (base.sentExpansionGroups >= base.maxExpansionGroups) - local goodAI = isAggressive or isRaiding or isOnslaught or isRaidSieging - if not goodAI then - return false - end - local surface = map.surface - if surface.peaceful_mode then - return false - end - local nocturalMode = universe.aiNocturnalMode - local noctural = (not nocturalMode) or (nocturalMode and surface.darkness > 0.65) - if not noctural then - return false - end - return true -end - -function aiPredicates.canMigrate(map, base) - local badAIState = (base.stateAI ~= BASE_AI_STATE_MIGRATING) and (base.stateAI ~= BASE_AI_STATE_SIEGE) - if badAIState then - return false - end - if not universe.expansion then - return false - end - local surface = map.surface - if surface.peaceful_mode then - return false - end - local nocturalMode = universe.aiNocturnalMode - local noctural = (not nocturalMode) or (nocturalMode and surface.darkness > 0.65) - if not noctural then - return false - end - return true -end - -function aiPredicates.init(universeGlobal) - universe = universeGlobal -end - -aiPredicatesG = aiPredicates -return aiPredicates diff --git a/libs/BaseUtils.lua b/libs/BaseUtils.lua index ef7871f..e87b3a7 100644 --- a/libs/BaseUtils.lua +++ b/libs/BaseUtils.lua @@ -25,57 +25,99 @@ local Universe -- imports -local stringUtils = require("StringUtils") -local mathUtils = require("MathUtils") -local constants = require("Constants") -local chunkPropertyUtils = require("ChunkPropertyUtils") -local mapUtils = require("MapUtils") -local queryUtils = require("QueryUtils") +local Utils = require("Utils") +local MathUtils = require("MathUtils") +local Constants = require("Constants") +local MapUtils = require("MapUtils") --- constants +-- Constants -local TIERS = constants.TIERS -local EVO_TO_TIER_MAPPING = constants.EVO_TO_TIER_MAPPING -local PROXY_ENTITY_LOOKUP = constants.PROXY_ENTITY_LOOKUP -local BUILDING_HIVE_TYPE_LOOKUP = constants.BUILDING_HIVE_TYPE_LOOKUP -local COST_LOOKUP = constants.COST_LOOKUP -local UPGRADE_LOOKUP = constants.UPGRADE_LOOKUP +local TIERS = Constants.TIERS +local EVO_TO_TIER_MAPPING = Constants.EVO_TO_TIER_MAPPING +local PROXY_ENTITY_LOOKUP = Constants.PROXY_ENTITY_LOOKUP +local BUILDING_HIVE_TYPE_LOOKUP = Constants.BUILDING_HIVE_TYPE_LOOKUP +local COST_LOOKUP = Constants.COST_LOOKUP +local UPGRADE_LOOKUP = Constants.UPGRADE_LOOKUP -local ENEMY_ALIGNMENT_LOOKUP = constants.ENEMY_ALIGNMENT_LOOKUP +local ENEMY_ALIGNMENT_LOOKUP = Constants.ENEMY_ALIGNMENT_LOOKUP -local EVOLUTION_TABLE_ALIGNMENT = constants.EVOLUTION_TABLE_ALIGNMENT -local BUILDING_EVOLVE_LOOKUP = constants.BUILDING_EVOLVE_LOOKUP +local EVOLUTION_TABLE_ALIGNMENT = Constants.EVOLUTION_TABLE_ALIGNMENT +local BUILDING_EVOLVE_LOOKUP = Constants.BUILDING_EVOLVE_LOOKUP -local MINIMUM_BUILDING_COST = constants.MINIMUM_BUILDING_COST -local FACTION_MUTATION_MAPPING = constants.FACTION_MUTATION_MAPPING +local BASE_AI_STATE_RAIDING = Constants.BASE_AI_STATE_RAIDING +local BASE_AI_STATE_AGGRESSIVE = Constants.BASE_AI_STATE_AGGRESSIVE +local BASE_AI_STATE_MIGRATING = Constants.BASE_AI_STATE_MIGRATING +local BASE_AI_STATE_SIEGE = Constants.BASE_AI_STATE_SIEGE +local BASE_AI_STATE_ONSLAUGHT = Constants.BASE_AI_STATE_ONSLAUGHT -local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER +local MINIMUM_BUILDING_COST = Constants.MINIMUM_BUILDING_COST +local FACTION_MUTATION_MAPPING = Constants.FACTION_MUTATION_MAPPING -local FACTIONS_BY_DAMAGE_TYPE = constants.FACTIONS_BY_DAMAGE_TYPE +local MAGIC_MAXIMUM_NUMBER = Constants.MAGIC_MAXIMUM_NUMBER -local BASE_GENERATION_STATE_ACTIVE = constants.BASE_GENERATION_STATE_ACTIVE +local FACTIONS_BY_DAMAGE_TYPE = Constants.FACTIONS_BY_DAMAGE_TYPE -local BASE_DISTANCE_THRESHOLD = constants.BASE_DISTANCE_THRESHOLD -local BASE_DISTANCE_LEVEL_BONUS = constants.BASE_DISTANCE_LEVEL_BONUS -local BASE_DISTANCE_TO_EVO_INDEX = constants.BASE_DISTANCE_TO_EVO_INDEX +local BASE_GENERATION_STATE_ACTIVE = Constants.BASE_GENERATION_STATE_ACTIVE -local CHUNK_SIZE = constants.CHUNK_SIZE +local BASE_DISTANCE_THRESHOLD = Constants.BASE_DISTANCE_THRESHOLD +local BASE_DISTANCE_LEVEL_BONUS = Constants.BASE_DISTANCE_LEVEL_BONUS +local BASE_DISTANCE_TO_EVO_INDEX = Constants.BASE_DISTANCE_TO_EVO_INDEX -local BASE_AI_STATE_PEACEFUL = constants.BASE_AI_STATE_PEACEFUL +local CHUNK_SIZE = Constants.CHUNK_SIZE + +local BASE_AI_STATE_PEACEFUL = Constants.BASE_AI_STATE_PEACEFUL + +local BASE_RALLY_CHANCE = Constants.BASE_RALLY_CHANCE +local BONUS_RALLY_CHANCE = Constants.BONUS_RALLY_CHANCE + +local RETREAT_MOVEMENT_PHEROMONE_LEVEL_MIN = Constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL_MIN +local RETREAT_MOVEMENT_PHEROMONE_LEVEL_MAX = Constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL_MAX +local MINIMUM_AI_POINTS = Constants.MINIMUM_AI_POINTS + +local AI_POINT_GENERATOR_AMOUNT = Constants.AI_POINT_GENERATOR_AMOUNT + +local BASE_AI_MIN_STATE_DURATION = Constants.BASE_AI_MIN_STATE_DURATION +local BASE_AI_MAX_STATE_DURATION = Constants.BASE_AI_MAX_STATE_DURATION +local NO_RETREAT_BASE_PERCENT = Constants.NO_RETREAT_BASE_PERCENT +local NO_RETREAT_EVOLUTION_BONUS_MAX = Constants.NO_RETREAT_EVOLUTION_BONUS_MAX + +local AI_UNIT_REFUND = Constants.AI_UNIT_REFUND +local AI_MAX_POINTS = Constants.AI_MAX_POINTS +local BASE_GENERATION_MIN_STATE_DURATION = Constants.BASE_GENERATION_MIN_STATE_DURATION +local BASE_GENERATION_MAX_STATE_DURATION = Constants.BASE_GENERATION_MAX_STATE_DURATION +local BASE_GENERATION_STATE_DORMANT = Constants.BASE_GENERATION_STATE_DORMANT +local STATE_ENGLISH = Constants.STATE_ENGLISH + +local ACTIVE_NESTS_PER_AGGRESSIVE_GROUPS = Constants.ACTIVE_NESTS_PER_AGGRESSIVE_GROUPS +local ALL_NESTS_PER_EXPANSION_GROUPS = Constants.ALL_NESTS_PER_EXPANSION_GROUPS +local TEMPERAMENT_RANGE_MAX = Constants.TEMPERAMENT_RANGE_MAX +local TEMPERAMENT_RANGE_MIN = Constants.TEMPERAMENT_RANGE_MIN +local TEMPERAMENT_DIVIDER = Constants.TEMPERAMENT_DIVIDER +local UNIT_DEATH_POINT_COST = Constants.UNIT_DEATH_POINT_COST +local BASE_PROCESS_INTERVAL = Constants.BASE_PROCESS_INTERVAL -- imported functions -local isMember = stringUtils.isMember +local getTimeStringFromTick = Utils.getTimeStringFromTick -local setPositionXYInQuery = queryUtils.setPositionXYInQuery +local randomTickEvent = MathUtils.randomTickEvent +local linearInterpolation = MathUtils.linearInterpolation +local randomTickDuration = MathUtils.randomTickDuration -local euclideanDistancePoints = mathUtils.euclideanDistancePoints +local isMember = Utils.isMember -local getChunkByPosition = mapUtils.getChunkByPosition +local setPositionXYInQuery = Utils.setPositionXYInQuery -local gaussianRandomRangeRG = mathUtils.gaussianRandomRangeRG +local euclideanDistancePoints = MathUtils.euclideanDistancePoints + +local getChunkByPosition = MapUtils.getChunkByPosition + +local gaussianRandomRangeRG = MathUtils.gaussianRandomRangeRG local mFloor = math.floor +local mCeil = math.ceil + +local tableSize = table_size local tableRemove = table.remove local mMin = math.min @@ -303,6 +345,50 @@ function BaseUtils.recycleBases() end end +function BaseUtils.canAttack(base) + local isAggressive = ((base.stateAI == BASE_AI_STATE_AGGRESSIVE) + and (base.sentAggressiveGroups < base.maxAggressiveGroups)) + local isRaiding = (base.stateAI == BASE_AI_STATE_RAIDING) + local isOnslaught = (base.stateAI == BASE_AI_STATE_ONSLAUGHT) + local isRaidSieging = Universe.raidAIToggle + and (base.stateAI == BASE_AI_STATE_SIEGE) + and (base.sentExpansionGroups >= base.maxExpansionGroups) + local goodAI = isAggressive or isRaiding or isOnslaught or isRaidSieging + if not goodAI then + return false + end + local surface = base.map.surface + if surface.peaceful_mode then + return false + end + local nocturalMode = Universe.aiNocturnalMode + local noctural = (not nocturalMode) or (nocturalMode and surface.darkness > 0.65) + if not noctural then + return false + end + return true +end + +function BaseUtils.canMigrate(base) + local badAIState = (base.stateAI ~= BASE_AI_STATE_MIGRATING) and (base.stateAI ~= BASE_AI_STATE_SIEGE) + if badAIState then + return false + end + if not Universe.expansion then + return false + end + local surface = base.map.surface + if surface.peaceful_mode then + return false + end + local nocturalMode = Universe.aiNocturnalMode + local noctural = (not nocturalMode) or (nocturalMode and surface.darkness > 0.65) + if not noctural then + return false + end + return true +end + function BaseUtils.queueUpgrade(entity, base, disPos, evolve, register, timeDelay) Universe.pendingUpgrades[entity.unit_number] = { ["position"] = disPos, @@ -402,7 +488,7 @@ local function pickMutationFromDamageType(damageType, roll, base) Universe.MAX_BASE_MUTATIONS}) end end - local alignmentCount = table_size(base.alignmentHistory) + local alignmentCount = tableSize(base.alignmentHistory) while (alignmentCount > Universe.MAX_BASE_ALIGNMENT_HISTORY) do tableRemove(base.alignmentHistory, 1) alignmentCount = alignmentCount - 1 @@ -604,6 +690,488 @@ function BaseUtils.modifyBaseSpecialPoints(base, points, tag, x, y) end end +function BaseUtils.planning(evolutionLevel) + Universe.evolutionLevel = evolutionLevel + local maxPoints = mMax(AI_MAX_POINTS * evolutionLevel, MINIMUM_AI_POINTS) + Universe.maxPoints = maxPoints + + local maxOverflowPoints = maxPoints * 3 + Universe.maxOverflowPoints = maxOverflowPoints + + local attackWaveMaxSize = Universe.attackWaveMaxSize + Universe.retreatThreshold = linearInterpolation(evolutionLevel, + RETREAT_MOVEMENT_PHEROMONE_LEVEL_MIN, + RETREAT_MOVEMENT_PHEROMONE_LEVEL_MAX) + Universe.rallyThreshold = BASE_RALLY_CHANCE + (evolutionLevel * BONUS_RALLY_CHANCE) + Universe.formSquadThreshold = mMax((0.35 * evolutionLevel), 0.1) + + Universe.attackWaveSize = attackWaveMaxSize * (evolutionLevel ^ 1.4) + Universe.attackWaveDeviation = (Universe.attackWaveSize * 0.333) + Universe.attackWaveUpperBound = Universe.attackWaveSize + (Universe.attackWaveSize * 0.35) + + if (Universe.attackWaveSize < 1) then + Universe.attackWaveSize = 2 + Universe.attackWaveDeviation = 1 + Universe.attackWaveUpperBound = 3 + end + + Universe.settlerWaveSize = linearInterpolation(evolutionLevel ^ 1.66667, + Universe.expansionMinSize, + Universe.expansionMaxSize) + Universe.settlerWaveDeviation = (Universe.settlerWaveSize * 0.33) + + Universe.settlerCooldown = randomTickDuration(Universe.random, + Universe.expansionMinTime, + mFloor(linearInterpolation(evolutionLevel ^ 1.66667, + Universe.expansionMaxTime, + Universe.expansionMinTime))) + + Universe.unitRefundAmount = AI_UNIT_REFUND * evolutionLevel + Universe.kamikazeThreshold = NO_RETREAT_BASE_PERCENT + (evolutionLevel * NO_RETREAT_EVOLUTION_BONUS_MAX) +end + +local function processBase(base, tick) + + base.maxAggressiveGroups = mCeil(base.activeNests / ACTIVE_NESTS_PER_AGGRESSIVE_GROUPS) + base.maxExpansionGroups = mCeil((base.activeNests + base.activeRaidNests) / ALL_NESTS_PER_EXPANSION_GROUPS) + + if (base.stateAI == BASE_AI_STATE_MIGRATING or base.stateAI == BASE_AI_STATE_SIEGE) + and base.resetExpensionGroupsTick <= tick + then + base.resetExpensionGroupsTick = tick + Universe.settlerCooldown + base.sentExpansionGroups = 0 + end + + local points = (AI_POINT_GENERATOR_AMOUNT * Universe.random()) + + (base.activeNests * 0.144) + + (AI_POINT_GENERATOR_AMOUNT * mMax(Universe.evolutionLevel ^ 2.5, 0.1)) + + if (base.temperament == 0) or (base.temperament == 1) then + points = points + 24 + elseif (base.temperament < 0.20) or (base.temperament > 0.80) then + points = points + 14.4 + elseif (base.temperament < 0.35) or (base.temperament > 0.65) then + points = points + 9.6 + elseif (base.temperament < 0.45) or (base.temperament > 0.55) then + points = points + 4.8 + end + + if (base.stateAI == BASE_AI_STATE_ONSLAUGHT) then + points = points * 2 + end + + points = points * Universe.aiPointsScaler + + local currentPoints = base.unitPoints + + if (currentPoints <= 0) then + currentPoints = 0 + end + + if (currentPoints < Universe.maxPoints) then + BaseUtils.modifyBaseUnitPoints(base, points, "Logic Cycle", base.x, base.y) + end + + if (base.points < Universe.maxPoints) then + BaseUtils.modifyBaseSpecialPoints(base, (points * 0.75), "Logic Cycle", base.x, base.y) + end + + if Universe.NEW_ENEMIES then + local deathThreshold = 0 + local evolutionLevel = Universe.evolutionLevel + if (evolutionLevel < Universe.minimumAdaptationEvolution) then + base.deathEvents = 0 + elseif (evolutionLevel < 0.5) then + deathThreshold = 17000 + elseif (evolutionLevel < 0.7) then + deathThreshold = 34000 + elseif (evolutionLevel < 0.9) then + deathThreshold = 60000 + else + deathThreshold = 100000 + end + + deathThreshold = Universe.adaptationModifier * deathThreshold + if ((base.deathEvents > deathThreshold) and (Universe.random() > 0.95)) then + if (base.mutations < Universe.MAX_BASE_MUTATIONS) then + if BaseUtils.upgradeBaseBasedOnDamage(base) then + base.mutations = base.mutations + 1 + end + elseif (base.mutations == Universe.MAX_BASE_MUTATIONS) then + local roll = Universe.random() + if (roll < 0.001) then + base.mutations = 0 + if (Universe.printBaseAdaptation) then + game.print({"description.rampant--adaptationResetDebugMessage", + base.x, + base.y, + base.mutations, + Universe.MAX_BASE_MUTATIONS}) + end + elseif (roll > 0.999) then + base.mutations = base.mutations + 1 + if (Universe.printBaseAdaptation) then + game.print({"description.rampant--adaptationFrozenDebugMessage", + base.x, + base.y}) + end + end + end + base.damagedBy = {} + base.deathEvents = 0 + end + else + base.damagedBy = {} + base.deathEvents = 0 + end + + if (base.stateGenerationTick <= tick) then + local roll = Universe.random() + if (roll < 0.85) then + base.stateGeneration = BASE_GENERATION_STATE_ACTIVE + else + base.stateGeneration = BASE_GENERATION_STATE_DORMANT + end + base.stateGenerationTick = randomTickEvent(Universe.random, + tick, + BASE_GENERATION_MIN_STATE_DURATION, + BASE_GENERATION_MAX_STATE_DURATION) + end + + base.tick = tick +end + +local function temperamentPlanner(base, evolutionLevel) + local destroyPlayerBuildings = base.destroyPlayerBuildings + local lostEnemyUnits = base.lostEnemyUnits + local lostEnemyBuilding = base.lostEnemyBuilding + local rocketLaunched = base.rocketLaunched + local builtEnemyBuilding = base.builtEnemyBuilding + local ionCannonBlasts = base.ionCannonBlasts + local artilleryBlasts = base.artilleryBlasts + local activeNests = base.activeNests + local activeRaidNests = base.activeRaidNests + + local currentTemperament = base.temperamentScore + local delta = 0 + + if activeNests > 0 then + local val = (5.76 * activeNests) + delta = delta + val + else + delta = delta - 5.553792 + end + + if destroyPlayerBuildings > 0 then + if currentTemperament > 0 then + delta = delta - (5.553792 * destroyPlayerBuildings) + else + delta = delta + (5.553792 * destroyPlayerBuildings) + end + end + + if activeRaidNests > 0 then + local val = (0.2304 * activeRaidNests) + delta = delta - val + else + delta = delta - 3.84 + end + + if lostEnemyUnits > 0 then + local multipler + if evolutionLevel < 0.3 then + multipler = 0.083328 + elseif evolutionLevel < 0.5 then + multipler = 0.041472 + elseif evolutionLevel < 0.7 then + multipler = 0.020736 + elseif evolutionLevel < 0.9 then + multipler = 0.010368 + elseif evolutionLevel < 0.9 then + multipler = 0.005184 + else + multipler = 0.002592 + end + local val = (multipler * lostEnemyUnits) + if (currentTemperament > 0) then + delta = delta - val + else + delta = delta + val + end + end + + if lostEnemyBuilding > 0 then + local val = (0.576 * lostEnemyBuilding) + if (currentTemperament > 0) then + delta = delta - val + else + delta = delta + val + end + end + + if builtEnemyBuilding > 0 then + local val = (0.261952 * builtEnemyBuilding) + if (currentTemperament > 0) then + delta = delta - val + else + delta = delta + val + end + else + delta = delta - 2.777088 + end + + if (rocketLaunched > 0) then + local val = (27.76 * rocketLaunched) + delta = delta + val + end + + if (ionCannonBlasts > 0) then + local val = (13.924864 * ionCannonBlasts) + delta = delta + val + end + + if (artilleryBlasts > 0) then + local val = (13.924864 * artilleryBlasts) + delta = delta + val + end + + delta = delta * Universe.temperamentRateModifier + base.temperamentScore = mMin(TEMPERAMENT_RANGE_MAX, mMax(TEMPERAMENT_RANGE_MIN, currentTemperament + delta)) + base.temperament = ((base.temperamentScore + TEMPERAMENT_RANGE_MAX) * TEMPERAMENT_DIVIDER) + + if Universe.debugTemperament then + local strConsole = "Rampant Stats:\nbaseId:".. base.id .. ", aN:" .. base.activeNests .. + ", aRN:" .. base.activeRaidNests .. ", dPB:" .. + base.destroyPlayerBuildings .. ", lEU:" .. base.lostEnemyUnits .. ", lEB:" .. + base.lostEnemyBuilding .. ", rL:" .. base.rocketLaunched .. ", bEB:" .. + base.builtEnemyBuilding .. ", iCB:" .. base.ionCannonBlasts .. ", aB:" .. + base.artilleryBlasts .. ", temp:" .. base.temperament .. ", tempScore:" .. base.temperamentScore .. + ", points:" .. base.points .. ", unitPoints:" .. base.unitPoints .. ", state:" .. + STATE_ENGLISH[base.stateAI] .. ", surface:" .. base.map.surface.index .. " [" .. + base.map.surface.name .. "]" .. ", aS:" .. Universe.squadCount .. ", aB:" .. Universe.builderCount .. + ", atkSize:" .. Universe.attackWaveSize .. ", stlSize:" .. Universe.settlerWaveSize .. + ", formGroup:" .. Universe.formSquadThreshold .. ", sAgg:".. base.sentAggressiveGroups .. + ", mAgg:" .. base.maxAggressiveGroups .. ", baseState:" .. base.stateGeneration .. ", sE:" + .. base.sentExpansionGroups .. ", mE:" .. base.maxExpansionGroups + game.print(strConsole) + print(strConsole) + end +end + +local function processState(base, tick) + + if (base.stateAITick > tick) or not Universe.awake then + if (not Universe.awake) and (tick >= Universe.initialPeaceTime) then + Universe.awake = true + if Universe.printAwakenMessage then + game.print({"description.rampant--planetHasAwoken"}) + end + else + return + end + end + local roll = Universe.random() + if (base.temperament < 0.05) then -- 0 - 0.05 + if Universe.enabledMigration then + if (roll < 0.30) then + base.stateAI = BASE_AI_STATE_MIGRATING + elseif (roll < 0.50) and Universe.raidAIToggle then + base.stateAI = BASE_AI_STATE_RAIDING + elseif Universe.siegeAIToggle then + base.stateAI = BASE_AI_STATE_SIEGE + else + base.stateAI = BASE_AI_STATE_MIGRATING + end + else + if Universe.raidAIToggle then + if (roll < 0.70) then + base.stateAI = BASE_AI_STATE_RAIDING + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + end + elseif (base.temperament < 0.20) then -- 0.05 - 0.2 + if (Universe.enabledMigration) then + if (roll < 0.4) then + base.stateAI = BASE_AI_STATE_MIGRATING + elseif (roll < 0.55) and Universe.raidAIToggle then + base.stateAI = BASE_AI_STATE_RAIDING + elseif Universe.siegeAIToggle then + base.stateAI = BASE_AI_STATE_SIEGE + else + base.stateAI = BASE_AI_STATE_MIGRATING + end + else + if Universe.raidAIToggle then + if (roll < 0.40) then + base.stateAI = BASE_AI_STATE_AGGRESSIVE + else + base.stateAI = BASE_AI_STATE_RAIDING + end + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + end + elseif (base.temperament < 0.4) then -- 0.2 - 0.4 + if (Universe.enabledMigration) then + if (roll < 0.2) and Universe.raidAIToggle then + base.stateAI = BASE_AI_STATE_RAIDING + elseif (roll < 0.2) then + base.stateAI = BASE_AI_STATE_AGGRESSIVE + elseif (roll < 0.8) then + base.stateAI = BASE_AI_STATE_MIGRATING + elseif Universe.peacefulAIToggle then + base.stateAI = BASE_AI_STATE_PEACEFUL + else + base.stateAI = BASE_AI_STATE_MIGRATING + end + else + if (roll < 0.3) then + base.stateAI = BASE_AI_STATE_AGGRESSIVE + elseif (roll < 0.6) and Universe.raidAIToggle then + base.stateAI = BASE_AI_STATE_RAIDING + elseif (roll < 0.6) then + base.stateAI = BASE_AI_STATE_AGGRESSIVE + elseif Universe.peacefulAIToggle then + base.stateAI = BASE_AI_STATE_PEACEFUL + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + end + elseif (base.temperament < 0.6) then -- 0.4 - 0.6 + if (roll < 0.4) then + base.stateAI = BASE_AI_STATE_AGGRESSIVE + elseif (roll < 0.5) and Universe.raidAIToggle then + base.stateAI = BASE_AI_STATE_RAIDING + elseif (roll < 0.75) and Universe.peacefulAIToggle then + base.stateAI = BASE_AI_STATE_PEACEFUL + else + if Universe.enabledMigration then + base.stateAI = BASE_AI_STATE_MIGRATING + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + end + elseif (base.temperament < 0.8) then -- 0.6 - 0.8 + if (roll < 0.4) then + base.stateAI = BASE_AI_STATE_AGGRESSIVE + elseif (roll < 0.6) then + base.stateAI = BASE_AI_STATE_ONSLAUGHT + elseif (roll < 0.8) then + base.stateAI = BASE_AI_STATE_RAIDING + elseif Universe.peacefulAIToggle then + base.stateAI = BASE_AI_STATE_PEACEFUL + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + elseif (base.temperament < 0.95) then -- 0.8 - 0.95 + if (Universe.enabledMigration and Universe.raidAIToggle) then + if (roll < 0.20) and Universe.siegeAIToggle then + base.stateAI = BASE_AI_STATE_SIEGE + elseif (roll < 0.45) then + base.stateAI = BASE_AI_STATE_RAIDING + elseif (roll < 0.85) then + base.stateAI = BASE_AI_STATE_ONSLAUGHT + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + elseif (Universe.enabledMigration) then + if (roll < 0.20) and Universe.siegeAIToggle then + base.stateAI = BASE_AI_STATE_SIEGE + elseif (roll < 0.75) then + base.stateAI = BASE_AI_STATE_ONSLAUGHT + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + elseif (Universe.raidAIToggle) then + if (roll < 0.45) then + base.stateAI = BASE_AI_STATE_ONSLAUGHT + elseif (roll < 0.75) then + base.stateAI = BASE_AI_STATE_RAIDING + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + else + if (roll < 0.65) then + base.stateAI = BASE_AI_STATE_ONSLAUGHT + else + base.stateAI = BASE_AI_STATE_AGGRESSIVE + end + end + else + if (Universe.enabledMigration and Universe.raidAIToggle) then + if (roll < 0.30) and Universe.siegeAIToggle then + base.stateAI = BASE_AI_STATE_SIEGE + elseif (roll < 0.65) then + base.stateAI = BASE_AI_STATE_RAIDING + else + base.stateAI = BASE_AI_STATE_ONSLAUGHT + end + elseif (Universe.enabledMigration) then + if (roll < 0.30) and Universe.siegeAIToggle then + base.stateAI = BASE_AI_STATE_SIEGE + else + base.stateAI = BASE_AI_STATE_ONSLAUGHT + end + elseif (Universe.raidAIToggle) then + if (roll < 0.45) then + base.stateAI = BASE_AI_STATE_ONSLAUGHT + else + base.stateAI = BASE_AI_STATE_RAIDING + end + else + base.stateAI = BASE_AI_STATE_ONSLAUGHT + end + end + + local remainingUnits = base.lostEnemyUnits % 20 + if remainingUnits ~= 0 then + BaseUtils.modifyBaseUnitPoints(base, -(remainingUnits*UNIT_DEATH_POINT_COST), remainingUnits.." Units Lost") + end + + base.destroyPlayerBuildings = 0 + base.lostEnemyUnits = 0 + base.lostEnemyBuilding = 0 + base.rocketLaunched = 0 + base.builtEnemyBuilding = 0 + base.ionCannonBlasts = 0 + base.artilleryBlasts = 0 + + base.stateAITick = randomTickEvent(Universe.random, tick, BASE_AI_MIN_STATE_DURATION, BASE_AI_MAX_STATE_DURATION) + + if Universe.printAIStateChanges then + game.print(base.id .. ": AI is now: " .. STATE_ENGLISH[base.stateAI] .. ", Next state change is in " + .. string.format("%.2f", (base.stateAITick - tick) / (60*60)) .. " minutes @ " .. + getTimeStringFromTick(base.stateAITick) .. " playtime") + end + +end + +function BaseUtils.processBaseAIs(tick) + local baseId = Universe.processBaseAIIterator + local base + if not baseId then + baseId, base = next(Universe.bases, nil) + else + base = Universe.bases[baseId] + end + if not baseId then + Universe.processBaseAIIterator = nil + return + else + Universe.processBaseAIIterator = next(Universe.bases, baseId) + if (tick - base.tick) <= BASE_PROCESS_INTERVAL then + return + end + temperamentPlanner(base, Universe.evolutionLevel) + processState(base, tick) + processBase(base, tick) + end +end + function BaseUtils.init(universe) Universe = universe end diff --git a/libs/ChunkProcessor.lua b/libs/ChunkProcessor.lua deleted file mode 100644 index cce1c85..0000000 --- a/libs/ChunkProcessor.lua +++ /dev/null @@ -1,241 +0,0 @@ --- 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 (ChunkProcessorG) then - return ChunkProcessorG -end -local ChunkProcessor = {} - --- - -local Universe - --- imports - -local ChunkUtils = require("ChunkUtils") -local QueryUtils = require("QueryUtils") -local MapUtils = require("MapUtils") -local MathUtils = require("MathUtils") -local Constants = require("Constants") -local BaseUtils = require("BaseUtils") - --- Constants - -local PROXY_ENTITY_LOOKUP = Constants.PROXY_ENTITY_LOOKUP -local BASE_DISTANCE_TO_EVO_INDEX = Constants.BASE_DISTANCE_TO_EVO_INDEX - -local BUILDING_SPACE_LOOKUP = Constants.BUILDING_SPACE_LOOKUP - --- imported functions - -local findInsertionPoint = MapUtils.findInsertionPoint -local removeChunkFromMap = MapUtils.removeChunkFromMap -local setPositionInQuery = QueryUtils.setPositionInQuery -local registerEnemyBaseStructure = ChunkUtils.registerEnemyBaseStructure -local unregisterEnemyBaseStructure = ChunkUtils.unregisterEnemyBaseStructure -local euclideanDistancePoints = MathUtils.euclideanDistancePoints - -local findEntityUpgrade = BaseUtils.findEntityUpgrade - -local createChunk = ChunkUtils.createChunk -local initialScan = ChunkUtils.initialScan -local chunkPassScan = ChunkUtils.chunkPassScan - -local mMin = math.min -local mMax = math.max -local next = next -local table_size = table_size - -local tInsert = table.insert - --- module code - -function ChunkProcessor.processPendingChunks(tick, flush) - local pendingChunks = Universe.pendingChunks - local eventId, event = next(pendingChunks, nil) - - if not eventId then - if (tableSize(pendingChunks) == 0) then - -- this is needed as the next command remembers the max length a table has been - Universe.pendingChunks = {} - end - return - end - - local endCount = 1 - if flush then - endCount = tableSize(pendingChunks) - end - for _=1,endCount do - if not flush and (event.tick > tick) then - return - end - local newEventId, newEvent = next(pendingChunks, eventId) - pendingChunks[eventId] = nil - local map = event.map - if not map.surface.valid then - return - end - - local topLeft = event.area.left_top - local x = topLeft.x - local y = topLeft.y - - if not map[x] then - map[x] = {} - end - - if map[x][y] then - local oldChunk = map[x][y] - local chunk = initialScan(oldChunk, map, tick) - if (chunk == -1) then - removeChunkFromMap(map, oldChunk) - end - else - local initialChunk = createChunk(map, x, y) - map[x][y] = initialChunk - Universe.chunkIdToChunk[initialChunk.id] = initialChunk - local chunk = initialScan(initialChunk, map, tick) - if (chunk ~= -1) then - tInsert( - map.processQueue, - findInsertionPoint(map.processQueue, chunk), - chunk - ) - else - map[x][y] = nil - Universe.chunkIdToChunk[initialChunk.id] = nil - end - end - - eventId = newEventId - event = newEvent - if not eventId then - return - end - end -end - -function ChunkProcessor.processPendingUpgrades(tick) - local entityId, entityData = next(Universe.pendingUpgrades, nil) - if not entityId then - if tableSize(Universe.pendingUpgrades) == 0 then - Universe.pendingUpgrades = {} - end - return - end - local entity = entityData.entity - if not entity.valid then - Universe.pendingUpgrades[entityId] = nil - end - if entityData.delayTLL and tick < entityData.delayTLL then - return - end - Universe.pendingUpgrades[entityId] = nil - local base = entityData.base - local map = base.map - local baseAlignment = base.alignment - local position = entityData.position or entity.position - - local pickedBaseAlignment - if baseAlignment[2] then - if Universe.random() < 0.75 then - pickedBaseAlignment = baseAlignment[2] - else - pickedBaseAlignment = baseAlignment[1] - end - else - pickedBaseAlignment = baseAlignment[1] - end - - local currentEvo = entity.prototype.build_base_evolution_requirement or 0 - - local distance = mMin(1, euclideanDistancePoints(position.x, position.y, 0, 0) * BASE_DISTANCE_TO_EVO_INDEX) - local evoIndex = mMax(distance, Universe.evolutionLevel) - - local name = findEntityUpgrade(pickedBaseAlignment, - currentEvo, - evoIndex, - entity, - map, - entityData.evolve) - - local entityName = entity.name - if not name and PROXY_ENTITY_LOOKUP[entityName] then - entity.destroy() - return - elseif (name == entityName) or not name then - return - end - - local surface = entity.surface - local query = Universe.ppuUpgradeEntityQuery - query.name = name - - unregisterEnemyBaseStructure(map, entity, nil, true) - entity.destroy() - local foundPosition = surface.find_non_colliding_position(BUILDING_SPACE_LOOKUP[name], - position, - 2, - 1, - true) - setPositionInQuery(query, foundPosition or position) - - local createdEntity = surface.create_entity({ - name = query.name, - position = query.position - }) - if createdEntity and createdEntity.valid then - if entityData.register then - registerEnemyBaseStructure(map, createdEntity, base, tick, true) - end - if not entityData.evolve and Universe.printBaseUpgrades then - surface.print("["..base.id.."]:"..surface.name.." Upgrading ".. entityName .. " to " .. name .. " [gps=".. position.x ..",".. position.y .."]") - end - if remote.interfaces["kr-creep"] then - remote.call("kr-creep", "spawn_creep_at_position", surface, foundPosition or position, false, createdEntity.name) - end - end -end - - -function ChunkProcessor.processScanChunks() - local chunkId, chunk = next(Universe.chunkToPassScan, nil) - if not chunkId then - if (tableSize(Universe.chunkToPassScan) == 0) then - -- this is needed as the next command remembers the max length a table has been - Universe.chunkToPassScan = {} - end - return - end - - Universe.chunkToPassScan[chunkId] = nil - local map = chunk.map - if not map.surface.valid then - return - end - - if (chunkPassScan(chunk, map) == -1) then - removeChunkFromMap(map, chunk) - end -end - -function ChunkProcessor.init(universe) - Universe = universe -end - -ChunkProcessorG = ChunkProcessor -return ChunkProcessor diff --git a/libs/ChunkUtils.lua b/libs/ChunkUtils.lua index 3d32469..32bf752 100644 --- a/libs/ChunkUtils.lua +++ b/libs/ChunkUtils.lua @@ -31,7 +31,7 @@ local Constants = require("Constants") local MapUtils = require("MapUtils") local ChunkPropertyUtils = require("ChunkPropertyUtils") local MathUtils = require("MathUtils") -local QueryUtils = require("QueryUtils") +local Utils = require("Utils") -- Constants @@ -73,9 +73,9 @@ local GENERATOR_PHEROMONE_LEVEL_6 = Constants.GENERATOR_PHEROMONE_LEVEL_6 local removeBaseResourceChunk = ChunkPropertyUtils.removeBaseResourceChunk local addBaseResourceChunk = ChunkPropertyUtils.addBaseResourceChunk -local setAreaInQueryChunkSize = QueryUtils.setAreaInQueryChunkSize -local setAreaXInQuery = QueryUtils.setAreaXInQuery -local setAreaYInQuery = QueryUtils.setAreaYInQuery +local setAreaInQueryChunkSize = Utils.setAreaInQueryChunkSize +local setAreaXInQuery = Utils.setAreaXInQuery +local setAreaYInQuery = Utils.setAreaYInQuery local setPlayerBaseGenerator = ChunkPropertyUtils.setPlayerBaseGenerator local addPlayerBaseGenerator = ChunkPropertyUtils.addPlayerBaseGenerator diff --git a/libs/MapUtils.lua b/libs/MapUtils.lua index 95057aa..6d78c15 100644 --- a/libs/MapUtils.lua +++ b/libs/MapUtils.lua @@ -28,9 +28,24 @@ local NeighborChunks local Constants = require("Constants") local ChunkPropertyUtils = require("ChunkPropertyUtils") +local MathUtils = require("MathUtils") -- Constants +local MAGIC_MAXIMUM_NUMBER = Constants.MAGIC_MAXIMUM_NUMBER +local ENEMY_PHEROMONE_MULTIPLER = Constants.ENEMY_PHEROMONE_MULTIPLER +local BASE_PHEROMONE = Constants.BASE_PHEROMONE +local PLAYER_PHEROMONE = Constants.PLAYER_PHEROMONE +local RESOURCE_PHEROMONE = Constants.RESOURCE_PHEROMONE +local ENEMY_PHEROMONE = Constants.ENEMY_PHEROMONE + +local CHUNK_TICK = Constants.CHUNK_TICK + +local VICTORY_SCENT = Constants.VICTORY_SCENT +local VICTORY_SCENT_MULTIPLER = Constants.VICTORY_SCENT_MULTIPLER +local VICTORY_SCENT_BOUND = Constants.VICTORY_SCENT_BOUND +local DEATH_PHEROMONE_GENERATOR_AMOUNT = Constants.DEATH_PHEROMONE_GENERATOR_AMOUNT +local TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT = Constants.TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT local CHUNK_NORTH_SOUTH = Constants.CHUNK_NORTH_SOUTH local CHUNK_EAST_WEST = Constants.CHUNK_EAST_WEST local CHUNK_IMPASSABLE = Constants.CHUNK_IMPASSABLE @@ -42,6 +57,19 @@ local CHUNK_SIZE_DIVIDER = Constants.CHUNK_SIZE_DIVIDER -- imported functions +local addVictoryGenerator = ChunkPropertyUtils.addVictoryGenerator +local addPermanentDeathGenerator = ChunkPropertyUtils.addPermanentDeathGenerator +local addDeathGenerator = ChunkPropertyUtils.addDeathGenerator +local getCombinedDeathGenerator = ChunkPropertyUtils.getCombinedDeathGenerator +local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating +local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount +local setDeathGenerator = ChunkPropertyUtils.setDeathGenerator +local decayPlayerGenerator = ChunkPropertyUtils.decayPlayerGenerator +local getPathRating = ChunkPropertyUtils.getPathRating +local linearInterpolation = MathUtils.linearInterpolation +local decayDeathGenerator = ChunkPropertyUtils.decayDeathGenerator + +local mMax = math.max local mFloor = math.floor local getPassable = ChunkPropertyUtils.getPassable local tRemove = table.remove @@ -355,6 +383,124 @@ function MapUtils.positionFromDirectionAndFlat(direction, startPosition, multipl } end +function MapUtils.victoryScent(chunk, entityType) + local value = VICTORY_SCENT[entityType] + if value then + addVictoryGenerator(chunk, value) + end +end + +function MapUtils.disperseVictoryScent() + local chunkToVictory = Universe.chunkToVictory + local chunkId, pheromonePack = next(chunkToVictory, nil) + if not chunkId then + return + end + + chunkToVictory[chunkId] = nil + local chunk = pheromonePack.chunk + local map = chunk.map + if not map.surface.valid then + return + end + local chunkX = chunk.x + local chunkY = chunk.y + local i = 1 + for x=chunkX - VICTORY_SCENT_BOUND, chunkX + VICTORY_SCENT_BOUND,32 do + for y = chunkY - VICTORY_SCENT_BOUND, chunkY + VICTORY_SCENT_BOUND,32 do + local c = MapUtils.getChunkByXY(map, x, y) + if (c ~= -1) then + local amount = pheromonePack.v * VICTORY_SCENT_MULTIPLER[i] + addDeathGenerator(c, amount) + addPermanentDeathGenerator(c, amount) + end + i = i + 1 + end + end +end + +function MapUtils.deathScent(chunk, structure) + local amount = -DEATH_PHEROMONE_GENERATOR_AMOUNT + if structure then + amount = -TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT + end + addDeathGenerator(chunk, amount) + addPermanentDeathGenerator(chunk, amount) +end + +function MapUtils.processPheromone(map, chunk, tick, player) + if chunk[CHUNK_TICK] > tick then + return + end + chunk[CHUNK_TICK] = tick + + local chunkPlayer = chunk[PLAYER_PHEROMONE] + local chunkBase = -MAGIC_MAXIMUM_NUMBER + local chunkDeath = getCombinedDeathGenerator(chunk) + local chunkResource = -MAGIC_MAXIMUM_NUMBER + local chunkEnemy = chunk[ENEMY_PHEROMONE] + + local chunkCount = 1 + + local enemyStructureCount = getEnemyStructureCount(chunk) + + local tempNeighbors = MapUtils.getNeighborChunks(map, chunk.x, chunk.y) + for i=1,8 do + local tempPheromone + local neighbor = tempNeighbors[i] + if (neighbor ~= -1) then + if MapUtils.canMoveChunkDirection(map, i, chunk, neighbor) then + chunkCount = chunkCount + 1 + chunkPlayer = chunkPlayer + neighbor[PLAYER_PHEROMONE] + chunkEnemy = chunkEnemy + neighbor[ENEMY_PHEROMONE] + chunkDeath = chunkDeath + getCombinedDeathGenerator(neighbor) + tempPheromone = neighbor[BASE_PHEROMONE] + if chunkBase < tempPheromone then + chunkBase = tempPheromone + end + tempPheromone = neighbor[RESOURCE_PHEROMONE] + if chunkResource < tempPheromone then + chunkResource = tempPheromone + end + end + end + end + + setDeathGenerator(chunk, (chunkDeath / chunkCount) * 0.75) + + if not player then + decayDeathGenerator(chunk) + end + + decayPlayerGenerator(chunk) + + local chunkDeathRating = getCombinedDeathGeneratorRating(chunk) * getPathRating(chunk) + + chunk[PLAYER_PHEROMONE] = chunkDeathRating * mMax( + chunk.playerGenerator or 0, + (chunkPlayer / chunkCount) * 0.98 + ) + + chunk[BASE_PHEROMONE] = chunkDeathRating * mMax( + chunk.playerBaseGenerator or 0, + chunkBase * 0.9 + ) + + chunk[ENEMY_PHEROMONE] = chunkDeathRating * mMax( + enemyStructureCount * ENEMY_PHEROMONE_MULTIPLER, + (chunkEnemy / chunkCount) * 0.9 + ) + + local resourcePheromoneGenerator = chunk.resourceGenerator or 0 + if (resourcePheromoneGenerator > 0) then + chunkResource = linearInterpolation(resourcePheromoneGenerator, 15000, 20000) + end + if enemyStructureCount ~= 0 then + chunkResource = chunkResource * 0.0001 + end + chunk[RESOURCE_PHEROMONE] = chunkDeathRating * chunkResource * 0.9 +end + function MapUtils.init(universe) Universe = universe NeighborChunks = universe.neighbors diff --git a/libs/MovementUtils.lua b/libs/MovementUtils.lua deleted file mode 100644 index 5d0865d..0000000 --- a/libs/MovementUtils.lua +++ /dev/null @@ -1,308 +0,0 @@ --- 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 MovementUtilsG then - return MovementUtilsG -end -local MovementUtils = {} - --- - -local Universe - --- imports - -local Constants = require("Constants") -local MapUtils = require("MapUtils") -local MathUtils = require("MathUtils") -local UnitGroupUtils = require("UnitGroupUtils") - --- Constants - -local MAGIC_MAXIMUM_NUMBER = Constants.MAGIC_MAXIMUM_NUMBER - -local SQUAD_SETTLING = Constants.SQUAD_SETTLING - --- imported functions - -local calculateSettlerMaxDistance = UnitGroupUtils.calculateSettlerMaxDistance - -local canMoveChunkDirection = MapUtils.canMoveChunkDirection -local getNeighborChunks = MapUtils.getNeighborChunks - -local tableRemove = table.remove -local tableInsert = table.insert - -local distortPosition = MathUtils.distortPosition - --- module code - -function MovementUtils.findMovementPosition(surface, position) - local pos = position - pos = surface.find_non_colliding_position("behemoth-biter", pos, 10, 2, false) - return pos -end - -function MovementUtils.findMovementPositionEntity(entityName, surface, position) - local pos = position - pos = surface.find_non_colliding_position(entityName, pos, 5, 4, true) - return pos -end - -function MovementUtils.findMovementPositionDistort(surface, position) - local pos = position - pos = surface.find_non_colliding_position("behemoth-biter", pos, 10, 2, false) - return distortPosition(pos, 8) -end - -function MovementUtils.addMovementPenalty(squad, chunk) - if (chunk == -1) then - return - end - local penalties = squad.penalties - local penaltyCount = #penalties - for i=1,penaltyCount do - local penalty = penalties[i] - if (penalty.c.id == chunk.id) then - penalty.v = penalty.v + 1 - if penalty.v >= 15 then - if Universe.enabledMigration and - (Universe.builderCount < Universe.AI_MAX_BUILDER_COUNT) then - squad.settler = true - squad.originPosition.x = squad.group.position.x - squad.originPosition.y = squad.group.position.y - squad.maxDistance = calculateSettlerMaxDistance() - - squad.status = SQUAD_SETTLING - else - squad.group.destroy() - end - end - return - end - end - if (penaltyCount == 10) then - tableRemove(penalties, 10) - end - tableInsert(penalties, - 1, - { v = 1, - c = chunk }) -end - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function MovementUtils.scoreNeighborsForAttack(map, chunk, neighborDirectionChunks, scoreFunction) - local highestChunk = -1 - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if (neighborChunk ~= -1) then - if (chunk == -1) or canMoveChunkDirection(map, x, chunk, neighborChunk) then - local score = scoreFunction(map, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - end - - local nextHighestChunk = -1 - local nextHighestScore = highestScore - local nextHighestDirection - - if (highestChunk ~= -1) then - neighborDirectionChunks = getNeighborChunks(map, highestChunk.x, highestChunk.y) - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if ((neighborChunk ~= -1) and ((chunk == -1) or (neighborChunk.id ~= chunk.id)) and - canMoveChunkDirection(map, x, highestChunk, neighborChunk)) then - local score = scoreFunction(map, neighborChunk) - if (score > nextHighestScore) then - nextHighestScore = score - nextHighestChunk = neighborChunk - nextHighestDirection = x - end - end - end - end - - return highestChunk, highestDirection, nextHighestChunk, nextHighestDirection -end - - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function MovementUtils.scoreNeighborsForSettling(map, chunk, neighborDirectionChunks, scoreFunction) - local highestChunk = -1 - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection = 0 - - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if (neighborChunk ~= -1) then - if (chunk == -1) or canMoveChunkDirection(map, x, chunk, neighborChunk) then - local score = scoreFunction(map, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - end - - if (chunk ~= -1) and (scoreFunction(map, chunk) > highestScore) then - return chunk, 0, -1, 0 - end - - local nextHighestChunk = -1 - local nextHighestScore = highestScore - local nextHighestDirection = 0 - - if (highestChunk ~= -1) then - neighborDirectionChunks = getNeighborChunks(map, highestChunk.x, highestChunk.y) - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if ((neighborChunk ~= -1) and ((chunk == -1) or (neighborChunk.id ~= chunk.id)) and - canMoveChunkDirection(map, x, highestChunk, neighborChunk)) then - local score = scoreFunction(map, neighborChunk) - if (score > nextHighestScore) then - nextHighestScore = score - nextHighestChunk = neighborChunk - nextHighestDirection = x - end - end - end - end - - return highestChunk, highestDirection, nextHighestChunk, nextHighestDirection -end - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function MovementUtils.scoreNeighborsForResource(chunk, neighborDirectionChunks, validFunction, scoreFunction, map) - local highestChunk = -1 - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if (neighborChunk ~= -1) and - canMoveChunkDirection(map, x, chunk, neighborChunk) and - validFunction(map, chunk, neighborChunk) - then - local score = scoreFunction(map, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - - if (chunk ~= -1) and (scoreFunction(map, chunk) > highestScore) then - return -1, -1 - end - - return highestChunk, highestDirection -end - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function MovementUtils.scoreNeighborsForRetreat(chunk, neighborDirectionChunks, scoreFunction, map) - local highestChunk = -1 - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if (neighborChunk ~= -1) then - if (chunk == -1) or canMoveChunkDirection(map, x, chunk, neighborChunk) then - local score = scoreFunction(map, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - end - - local nextHighestChunk = -1 - local nextHighestScore = highestScore - local nextHighestDirection - - if (highestChunk ~= -1) then - neighborDirectionChunks = getNeighborChunks(map, highestChunk.x, highestChunk.y) - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - - if ((neighborChunk ~= -1) and ((chunk == -1) or (neighborChunk.id ~= chunk.id)) and - canMoveChunkDirection(map, x, highestChunk, neighborChunk)) then - local score = scoreFunction(map, neighborChunk) - if (score > nextHighestScore) then - nextHighestScore = score - nextHighestChunk = neighborChunk - nextHighestDirection = x - end - end - end - end - - if (nextHighestChunk == nil) then - nextHighestChunk = -1 - end - - return highestChunk, highestDirection, nextHighestChunk, nextHighestDirection -end - - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function MovementUtils.scoreNeighborsForFormation(neighborChunks, validFunction, scoreFunction, map) - local highestChunk = -1 - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - for x=1,8 do - local neighborChunk = neighborChunks[x] - if (neighborChunk ~= -1) and validFunction(map, neighborChunk) then - local score = scoreFunction(map, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - - return highestChunk, highestDirection -end - -function MovementUtils.init(universe) - Universe = universe -end - -MovementUtilsG = MovementUtils -return MovementUtils diff --git a/libs/PheromoneUtils.lua b/libs/PheromoneUtils.lua deleted file mode 100644 index ea8ee5d..0000000 --- a/libs/PheromoneUtils.lua +++ /dev/null @@ -1,204 +0,0 @@ --- 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 PheromoneUtilsG then - return PheromoneUtilsG -end -local PheromoneUtils = {} - --- - -local Universe - --- imports -local MathUtils = require("MathUtils") -local MapUtils = require("MapUtils") -local Constants = require("Constants") -local ChunkPropertyUtils = require("ChunkPropertyUtils") - --- Constants - -local CHUNK_TICK = Constants.CHUNK_TICK - -local ENEMY_PHEROMONE_MULTIPLER = Constants.ENEMY_PHEROMONE_MULTIPLER -local VICTORY_SCENT_MULTIPLER = Constants.VICTORY_SCENT_MULTIPLER -local VICTORY_SCENT_BOUND = Constants.VICTORY_SCENT_BOUND - -local MAGIC_MAXIMUM_NUMBER = Constants.MAGIC_MAXIMUM_NUMBER - -local BASE_PHEROMONE = Constants.BASE_PHEROMONE -local PLAYER_PHEROMONE = Constants.PLAYER_PHEROMONE -local RESOURCE_PHEROMONE = Constants.RESOURCE_PHEROMONE -local ENEMY_PHEROMONE = Constants.ENEMY_PHEROMONE - -local VICTORY_SCENT = Constants.VICTORY_SCENT - -local DEATH_PHEROMONE_GENERATOR_AMOUNT = Constants.DEATH_PHEROMONE_GENERATOR_AMOUNT -local TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT = Constants.TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT - --- imported functions - -local decayPlayerGenerator = ChunkPropertyUtils.decayPlayerGenerator -local addVictoryGenerator = ChunkPropertyUtils.addVictoryGenerator -local getCombinedDeathGenerator = ChunkPropertyUtils.getCombinedDeathGenerator -local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating -local setDeathGenerator = ChunkPropertyUtils.setDeathGenerator - -local addPermanentDeathGenerator = ChunkPropertyUtils.addPermanentDeathGenerator - -local canMoveChunkDirection = MapUtils.canMoveChunkDirection -local getNeighborChunks = MapUtils.getNeighborChunks -local getChunkById = MapUtils.getChunkById - -local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount -local getPathRating = ChunkPropertyUtils.getPathRating -local addDeathGenerator = ChunkPropertyUtils.addDeathGenerator - -local decayDeathGenerator = ChunkPropertyUtils.decayDeathGenerator - -local linearInterpolation = MathUtils.linearInterpolation - -local getChunkByXY = MapUtils.getChunkByXY - -local next = next -local mMax = math.max - --- module code - -function PheromoneUtils.victoryScent(chunk, entityType) - local value = VICTORY_SCENT[entityType] - if value then - addVictoryGenerator(chunk, value) - end -end - -function PheromoneUtils.disperseVictoryScent() - local chunkToVictory = Universe.chunkToVictory - local chunkId, pheromonePack = next(chunkToVictory, nil) - if not chunkId then - return - end - - chunkToVictory[chunkId] = nil - local chunk = pheromonePack.chunk - local map = chunk.map - if not map.surface.valid then - return - end - local chunkX = chunk.x - local chunkY = chunk.y - local i = 1 - for x=chunkX - VICTORY_SCENT_BOUND, chunkX + VICTORY_SCENT_BOUND,32 do - for y = chunkY - VICTORY_SCENT_BOUND, chunkY + VICTORY_SCENT_BOUND,32 do - local c = getChunkByXY(map, x, y) - if (c ~= -1) then - local amount = pheromonePack.v * VICTORY_SCENT_MULTIPLER[i] - addDeathGenerator(c, amount) - addPermanentDeathGenerator(c, amount) - end - i = i + 1 - end - end -end - -function PheromoneUtils.deathScent(chunk, structure) - local amount = -DEATH_PHEROMONE_GENERATOR_AMOUNT - if structure then - amount = -TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT - end - addDeathGenerator(chunk, amount) - addPermanentDeathGenerator(chunk, amount) -end - -function PheromoneUtils.processPheromone(map, chunk, tick, player) - if chunk[CHUNK_TICK] > tick then - return - end - chunk[CHUNK_TICK] = tick - - local chunkPlayer = chunk[PLAYER_PHEROMONE] - local chunkBase = -MAGIC_MAXIMUM_NUMBER - local chunkDeath = getCombinedDeathGenerator(chunk) - local chunkResource = -MAGIC_MAXIMUM_NUMBER - local chunkEnemy = chunk[ENEMY_PHEROMONE] - - local chunkCount = 1 - - local enemyStructureCount = getEnemyStructureCount(chunk) - - local tempNeighbors = getNeighborChunks(map, chunk.x, chunk.y) - for i=1,8 do - local tempPheromone - local neighbor = tempNeighbors[i] - if (neighbor ~= -1) then - if canMoveChunkDirection(map, i, chunk, neighbor) then - chunkCount = chunkCount + 1 - chunkPlayer = chunkPlayer + neighbor[PLAYER_PHEROMONE] - chunkEnemy = chunkEnemy + neighbor[ENEMY_PHEROMONE] - chunkDeath = chunkDeath + getCombinedDeathGenerator(neighbor) - tempPheromone = neighbor[BASE_PHEROMONE] - if chunkBase < tempPheromone then - chunkBase = tempPheromone - end - tempPheromone = neighbor[RESOURCE_PHEROMONE] - if chunkResource < tempPheromone then - chunkResource = tempPheromone - end - end - end - end - - setDeathGenerator(chunk, (chunkDeath / chunkCount) * 0.75) - - if not player then - decayDeathGenerator(chunk) - end - - decayPlayerGenerator(chunk) - - local chunkDeathRating = getCombinedDeathGeneratorRating(chunk) * getPathRating(chunk) - - chunk[PLAYER_PHEROMONE] = chunkDeathRating * mMax( - chunk.playerGenerator or 0, - (chunkPlayer / chunkCount) * 0.98 - ) - - chunk[BASE_PHEROMONE] = chunkDeathRating * mMax( - chunk.playerBaseGenerator or 0, - chunkBase * 0.9 - ) - - chunk[ENEMY_PHEROMONE] = chunkDeathRating * mMax( - enemyStructureCount * ENEMY_PHEROMONE_MULTIPLER, - (chunkEnemy / chunkCount) * 0.9 - ) - - local resourcePheromoneGenerator = chunk.resourceGenerator or 0 - if (resourcePheromoneGenerator > 0) then - chunkResource = linearInterpolation(resourcePheromoneGenerator, 15000, 20000) - end - if enemyStructureCount ~= 0 then - chunkResource = chunkResource * 0.0001 - end - chunk[RESOURCE_PHEROMONE] = chunkDeathRating * chunkResource * 0.9 -end - -function PheromoneUtils.init(universe) - Universe = universe -end - -PheromoneUtilsG = PheromoneUtils -return PheromoneUtils diff --git a/libs/PlayerUtils.lua b/libs/PlayerUtils.lua deleted file mode 100644 index f2b8ce0..0000000 --- a/libs/PlayerUtils.lua +++ /dev/null @@ -1,37 +0,0 @@ --- 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 playerUtilsG then - return playerUtilsG -end -local playerUtils = {} - --- imports - --- imported functions - --- module code - -function playerUtils.validPlayer(player) - if player and player.valid then - local char = player.character - return char and char.valid - end - return false -end - -playerUtilsG = playerUtils -return playerUtils diff --git a/libs/MapProcessor.lua b/libs/Processor.lua similarity index 61% rename from libs/MapProcessor.lua rename to libs/Processor.lua index 02688cd..0922443 100644 --- a/libs/MapProcessor.lua +++ b/libs/Processor.lua @@ -14,10 +14,10 @@ -- along with this program. If not, see . -if MapProcessorG then - return MapProcessorG +if ProcessorG then + return ProcessorG end -local MapProcessor = {} +local Processor = {} -- @@ -25,21 +25,18 @@ local Universe -- imports -local QueryUtils = require("QueryUtils") -local PheromoneUtils = require("PheromoneUtils") -local AiAttackWave = require("AIAttackWave") -local AiPredicates = require("AIPredicates") -local Constants = require("Constants") +local Utils = require("Utils") local MapUtils = require("MapUtils") -local PlayerUtils = require("PlayerUtils") +local Squad = require("Squad") +local Constants = require("Constants") local ChunkUtils = require("ChunkUtils") local ChunkPropertyUtils = require("ChunkPropertyUtils") local BaseUtils = require("BaseUtils") +local MathUtils = require("MathUtils") -- Constants local PLAYER_PHEROMONE_GENERATOR_AMOUNT = Constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT -local DURATION_ACTIVE_NEST = Constants.DURATION_ACTIVE_NEST local PROCESS_QUEUE_SIZE = Constants.PROCESS_QUEUE_SIZE local RESOURCE_QUEUE_SIZE = Constants.RESOURCE_QUEUE_SIZE @@ -60,33 +57,49 @@ local BASE_AI_STATE_MIGRATING = Constants.BASE_AI_STATE_MIGRATING local COOLDOWN_DRAIN = Constants.COOLDOWN_DRAIN local COOLDOWN_RALLY = Constants.COOLDOWN_RALLY local COOLDOWN_RETREAT = Constants.COOLDOWN_RETREAT +local PROXY_ENTITY_LOOKUP = Constants.PROXY_ENTITY_LOOKUP +local BASE_DISTANCE_TO_EVO_INDEX = Constants.BASE_DISTANCE_TO_EVO_INDEX + +local BUILDING_SPACE_LOOKUP = Constants.BUILDING_SPACE_LOOKUP -- imported functions -local setPositionInQuery = QueryUtils.setPositionInQuery +local findInsertionPoint = MapUtils.findInsertionPoint + +local createChunk = ChunkUtils.createChunk +local initialScan = ChunkUtils.initialScan +local euclideanDistancePoints = MathUtils.euclideanDistancePoints + +local removeChunkFromMap = MapUtils.removeChunkFromMap +local chunkPassScan = ChunkUtils.chunkPassScan +local findEntityUpgrade = BaseUtils.findEntityUpgrade + +local unregisterEnemyBaseStructure = ChunkUtils.unregisterEnemyBaseStructure +local registerEnemyBaseStructure = ChunkUtils.registerEnemyBaseStructure +local setPositionInQuery = Utils.setPositionInQuery local addPlayerGenerator = ChunkPropertyUtils.addPlayerGenerator local findNearbyBase = ChunkPropertyUtils.findNearbyBase local removeChunkToNest = MapUtils.removeChunkToNest -local processPheromone = PheromoneUtils.processPheromone +local processPheromone = MapUtils.processPheromone local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating local processBaseMutation = BaseUtils.processBaseMutation local processNestActiveness = ChunkPropertyUtils.processNestActiveness -local formSquads = AiAttackWave.formSquads -local formVengenceSquad = AiAttackWave.formVengenceSquad -local formVengenceSettler = AiAttackWave.formVengenceSettler -local formSettlers = AiAttackWave.formSettlers +local formSquads = Squad.formSquads +local formVengenceSquad = Squad.formVengenceSquad +local formVengenceSettler = Squad.formVengenceSettler +local formSettlers = Squad.formSettlers local getChunkByPosition = MapUtils.getChunkByPosition local getChunkByXY = MapUtils.getChunkByXY local getChunkById = MapUtils.getChunkById -local validPlayer = PlayerUtils.validPlayer +local validPlayer = Utils.validPlayer local mapScanEnemyChunk = ChunkUtils.mapScanEnemyChunk local mapScanPlayerChunk = ChunkUtils.mapScanPlayerChunk @@ -94,13 +107,14 @@ local mapScanResourceChunk = ChunkUtils.mapScanResourceChunk local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount -local canAttack = AiPredicates.canAttack -local canMigrate = AiPredicates.canMigrate +local canAttack = BaseUtils.canAttack +local canMigrate = BaseUtils.canMigrate local tableSize = table_size local mMin = math.min local mMax = math.max +local tableInsert = table.insert local next = next @@ -113,7 +127,7 @@ local next = next In theory, this might be fine as smaller bases have less surface to attack and need to have pheromone dissipate at a faster rate. --]] -function MapProcessor.processMap(map, tick) +function Processor.processMap(map, tick) local processQueue = map.processQueue local processQueueLength = #processQueue @@ -156,7 +170,7 @@ end vs the slower passive version processing the entire map in multiple passes. --]] -function MapProcessor.processPlayers(players, tick) +function Processor.processPlayers(players, tick) -- put down player pheromone for player hunters -- randomize player order to ensure a single player isn't singled out -- not looping everyone because the cost is high enough already in multiplayer @@ -229,7 +243,7 @@ local function processCleanUp(chunks, tick, duration) end end -function MapProcessor.cleanUpMapTables(tick) +function Processor.cleanUpMapTables(tick) local retreats = Universe.chunkToRetreats local rallys = Universe.chunkToRallys local drained = Universe.chunkToDrained @@ -246,7 +260,7 @@ end --[[ Passive scan to find entities that have been generated outside the factorio event system --]] -function MapProcessor.scanPlayerMap(map, tick) +function Processor.scanPlayerMap(map, tick) if (map.nextProcessMap == tick) or (map.nextPlayerScan == tick) or (map.nextEnemyScan == tick) or (map.nextChunkProcess == tick) then @@ -274,7 +288,7 @@ function MapProcessor.scanPlayerMap(map, tick) end end -function MapProcessor.scanEnemyMap(map, tick) +function Processor.scanEnemyMap(map, tick) local index = map.scanEnemyIndex local processQueue = map.processQueue @@ -297,7 +311,7 @@ function MapProcessor.scanEnemyMap(map, tick) end end -function MapProcessor.scanResourceMap(map, tick) +function Processor.scanResourceMap(map, tick) local index = map.scanResourceIndex local processQueue = map.processQueue @@ -320,7 +334,7 @@ function MapProcessor.scanResourceMap(map, tick) end end -function MapProcessor.processVengence() +function Processor.processVengence() local vengenceQueue = Universe.vengenceQueue local chunkId, chunk = next(vengenceQueue, nil) if not chunkId then @@ -343,7 +357,7 @@ function MapProcessor.processVengence() end end -function MapProcessor.processNests(tick) +function Processor.processNests(tick) local chunkId = Universe.processNestIterator local chunkPack if not chunkId then @@ -383,7 +397,7 @@ local function processSpawnersBody(iterator, chunks) Universe[iterator] = nil else Universe[iterator] = next(chunks, chunkId) - local map = chunkPack.map + local map = chunkPack.map -- error if not map.surface.valid then if (iterator == "processMigrationIterator") then removeChunkToNest(chunkId) @@ -411,8 +425,8 @@ local function processSpawnersBody(iterator, chunks) end end - local migrate = canMigrate(map, base) - local attack = canAttack(map, base) + local migrate = canMigrate(base) + local attack = canAttack(base) if migrate then formSettlers(chunk, base) end @@ -422,7 +436,7 @@ local function processSpawnersBody(iterator, chunks) end end -function MapProcessor.processAttackWaves() +function Processor.processAttackWaves() processSpawnersBody("processActiveSpawnerIterator", Universe.chunkToActiveNest) processSpawnersBody("processActiveRaidSpawnerIterator", @@ -431,7 +445,7 @@ function MapProcessor.processAttackWaves() Universe.chunkToNests) end -function MapProcessor.processClouds(tick) +function Processor.processClouds(tick) local eventId, builderPack = next(Universe.settlePurpleCloud, nil) if builderPack and (builderPack.tick <= tick) then Universe.settlePurpleCloud[eventId] = nil @@ -446,9 +460,180 @@ function MapProcessor.processClouds(tick) end end -function MapProcessor.init(universe) + +function Processor.processPendingChunks(tick, flush) + local pendingChunks = Universe.pendingChunks + local eventId, event = next(pendingChunks, nil) + + if not eventId then + if (tableSize(pendingChunks) == 0) then + -- this is needed as the next command remembers the max length a table has been + Universe.pendingChunks = {} + end + return + end + + local endCount = 1 + if flush then + endCount = tableSize(pendingChunks) + end + for _=1,endCount do + if not flush and (event.tick > tick) then + return + end + local newEventId, newEvent = next(pendingChunks, eventId) + pendingChunks[eventId] = nil + local map = event.map + if not map.surface.valid then + return + end + + local topLeft = event.area.left_top + local x = topLeft.x + local y = topLeft.y + + if not map[x] then + map[x] = {} + end + + if map[x][y] then + local oldChunk = map[x][y] + local chunk = initialScan(oldChunk, map, tick) + if (chunk == -1) then + removeChunkFromMap(map, oldChunk) + end + else + local initialChunk = createChunk(map, x, y) + map[x][y] = initialChunk + Universe.chunkIdToChunk[initialChunk.id] = initialChunk + local chunk = initialScan(initialChunk, map, tick) + if (chunk ~= -1) then + tableInsert( + map.processQueue, + findInsertionPoint(map.processQueue, chunk), + chunk + ) + else + map[x][y] = nil + Universe.chunkIdToChunk[initialChunk.id] = nil + end + end + + eventId = newEventId + event = newEvent + if not eventId then + return + end + end +end + +function Processor.processPendingUpgrades(tick) + local entityId, entityData = next(Universe.pendingUpgrades, nil) + if not entityId then + if tableSize(Universe.pendingUpgrades) == 0 then + Universe.pendingUpgrades = {} + end + return + end + local entity = entityData.entity + if not entity.valid then + Universe.pendingUpgrades[entityId] = nil + end + if entityData.delayTLL and tick < entityData.delayTLL then + return + end + Universe.pendingUpgrades[entityId] = nil + local base = entityData.base + local map = base.map + local baseAlignment = base.alignment + local position = entityData.position or entity.position + + local pickedBaseAlignment + if baseAlignment[2] then + if Universe.random() < 0.75 then + pickedBaseAlignment = baseAlignment[2] + else + pickedBaseAlignment = baseAlignment[1] + end + else + pickedBaseAlignment = baseAlignment[1] + end + + local currentEvo = entity.prototype.build_base_evolution_requirement or 0 + + local distance = mMin(1, euclideanDistancePoints(position.x, position.y, 0, 0) * BASE_DISTANCE_TO_EVO_INDEX) + local evoIndex = mMax(distance, Universe.evolutionLevel) + + local name = findEntityUpgrade(pickedBaseAlignment, + currentEvo, + evoIndex, + entity, + map, + entityData.evolve) + + local entityName = entity.name + if not name and PROXY_ENTITY_LOOKUP[entityName] then + entity.destroy() + return + elseif (name == entityName) or not name then + return + end + + local surface = entity.surface + local query = Universe.ppuUpgradeEntityQuery + query.name = name + + unregisterEnemyBaseStructure(map, entity, nil, true) + entity.destroy() + local foundPosition = surface.find_non_colliding_position(BUILDING_SPACE_LOOKUP[name], + position, + 2, + 1, + true) + setPositionInQuery(query, foundPosition or position) + + local createdEntity = surface.create_entity({ + name = query.name, + position = query.position + }) + if createdEntity and createdEntity.valid then + if entityData.register then + registerEnemyBaseStructure(map, createdEntity, base, tick, true) + end + if not entityData.evolve and Universe.printBaseUpgrades then + surface.print("["..base.id.."]:"..surface.name.." Upgrading ".. entityName .. " to " .. name .. " [gps=".. position.x ..",".. position.y .."]") + end + if remote.interfaces["kr-creep"] then + remote.call("kr-creep", "spawn_creep_at_position", surface, foundPosition or position, false, createdEntity.name) + end + end +end + + +function Processor.processScanChunks() + local chunkId, chunk = next(Universe.chunkToPassScan, nil) + if not chunkId then + if (tableSize(Universe.chunkToPassScan) == 0) then + -- this is needed as the next command remembers the max length a table has been + Universe.chunkToPassScan = {} + end + return + end + + Universe.chunkToPassScan[chunkId] = nil + local map = chunk.map + if not map.surface.valid then + return + end + + if (chunkPassScan(chunk, map) == -1) then + removeChunkFromMap(map, chunk) + end +end + +function Processor.init(universe) Universe = universe end -MapProcessorG = MapProcessor -return MapProcessor +ProcessorG = Processor +return Processor diff --git a/libs/QueryUtils.lua b/libs/QueryUtils.lua deleted file mode 100644 index 783bb20..0000000 --- a/libs/QueryUtils.lua +++ /dev/null @@ -1,81 +0,0 @@ --- 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 QueryUtilsG then - return QueryUtilsG -end -local QueryUtils = {} - -local Constants = require("Constants") - -local CHUNK_SIZE = Constants.CHUNK_SIZE - -function QueryUtils.setPositionInQuery(query, position) - local point = query.position - point[1] = position.x - point[2] = position.y -end - -function QueryUtils.setPositionInCommand(cmd, position) - local point = cmd.destination - point[1] = position.x - point[2] = position.y -end - -function QueryUtils.setPositionXYInQuery(query, x, y) - local point = query.position - point[1] = x - point[2] = y -end - -function QueryUtils.setAreaInQuery(query, topLeftPosition, size) - local area = query.area - area[1][1] = topLeftPosition.x - area[1][2] = topLeftPosition.y - area[2][1] = topLeftPosition.x + size - area[2][2] = topLeftPosition.y + size -end - -function QueryUtils.setAreaInQueryChunkSize(query, topLeftPosition) - local area = query.area - area[1][1] = topLeftPosition.x - area[1][2] = topLeftPosition.y - area[2][1] = topLeftPosition.x + CHUNK_SIZE - area[2][2] = topLeftPosition.y + CHUNK_SIZE -end - -function QueryUtils.setPointAreaInQuery(query, position, size) - local area = query.area - area[1][1] = position.x - size - area[1][2] = position.y - size - area[2][1] = position.x + size - area[2][2] = position.y + size -end - -function QueryUtils.setAreaYInQuery(query, y1, y2) - local area = query.area - area[1][2] = y1 - area[2][2] = y2 -end - -function QueryUtils.setAreaXInQuery(query, x1, x2) - local area = query.area - area[1][1] = x1 - area[2][1] = x2 -end - -QueryUtilsG = QueryUtils -return QueryUtils diff --git a/libs/Squad.lua b/libs/Squad.lua new file mode 100644 index 0000000..8d2e452 --- /dev/null +++ b/libs/Squad.lua @@ -0,0 +1,1270 @@ +-- 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 SquadG then + return SquadG +end +local Squad = {} + +-- + +local Universe + +-- imports + +local Constants = require("Constants") +local MapUtils = require("MapUtils") +local BaseUtils = require("BaseUtils") +local MathUtils = require("MathUtils") +local ChunkPropertyUtils = require("ChunkPropertyUtils") +local Utils = require("Utils") + +-- Constants + +local BASE_AI_STATE_ONSLAUGHT = Constants.BASE_AI_STATE_ONSLAUGHT +local BASE_AI_STATE_RAIDING = Constants.BASE_AI_STATE_RAIDING +local MAGIC_MAXIMUM_NUMBER = Constants.MAGIC_MAXIMUM_NUMBER +local MINIMUM_EXPANSION_DISTANCE = Constants.MINIMUM_EXPANSION_DISTANCE +local PLAYER_PHEROMONE_GENERATOR_THRESHOLD = Constants.PLAYER_PHEROMONE_GENERATOR_THRESHOLD +local COMMAND_TIMEOUT = Constants.COMMAND_TIMEOUT +local PLAYER_PHEROMONE = Constants.PLAYER_PHEROMONE +local BASE_PHEROMONE = Constants.BASE_PHEROMONE +local ENEMY_PHEROMONE = Constants.ENEMY_PHEROMONE +local RESOURCE_PHEROMONE = Constants.RESOURCE_PHEROMONE + +local FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT = Constants.FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT + +local SQUAD_BUILDING = Constants.SQUAD_BUILDING + +local SQUAD_RAIDING = Constants.SQUAD_RAIDING +local SQUAD_SETTLING = Constants.SQUAD_SETTLING +local SQUAD_GUARDING = Constants.SQUAD_GUARDING +local SQUAD_RETREATING = Constants.SQUAD_RETREATING + +local BASE_AI_STATE_SIEGE = Constants.BASE_AI_STATE_SIEGE +local BASE_AI_STATE_AGGRESSIVE = Constants.BASE_AI_STATE_AGGRESSIVE + +local PLAYER_PHEROMONE_MULTIPLER = Constants.PLAYER_PHEROMONE_MULTIPLER + +local DEFINES_DISTRACTION_NONE = defines.distraction.none +local DEFINES_DISTRACTION_BY_ENEMY = defines.distraction.by_enemy +local DEFINES_DISTRACTION_BY_ANYTHING = defines.distraction.by_anything +local COOLDOWN_RETREAT = Constants.COOLDOWN_RETREAT + +local CHUNK_SIZE = Constants.CHUNK_SIZE +local COOLDOWN_RALLY = Constants.COOLDOWN_RALLY +local RALLY_CRY_DISTANCE = Constants.RALLY_CRY_DISTANCE + +local AI_SQUAD_COST = Constants.AI_SQUAD_COST +local AI_SETTLER_COST = Constants.AI_SETTLER_COST +local AI_VENGENCE_SQUAD_COST = Constants.AI_VENGENCE_SQUAD_COST +local CHUNK_ALL_DIRECTIONS = Constants.CHUNK_ALL_DIRECTIONS + +-- imported functions + +local getPassable = ChunkPropertyUtils.getPassable +local getRallyTick = ChunkPropertyUtils.getRallyTick +local setRallyTick = ChunkPropertyUtils.setRallyTick +local positionFromDirectionAndChunk = MapUtils.positionFromDirectionAndChunk +local modifyBaseUnitPoints = BaseUtils.modifyBaseUnitPoints + +local tableRemove = table.remove +local tableInsert = table.insert +local mCeil = math.ceil + +local isActiveNest = ChunkPropertyUtils.isActiveNest +local isActiveRaidNest = ChunkPropertyUtils.isActiveRaidNest + +local gaussianRandomRangeRG = MathUtils.gaussianRandomRangeRG +local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount +local getRetreatTick = ChunkPropertyUtils.getRetreatTick + +local findNearbyBase = ChunkPropertyUtils.findNearbyBase +local tableSize = table_size +local setPositionInCommand = Utils.setPositionInCommand + +local euclideanDistancePoints = MathUtils.euclideanDistancePoints +local canMoveChunkDirection = MapUtils.canMoveChunkDirection + +local setRetreatTick = ChunkPropertyUtils.setRetreatTick + +local removeSquadFromChunk = ChunkPropertyUtils.removeSquadFromChunk +local addDeathGenerator = ChunkPropertyUtils.addDeathGenerator + +local getNeighborChunks = MapUtils.getNeighborChunks +local addSquadToChunk = ChunkPropertyUtils.addSquadToChunk +local getChunkByXY = MapUtils.getChunkByXY +local positionToChunkXY = MapUtils.positionToChunkXY +local positionFromDirectionAndFlat = MapUtils.positionFromDirectionAndFlat + +local euclideanDistanceNamed = MathUtils.euclideanDistanceNamed + +-- module code +local function scoreRetreatLocation(map, neighborChunk) + return (-neighborChunk[BASE_PHEROMONE] + + -(neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) + + -((neighborChunk.playerBaseGenerator or 0) * 1000)) +end + +local function scoreResourceLocation(map, neighborChunk) + return neighborChunk[RESOURCE_PHEROMONE] + - (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) + - neighborChunk[ENEMY_PHEROMONE] +end + +local function scoreSiegeLocation(map, neighborChunk) + local settle = neighborChunk[BASE_PHEROMONE] + + neighborChunk[RESOURCE_PHEROMONE] * 0.5 + + (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) + + return settle - neighborChunk[ENEMY_PHEROMONE] +end + +local function scoreAttackLocation(map, neighborChunk) + local damage = neighborChunk[BASE_PHEROMONE] + + (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) + return damage +end + +local function findMovementPosition(surface, position) + local pos = position + pos = surface.find_non_colliding_position("behemoth-biter", pos, 10, 2, false) + return pos +end + +local function calculateSettlerMaxDistance() + local targetDistance + local distanceRoll = Universe.random() + if distanceRoll < 0.05 then + return 0 + elseif distanceRoll < 0.30 then + targetDistance = Universe.expansionLowTargetDistance + elseif distanceRoll < 0.70 then + targetDistance = Universe.expansionMediumTargetDistance + elseif distanceRoll < 0.95 then + targetDistance = Universe.expansionHighTargetDistance + else + return Universe.expansionMaxDistance + end + return gaussianRandomRangeRG(targetDistance, + Universe.expansionDistanceDeviation, + MINIMUM_EXPANSION_DISTANCE, + Universe.expansionMaxDistance, + Universe.random) +end + +local function addMovementPenalty(squad, chunk) + if (chunk == -1) then + return + end + local penalties = squad.penalties + local penaltyCount = #penalties + for i=1,penaltyCount do + local penalty = penalties[i] + if (penalty.c.id == chunk.id) then + penalty.v = penalty.v + 1 + if penalty.v >= 15 then + if Universe.enabledMigration and + (Universe.builderCount < Universe.AI_MAX_BUILDER_COUNT) then + squad.settler = true + squad.originPosition.x = squad.group.position.x + squad.originPosition.y = squad.group.position.y + squad.maxDistance = calculateSettlerMaxDistance() + + squad.status = SQUAD_SETTLING + else + squad.group.destroy() + end + end + return + end + end + if (penaltyCount == 10) then + tableRemove(penalties, 10) + end + tableInsert(penalties, + 1, + { v = 1, + c = chunk }) +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +function scoreNeighborsForSettling(map, chunk, neighborDirectionChunks, scoreFunction) + local highestChunk = -1 + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection = 0 + + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= -1) then + if (chunk == -1) or canMoveChunkDirection(map, x, chunk, neighborChunk) then + local score = scoreFunction(map, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + end + + if (chunk ~= -1) and (scoreFunction(map, chunk) > highestScore) then + return chunk, 0, -1, 0 + end + + local nextHighestChunk = -1 + local nextHighestScore = highestScore + local nextHighestDirection = 0 + + if (highestChunk ~= -1) then + neighborDirectionChunks = getNeighborChunks(map, highestChunk.x, highestChunk.y) + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if ((neighborChunk ~= -1) and ((chunk == -1) or (neighborChunk.id ~= chunk.id)) and + canMoveChunkDirection(map, x, highestChunk, neighborChunk)) then + local score = scoreFunction(map, neighborChunk) + if (score > nextHighestScore) then + nextHighestScore = score + nextHighestChunk = neighborChunk + nextHighestDirection = x + end + end + end + end + + return highestChunk, highestDirection, nextHighestChunk, nextHighestDirection +end + +local function settleMove(map, squad) + local group = squad.group + local targetPosition = {x=0,y=0} + + local groupPosition = group.position + local x, y = positionToChunkXY(groupPosition) + local chunk = getChunkByXY(map, x, y) + local scoreFunction = scoreResourceLocation + if (squad.type == BASE_AI_STATE_SIEGE) then + scoreFunction = scoreSiegeLocation + end + local squadChunk = squad.chunk + if squadChunk ~= -1 then + addDeathGenerator(squadChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + end + if chunk ~= -1 then + addSquadToChunk(chunk, squad) + addMovementPenalty(squad, chunk) + if not squad.group.valid then + return + end + end + local distance = euclideanDistancePoints(groupPosition.x, + groupPosition.y, + squad.originPosition.x, + squad.originPosition.y) + local cmd + local position + local surface = map.surface + + if (chunk ~= -1) and + ( + (distance >= squad.maxDistance) or + ( + chunk.resourceGenerator and (not chunk.nestCount) and (not chunk.hiveCount) + ) + ) + then + position = findMovementPosition(surface, groupPosition) + + if not position then + position = groupPosition + end + + cmd = Universe.settleCommand + if squad.kamikaze then + cmd.distraction = DEFINES_DISTRACTION_NONE + else + cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY + end + + setPositionInCommand(cmd, position) + + squad.status = SQUAD_BUILDING + + group.set_command(cmd) + else + local attackChunk, + attackDirection, + nextAttackChunk, + nextAttackDirection = scoreNeighborsForSettling(map, + chunk, + getNeighborChunks(map, x, y), + scoreFunction) + + if (attackChunk == -1) then + cmd = Universe.wanderCommand + group.set_command(cmd) + return + elseif (attackDirection ~= 0) then + local attackPlayerThreshold = Universe.attackPlayerThreshold + + if (nextAttackChunk ~= -1) then + if (not nextAttackChunk.playerBaseGenerator) + and ((nextAttackChunk.playerGenerator or 0) < PLAYER_PHEROMONE_GENERATOR_THRESHOLD) + then + attackChunk = nextAttackChunk + position = findMovementPosition( + surface, + positionFromDirectionAndFlat( + nextAttackDirection, + positionFromDirectionAndFlat( + attackDirection, + groupPosition + ) + ) + ) + else + position = groupPosition + end + else + position = findMovementPosition( + surface, + positionFromDirectionAndFlat( + attackDirection, + groupPosition + ) + ) + end + + if position then + targetPosition.x = position.x + targetPosition.y = position.y + if nextAttackChunk ~= -1 then + addDeathGenerator(nextAttackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + else + addDeathGenerator(attackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + end + else + cmd = Universe.wanderCommand + group.set_command(cmd) + return + end + + if (nextAttackChunk ~= -1) + and ( + nextAttackChunk.playerBaseGenerator + or ((nextAttackChunk.playerBaseGenerator or 0) >= PLAYER_PHEROMONE_GENERATOR_THRESHOLD) + ) + then + cmd = Universe.settleCommand + squad.status = SQUAD_BUILDING + if squad.kamikaze then + cmd.distraction = DEFINES_DISTRACTION_NONE + else + cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY + end + elseif attackChunk.playerBaseGenerator + or (attackChunk[PLAYER_PHEROMONE] >= attackPlayerThreshold) + then + cmd = Universe.attackCommand + + if not squad.rabid then + squad.frenzy = true + squad.frenzyPosition.x = groupPosition.x + squad.frenzyPosition.y = groupPosition.y + end + else + cmd = Universe.moveCommand + if squad.rabid or squad.kamikaze then + cmd.distraction = DEFINES_DISTRACTION_NONE + else + cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY + end + end + else + cmd = Universe.settleCommand + targetPosition.x = groupPosition.x + targetPosition.y = groupPosition.y + + if squad.kamikaze then + cmd.distraction = DEFINES_DISTRACTION_NONE + else + cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY + end + + squad.status = SQUAD_BUILDING + end + + setPositionInCommand(cmd, targetPosition) + + group.set_command(cmd) + end +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +local function scoreNeighborsForAttack(map, chunk, neighborDirectionChunks, scoreFunction) + local highestChunk = -1 + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= -1) then + if (chunk == -1) or canMoveChunkDirection(map, x, chunk, neighborChunk) then + local score = scoreFunction(map, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + end + + local nextHighestChunk = -1 + local nextHighestScore = highestScore + local nextHighestDirection + + if (highestChunk ~= -1) then + neighborDirectionChunks = getNeighborChunks(map, highestChunk.x, highestChunk.y) + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if ((neighborChunk ~= -1) and ((chunk == -1) or (neighborChunk.id ~= chunk.id)) and + canMoveChunkDirection(map, x, highestChunk, neighborChunk)) then + local score = scoreFunction(map, neighborChunk) + if (score > nextHighestScore) then + nextHighestScore = score + nextHighestChunk = neighborChunk + nextHighestDirection = x + end + end + end + end + + return highestChunk, highestDirection, nextHighestChunk, nextHighestDirection +end + +local function attackMove(map, squad) + local targetPosition = {0,0} + + local group = squad.group + + local surface = map.surface + local position + local groupPosition = group.position + local x, y = positionToChunkXY(groupPosition) + local chunk = getChunkByXY(map, x, y) + local attackScorer = scoreAttackLocation + local squadChunk = squad.chunk + if squadChunk ~= -1 then + addDeathGenerator(squadChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + end + if chunk ~= -1 then + addSquadToChunk(chunk, squad) + addMovementPenalty(squad, chunk) + if not squad.group.valid then + return + end + end + squad.frenzy = (squad.frenzy and (euclideanDistanceNamed(groupPosition, squad.frenzyPosition) < 100)) + local attackChunk, attackDirection, + nextAttackChunk, nextAttackDirection = scoreNeighborsForAttack(map, + chunk, + getNeighborChunks(map, x, y), + attackScorer) + local cmd + if (attackChunk == -1) then + cmd = Universe.wanderCommand + group.set_command(cmd) + return + end + + if (nextAttackChunk ~= -1) then + attackChunk = nextAttackChunk + position = findMovementPosition( + surface, + positionFromDirectionAndFlat( + nextAttackDirection, + positionFromDirectionAndFlat( + attackDirection, + groupPosition + ) + ) + ) + else + position = findMovementPosition( + surface, + positionFromDirectionAndFlat( + attackDirection, + groupPosition + ) + ) + end + + if not position then + cmd = Universe.wanderCommand + group.set_command(cmd) + return + else + targetPosition.x = position.x + targetPosition.y = position.y + if (nextAttackChunk ~= -1) then + addDeathGenerator(nextAttackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + else + addDeathGenerator(attackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + end + end + + if attackChunk.playerBaseGenerator and + (attackChunk[PLAYER_PHEROMONE] >= Universe.attackPlayerThreshold) + then + cmd = Universe.attackCommand + + if not squad.rabid then + squad.frenzy = true + squad.frenzyPosition.x = groupPosition.x + squad.frenzyPosition.y = groupPosition.y + end + else + cmd = Universe.moveCommand + if squad.rabid or squad.frenzy then + cmd.distraction = DEFINES_DISTRACTION_BY_ANYTHING + else + cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY + end + end + setPositionInCommand(cmd, targetPosition) + + group.set_command(cmd) +end + +local function buildMove(map, squad) + local group = squad.group + local groupPosition = group.position + local newGroupPosition = findMovementPosition(map.surface, groupPosition) + + if not newGroupPosition then + setPositionInCommand(Universe.settleCommand, groupPosition) + else + setPositionInCommand(Universe.settleCommand, newGroupPosition) + end + + group.set_command(Universe.compoundSettleCommand) +end + +function Squad.cleanSquads(tick) + local squads = Universe.groupNumberToSquad + local groupId = Universe.squadIterator + local squad + if not groupId then + groupId, squad = next(squads, groupId) + else + squad = squads[groupId] + end + if not groupId then + Universe.squadIterator = nil + if (tableSize(squads) == 0) then + -- this is needed as the next command remembers the max length a table has been + Universe.groupNumberToSquad = {} + end + else + Universe.squadIterator = next(squads, groupId) + local group = squad.group + if not group.valid then + if squad.chunk ~= -1 then + addDeathGenerator(squad.chunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) + end + removeSquadFromChunk(squad) + if squad.settlers then + Universe.builderCount = Universe.builderCount - 1 + if Universe.builderCount < 0 then + Universe.builderCount = 0 + end + else + Universe.squadCount = Universe.squadCount - 1 + if Universe.squadCount < 0 then + Universe.squadCount = 0 + end + if squad.type == BASE_AI_STATE_AGGRESSIVE then + local base = squad.base + base.sentAggressiveGroups = base.sentAggressiveGroups - 1 + if base.sentAggressiveGroups < 0 then + base.sentAggressiveGroups = 0 + end + end + end + squads[groupId] = nil + elseif (group.state == 4) then + squad.wanders = 0 + Squad.squadDispatch(squad.map, squad, tick) + elseif (squad.commandTick and (squad.commandTick < tick)) then + if squad.wanders > 5 then + squad.group.destroy() + else + squad.wanders = squad.wanders + 1 + local cmd = Universe.wander2Command + squad.commandTick = tick + COMMAND_TIMEOUT + group.set_command(cmd) + group.start_moving() + end + end + end +end + +function Squad.squadDispatch(map, squad, tick) + local group = squad.group + if group and group.valid then + local status = squad.status + if (status == SQUAD_RAIDING) then + squad.commandTick = tick + COMMAND_TIMEOUT + attackMove(map, squad) + elseif (status == SQUAD_SETTLING) then + squad.commandTick = tick + COMMAND_TIMEOUT + settleMove(map, squad) + elseif (status == SQUAD_RETREATING) then + squad.commandTick = tick + COMMAND_TIMEOUT + if squad.settlers then + squad.status = SQUAD_SETTLING + settleMove(map, squad) + else + squad.status = SQUAD_RAIDING + attackMove(map, squad) + end + elseif (status == SQUAD_BUILDING) then + squad.commandTick = tick + COMMAND_TIMEOUT + removeSquadFromChunk(squad) + buildMove(map, squad) + elseif (status == SQUAD_GUARDING) then + squad.commandTick = tick + COMMAND_TIMEOUT + if squad.settlers then + squad.status = SQUAD_SETTLING + settleMove(map, squad) + else + squad.status = SQUAD_RAIDING + attackMove(map, squad) + end + end + end +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +local function scoreNeighborsForRetreat(chunk, neighborDirectionChunks, scoreFunction, map) + local highestChunk = -1 + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= -1) then + if (chunk == -1) or canMoveChunkDirection(map, x, chunk, neighborChunk) then + local score = scoreFunction(map, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + end + + local nextHighestChunk = -1 + local nextHighestScore = highestScore + local nextHighestDirection + + if (highestChunk ~= -1) then + neighborDirectionChunks = getNeighborChunks(map, highestChunk.x, highestChunk.y) + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + + if ((neighborChunk ~= -1) and ((chunk == -1) or (neighborChunk.id ~= chunk.id)) and + canMoveChunkDirection(map, x, highestChunk, neighborChunk)) then + local score = scoreFunction(map, neighborChunk) + if (score > nextHighestScore) then + nextHighestScore = score + nextHighestChunk = neighborChunk + nextHighestDirection = x + end + end + end + end + + if (nextHighestChunk == nil) then + nextHighestChunk = -1 + end + + return highestChunk, highestDirection, nextHighestChunk, nextHighestDirection +end + +local function findNearbyRetreatingSquad(map, chunk) + if chunk.squads then + for _,squad in pairs(chunk.squads) do + local unitGroup = squad.group + if (squad.status == SQUAD_RETREATING) and unitGroup and unitGroup.valid then + return squad + end + end + end + + local neighbors = getNeighborChunks(map, chunk.x, chunk.y) + + for i=1,#neighbors do + local neighbor = neighbors[i] + if (neighbor ~= -1) and neighbor.squads then + for _,squad in pairs(neighbor.squads) do + local unitGroup = squad.group + if (squad.status == SQUAD_RETREATING) and unitGroup and unitGroup.valid then + return squad + end + end + end + end + return nil +end + +function Squad.retreatUnits(chunk, cause, map, tick, radius) + if (tick - getRetreatTick(chunk) > COOLDOWN_RETREAT) and (getEnemyStructureCount(chunk) == 0) then + + setRetreatTick(chunk, tick) + local exitPath,exitDirection, + nextExitPath,nextExitDirection = scoreNeighborsForRetreat(chunk, + getNeighborChunks(map, + chunk.x, + chunk.y), + scoreRetreatLocation, + map) + local position = { + x = chunk.x + 16, + y = chunk.y + 16 + } + local retreatPosition + local surface = map.surface + if (exitPath == -1) then + return + elseif (nextExitPath ~= -1) then + retreatPosition = findMovementPosition( + surface, + positionFromDirectionAndFlat( + nextExitDirection, + positionFromDirectionAndFlat( + exitDirection, + position + ) + ) + ) + exitPath = nextExitPath + else + retreatPosition = findMovementPosition( + surface, + positionFromDirectionAndFlat( + exitDirection, + position + ) + ) + end + + if retreatPosition then + position.x = retreatPosition.x + position.y = retreatPosition.y + else + return + end + + local newSquad = findNearbyRetreatingSquad(map, exitPath) + local created = false + + if not newSquad then + if (Universe.squadCount < Universe.AI_MAX_SQUAD_COUNT) then + created = true + local base = findNearbyBase(chunk) + if not base then + return + end + newSquad = Squad.createSquad(position, map, nil, false, base) + else + return + end + end + + Universe.fleeCommand.from = cause + Universe.retreatCommand.group = newSquad.group + + Universe.formRetreatCommand.unit_search_distance = radius + + local foundUnits = surface.set_multi_command(Universe.formRetreatCommand) + + if (foundUnits == 0) then + if created then + newSquad.group.destroy() + end + return + end + + if created then + Universe.groupNumberToSquad[newSquad.groupNumber] = newSquad + Universe.squadCount = Universe.squadCount + 1 + end + + newSquad.status = SQUAD_RETREATING + + addSquadToChunk(chunk, newSquad) + + newSquad.frenzy = true + local squadPosition = newSquad.group.position + newSquad.frenzyPosition.x = squadPosition.x + newSquad.frenzyPosition.y = squadPosition.y + end +end + +function Squad.createSquad(position, map, group, settlers, base) + local unitGroup = group or map.surface.create_unit_group({position=position}) + + local squad = { + group = unitGroup, + status = SQUAD_GUARDING, + rabid = false, + penalties = {}, + base = base, + type = base.stateAI, + frenzy = false, + map = map, + wanders = 0, + settlers = settlers or false, + kamikaze = false, + frenzyPosition = {x = 0, + y = 0}, + maxDistance = 0, + groupNumber = unitGroup.group_number, + originPosition = {x = 0, + y = 0}, + commandTick = nil, + chunk = -1 + } + + if settlers then + squad.maxDistance = calculateSettlerMaxDistance() + end + + if position then + squad.originPosition.x = position.x + squad.originPosition.y = position.y + elseif group then + squad.originPosition.x = group.position.x + squad.originPosition.y = group.position.y + end + + return squad +end + +function Squad.calculateKamikazeSquadThreshold(memberCount) + local threshold = (memberCount / Universe.attackWaveMaxSize) * 0.2 + (Universe.evolutionLevel * 0.2) + return threshold +end + +function Squad.calculateKamikazeSettlerThreshold(memberCount) + local threshold = (memberCount / Universe.expansionMaxSize) * 0.2 + (Universe.evolutionLevel * 0.2) + return threshold +end + + +local function settlerWaveScaling() + return mCeil(gaussianRandomRangeRG(Universe.settlerWaveSize, + Universe.settlerWaveDeviation, + Universe.expansionMinSize, + Universe.expansionMaxSize, + Universe.random)) +end + +local function attackWaveScaling() + return mCeil(gaussianRandomRangeRG(Universe.attackWaveSize, + Universe.attackWaveDeviation, + 1, + Universe.attackWaveUpperBound, + Universe.random)) +end + +local function attackWaveValidCandidate(chunk, base) + if isActiveNest(chunk) then + return true + end + if (base.stateAI == BASE_AI_STATE_RAIDING) or + (base.stateAI == BASE_AI_STATE_SIEGE) or + (base.stateAI == BASE_AI_STATE_ONSLAUGHT) + then + return isActiveRaidNest(chunk) + end + return false +end + +local function scoreSettlerLocation(map, neighborChunk) + return neighborChunk[RESOURCE_PHEROMONE] + -neighborChunk[PLAYER_PHEROMONE] +end + +local function scoreSiegeSettlerLocation(map, neighborChunk) + return (neighborChunk[RESOURCE_PHEROMONE] + neighborChunk[BASE_PHEROMONE]) + -neighborChunk[PLAYER_PHEROMONE] +end + +local function scoreUnitGroupLocation(map, neighborChunk) + return neighborChunk[PLAYER_PHEROMONE] + neighborChunk[BASE_PHEROMONE] +end + +local function validSiegeSettlerLocation(map, neighborChunk) + return (getPassable(neighborChunk) == CHUNK_ALL_DIRECTIONS) and + (not neighborChunk.nestCount) +end + +local function validSettlerLocation(map, chunk, neighborChunk) + local chunkResource = chunk[RESOURCE_PHEROMONE] + return (getPassable(neighborChunk) == CHUNK_ALL_DIRECTIONS) and + (not neighborChunk.nestCount) and + (neighborChunk[RESOURCE_PHEROMONE] >= chunkResource) +end + +local function validUnitGroupLocation(map, neighborChunk) + return getPassable(neighborChunk) == CHUNK_ALL_DIRECTIONS and + (not neighborChunk.nestCount) +end + +local function visitPattern(o, cX, cY, distance) + local startX + local endX + local stepX + local startY + local endY + local stepY + if (o == 0) then + startX = cX - distance + endX = cX + distance + stepX = 32 + startY = cY - distance + endY = cY + distance + stepY = 32 + elseif (o == 1) then + startX = cX + distance + endX = cX - distance + stepX = -32 + startY = cY + distance + endY = cY - distance + stepY = -32 + elseif (o == 2) then + startX = cX - distance + endX = cX + distance + stepX = 32 + startY = cY + distance + endY = cY - distance + stepY = -32 + elseif (o == 3) then + startX = cX + distance + endX = cX - distance + stepX = -32 + startY = cY - distance + endY = cY + distance + stepY = 32 + end + return startX, endX, stepX, startY, endY, stepY +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +local function scoreNeighborsForResource(chunk, neighborDirectionChunks, validFunction, scoreFunction, map) + local highestChunk = -1 + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= -1) and + canMoveChunkDirection(map, x, chunk, neighborChunk) and + validFunction(map, chunk, neighborChunk) + then + local score = scoreFunction(map, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + + if (chunk ~= -1) and (scoreFunction(map, chunk) > highestScore) then + return -1, -1 + end + + return highestChunk, highestDirection +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +local function scoreNeighborsForFormation(neighborChunks, validFunction, scoreFunction, map) + local highestChunk = -1 + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + for x=1,8 do + local neighborChunk = neighborChunks[x] + if (neighborChunk ~= -1) and validFunction(map, neighborChunk) then + local score = scoreFunction(map, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + + return highestChunk, highestDirection +end + +function Squad.rallyUnits(chunk, tick, base) + if ((tick - getRallyTick(chunk) > COOLDOWN_RALLY) and (base.unitPoints >= AI_VENGENCE_SQUAD_COST)) then + setRallyTick(chunk, tick) + local cX = chunk.x + local cY = chunk.y + local startX, endX, stepX, startY, endY, stepY = visitPattern(tick % 4, cX, cY, RALLY_CRY_DISTANCE) + local vengenceQueue = Universe.vengenceQueue + local map = chunk.map + for x=startX, endX, stepX do + for y=startY, endY, stepY do + if (x ~= cX) and (y ~= cY) then + local rallyChunk = getChunkByXY(map, x, y) + if (rallyChunk ~= -1) and rallyChunk.nestCount then + vengenceQueue[rallyChunk.id] = rallyChunk + end + end + end + end + + return true + end +end + +function Squad.formSettlers(chunk, base) + if (Universe.builderCount < Universe.AI_MAX_BUILDER_COUNT) + and (base.sentExpansionGroups < base.maxExpansionGroups) + and ((base.unitPoints - AI_SETTLER_COST) > 0) + and (Universe.random() < Universe.formSquadThreshold) + then + local map = chunk.map + local surface = map.surface + local squadPath, squadDirection + if (base.stateAI == BASE_AI_STATE_SIEGE) then + squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), + validSiegeSettlerLocation, + scoreSiegeSettlerLocation, + map) + else + squadPath, squadDirection = scoreNeighborsForResource(chunk, + getNeighborChunks(map, chunk.x, chunk.y), + validSettlerLocation, + scoreSettlerLocation, + map) + end + + if (squadPath ~= -1) then + local squadPosition = surface.find_non_colliding_position("biter-spawner", + positionFromDirectionAndChunk(squadDirection, + chunk, + 0.98), + CHUNK_SIZE, + 4, + true) + if squadPosition then + local squad = Squad.createSquad(squadPosition, map, nil, true, base) + + local scaledWaveSize = settlerWaveScaling() + Universe.formGroupCommand.group = squad.group + Universe.formCommand.unit_count = scaledWaveSize + local foundUnits = surface.set_multi_command(Universe.formCommand) + if (foundUnits > 0) then + base.sentExpansionGroups = base.sentExpansionGroups + 1 + + squad.base = base + local kamikazeThreshold = Squad.calculateKamikazeSettlerThreshold(foundUnits) + if base.stateAI == BASE_AI_STATE_SIEGE then + kamikazeThreshold = kamikazeThreshold * 2.5 + end + squad.kamikaze = Universe.random() < kamikazeThreshold + + Universe.builderCount = Universe.builderCount + 1 + modifyBaseUnitPoints(base, -AI_SETTLER_COST, "Settler", squadPosition.x, squadPosition.y) + Universe.groupNumberToSquad[squad.groupNumber] = squad + else + if (squad.group.valid) then + squad.group.destroy() + end + end + end + end + end +end + +function Squad.formVengenceSquad(chunk, base) + if (Universe.squadCount < Universe.AI_MAX_SQUAD_COUNT) + and ((base.unitPoints - AI_VENGENCE_SQUAD_COST) > 0) + and (Universe.random() < Universe.formSquadThreshold) + then + if (chunk[BASE_PHEROMONE] < 0.0001) or (chunk[PLAYER_PHEROMONE] < 0.0001) then + return + end + local map = chunk.map + + local surface = map.surface + local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), + validUnitGroupLocation, + scoreUnitGroupLocation, + map) + if (squadPath ~= -1) then + local squadPosition = surface.find_non_colliding_position("biter-spawner", + positionFromDirectionAndChunk(squadDirection, + chunk, + 0.98), + CHUNK_SIZE, + 4, + true) + if squadPosition then + local squad = Squad.createSquad(squadPosition, map, nil, false, base) + + squad.rabid = Universe.random() < 0.03 + + local scaledWaveSize = attackWaveScaling() + Universe.formGroupCommand.group = squad.group + Universe.formCommand.unit_count = scaledWaveSize + local foundUnits = surface.set_multi_command(Universe.formCommand) + if (foundUnits > 0) then + squad.base = base + squad.kamikaze = Universe.random() < Squad.calculateKamikazeSquadThreshold(foundUnits) + Universe.groupNumberToSquad[squad.groupNumber] = squad + Universe.squadCount = Universe.squadCount + 1 + modifyBaseUnitPoints(base, -AI_VENGENCE_SQUAD_COST, "Vengence", squadPosition.x, squadPosition.y) + else + if (squad.group.valid) then + squad.group.destroy() + end + end + end + end + end +end + +function Squad.formVengenceSettler(chunk, base) + if (Universe.builderCount < Universe.AI_MAX_BUILDER_COUNT) + and (base.sentExpansionGroups < base.maxExpansionGroups) + and ((base.unitPoints - AI_VENGENCE_SQUAD_COST) > 0) + and (Universe.random() < Universe.formSquadThreshold) + then + local map = chunk.map + local surface = map.surface + local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), + validUnitGroupLocation, + scoreUnitGroupLocation, + map) + if (squadPath ~= -1) then + local squadPosition = surface.find_non_colliding_position("biter-spawner", + positionFromDirectionAndChunk(squadDirection, + chunk, + 0.98), + CHUNK_SIZE, + 4, + true) + if squadPosition then + local squad = Squad.createSquad(squadPosition, map, nil, true, base) + + squad.rabid = Universe.random() < 0.03 + + local scaledWaveSize = settlerWaveScaling() + Universe.formGroupCommand.group = squad.group + Universe.formCommand.unit_count = scaledWaveSize + local foundUnits = surface.set_multi_command(Universe.formCommand) + if (foundUnits > 0) then + base.sentExpansionGroups = base.sentExpansionGroups + 1 + + squad.base = base + squad.kamikaze = Universe.random() < Squad.calculateKamikazeSettlerThreshold(foundUnits) + Universe.groupNumberToSquad[squad.groupNumber] = squad + Universe.builderCount = Universe.builderCount + 1 + modifyBaseUnitPoints(base, -AI_VENGENCE_SQUAD_COST, "Vengence Settlers", squadPosition.x, squadPosition.y) + else + if (squad.group.valid) then + squad.group.destroy() + end + end + end + end + end +end + +function Squad.formSquads(chunk, base) + if (Universe.squadCount < Universe.AI_MAX_SQUAD_COUNT) + and attackWaveValidCandidate(chunk, base) + and ((base.unitPoints - AI_SQUAD_COST) > 0) + and (Universe.random() < Universe.formSquadThreshold) + then + if (chunk[BASE_PHEROMONE] < 0.0001) or (chunk[PLAYER_PHEROMONE] < 0.0001) then + return + end + + local map = chunk.map + local surface = map.surface + local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(map, chunk.x, chunk.y), + validUnitGroupLocation, + scoreUnitGroupLocation, + map) + if (squadPath ~= -1) then + local squadPosition = surface.find_non_colliding_position("biter-spawner", + positionFromDirectionAndChunk(squadDirection, + chunk, + 0.98), + CHUNK_SIZE, + 4, + true) + if squadPosition then + local squad = Squad.createSquad(squadPosition, map, nil, false, base) + + squad.rabid = Universe.random() < 0.03 + + local scaledWaveSize = attackWaveScaling() + Universe.formGroupCommand.group = squad.group + Universe.formCommand.unit_count = scaledWaveSize + local foundUnits = surface.set_multi_command(Universe.formCommand) + if (foundUnits > 0) then + squad.base = base + squad.kamikaze = Universe.random() < Squad.calculateKamikazeSquadThreshold(foundUnits) + Universe.squadCount = Universe.squadCount + 1 + Universe.groupNumberToSquad[squad.groupNumber] = squad + if (base.stateAI == BASE_AI_STATE_AGGRESSIVE) then + base.sentAggressiveGroups = base.sentAggressiveGroups + 1 + end + modifyBaseUnitPoints(base, -AI_SQUAD_COST, "Squad", squadPosition.x, squadPosition.y) + else + if (squad.group.valid) then + squad.group.destroy() + end + end + end + end + end +end + +function Squad.init(universe) + Universe = universe +end + +SquadG = Squad +return Squad diff --git a/libs/SquadAttack.lua b/libs/SquadAttack.lua deleted file mode 100644 index 9d7cafc..0000000 --- a/libs/SquadAttack.lua +++ /dev/null @@ -1,480 +0,0 @@ --- 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 SquadAttackG then - return SquadAttackG -end -local SquadAttack = {} - --- - -local Universe - --- imports - -local Constants = require("Constants") -local MapUtils = require("MapUtils") -local MovementUtils = require("MovementUtils") -local MathUtils = require("MathUtils") -local ChunkPropertyUtils = require("ChunkPropertyUtils") -local QueryUtils = require("QueryUtils") - --- Constants - -local PLAYER_PHEROMONE_GENERATOR_THRESHOLD = Constants.PLAYER_PHEROMONE_GENERATOR_THRESHOLD -local COMMAND_TIMEOUT = Constants.COMMAND_TIMEOUT -local PLAYER_PHEROMONE = Constants.PLAYER_PHEROMONE -local BASE_PHEROMONE = Constants.BASE_PHEROMONE -local ENEMY_PHEROMONE = Constants.ENEMY_PHEROMONE -local RESOURCE_PHEROMONE = Constants.RESOURCE_PHEROMONE - -local FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT = Constants.FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT - -local SQUAD_BUILDING = Constants.SQUAD_BUILDING - -local SQUAD_RAIDING = Constants.SQUAD_RAIDING -local SQUAD_SETTLING = Constants.SQUAD_SETTLING -local SQUAD_GUARDING = Constants.SQUAD_GUARDING -local SQUAD_RETREATING = Constants.SQUAD_RETREATING - -local BASE_AI_STATE_SIEGE = Constants.BASE_AI_STATE_SIEGE -local BASE_AI_STATE_AGGRESSIVE = Constants.BASE_AI_STATE_AGGRESSIVE - -local PLAYER_PHEROMONE_MULTIPLER = Constants.PLAYER_PHEROMONE_MULTIPLER - -local DEFINES_DISTRACTION_NONE = defines.distraction.none -local DEFINES_DISTRACTION_BY_ENEMY = defines.distraction.by_enemy -local DEFINES_DISTRACTION_BY_ANYTHING = defines.distraction.by_anything - --- imported functions - -local tableSize = table_size -local setPositionInCommand = QueryUtils.setPositionInCommand - -local euclideanDistancePoints = MathUtils.euclideanDistancePoints - -local findMovementPosition = MovementUtils.findMovementPosition - -local removeSquadFromChunk = ChunkPropertyUtils.removeSquadFromChunk -local addDeathGenerator = ChunkPropertyUtils.addDeathGenerator - -local getNeighborChunks = MapUtils.getNeighborChunks -local addSquadToChunk = ChunkPropertyUtils.addSquadToChunk -local getChunkByXY = MapUtils.getChunkByXY -local positionToChunkXY = MapUtils.positionToChunkXY -local addMovementPenalty = MovementUtils.addMovementPenalty -local positionFromDirectionAndFlat = MapUtils.positionFromDirectionAndFlat - -local euclideanDistanceNamed = MathUtils.euclideanDistanceNamed - -local scoreNeighborsForAttack = MovementUtils.scoreNeighborsForAttack -local scoreNeighborsForSettling = MovementUtils.scoreNeighborsForSettling - --- module code -local function scoreResourceLocation(map, neighborChunk) - return neighborChunk[RESOURCE_PHEROMONE] - - (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) - - neighborChunk[ENEMY_PHEROMONE] -end - -local function scoreSiegeLocation(map, neighborChunk) - local settle = neighborChunk[BASE_PHEROMONE] - + neighborChunk[RESOURCE_PHEROMONE] * 0.5 - + (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) - - return settle - neighborChunk[ENEMY_PHEROMONE] -end - -local function scoreAttackLocation(map, neighborChunk) - local damage = neighborChunk[BASE_PHEROMONE] + - (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) - return damage -end - -local function settleMove(map, squad) - local group = squad.group - local targetPosition = {x=0,y=0} - - local groupPosition = group.position - local x, y = positionToChunkXY(groupPosition) - local chunk = getChunkByXY(map, x, y) - local scoreFunction = scoreResourceLocation - if (squad.type == BASE_AI_STATE_SIEGE) then - scoreFunction = scoreSiegeLocation - end - local squadChunk = squad.chunk - if squadChunk ~= -1 then - addDeathGenerator(squadChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - end - if chunk ~= -1 then - addSquadToChunk(chunk, squad) - addMovementPenalty(squad, chunk) - if not squad.group.valid then - return - end - end - local distance = euclideanDistancePoints(groupPosition.x, - groupPosition.y, - squad.originPosition.x, - squad.originPosition.y) - local cmd - local position - local surface = map.surface - - if (chunk ~= -1) and - ( - (distance >= squad.maxDistance) or - ( - chunk.resourceGenerator and (not chunk.nestCount) and (not chunk.hiveCount) - ) - ) - then - position = findMovementPosition(surface, groupPosition) - - if not position then - position = groupPosition - end - - cmd = Universe.settleCommand - if squad.kamikaze then - cmd.distraction = DEFINES_DISTRACTION_NONE - else - cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY - end - - setPositionInCommand(cmd, position) - - squad.status = SQUAD_BUILDING - - group.set_command(cmd) - else - local attackChunk, - attackDirection, - nextAttackChunk, - nextAttackDirection = scoreNeighborsForSettling(map, - chunk, - getNeighborChunks(map, x, y), - scoreFunction) - - if (attackChunk == -1) then - cmd = Universe.wanderCommand - group.set_command(cmd) - return - elseif (attackDirection ~= 0) then - local attackPlayerThreshold = Universe.attackPlayerThreshold - - if (nextAttackChunk ~= -1) then - if (not nextAttackChunk.playerBaseGenerator) - and ((nextAttackChunk.playerGenerator or 0) < PLAYER_PHEROMONE_GENERATOR_THRESHOLD) - then - attackChunk = nextAttackChunk - position = findMovementPosition( - surface, - positionFromDirectionAndFlat( - nextAttackDirection, - positionFromDirectionAndFlat( - attackDirection, - groupPosition - ) - ) - ) - else - position = groupPosition - end - else - position = findMovementPosition( - surface, - positionFromDirectionAndFlat( - attackDirection, - groupPosition - ) - ) - end - - if position then - targetPosition.x = position.x - targetPosition.y = position.y - if nextAttackChunk ~= -1 then - addDeathGenerator(nextAttackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - else - addDeathGenerator(attackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - end - else - cmd = Universe.wanderCommand - group.set_command(cmd) - return - end - - if (nextAttackChunk ~= -1) - and ( - nextAttackChunk.playerBaseGenerator - or ((nextAttackChunk.playerBaseGenerator or 0) >= PLAYER_PHEROMONE_GENERATOR_THRESHOLD) - ) - then - cmd = Universe.settleCommand - squad.status = SQUAD_BUILDING - if squad.kamikaze then - cmd.distraction = DEFINES_DISTRACTION_NONE - else - cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY - end - elseif attackChunk.playerBaseGenerator - or (attackChunk[PLAYER_PHEROMONE] >= attackPlayerThreshold) - then - cmd = Universe.attackCommand - - if not squad.rabid then - squad.frenzy = true - squad.frenzyPosition.x = groupPosition.x - squad.frenzyPosition.y = groupPosition.y - end - else - cmd = Universe.moveCommand - if squad.rabid or squad.kamikaze then - cmd.distraction = DEFINES_DISTRACTION_NONE - else - cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY - end - end - else - cmd = Universe.settleCommand - targetPosition.x = groupPosition.x - targetPosition.y = groupPosition.y - - if squad.kamikaze then - cmd.distraction = DEFINES_DISTRACTION_NONE - else - cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY - end - - squad.status = SQUAD_BUILDING - end - - setPositionInCommand(cmd, targetPosition) - - group.set_command(cmd) - end -end - -local function attackMove(map, squad) - local targetPosition = {0,0} - - local group = squad.group - - local surface = map.surface - local position - local groupPosition = group.position - local x, y = positionToChunkXY(groupPosition) - local chunk = getChunkByXY(map, x, y) - local attackScorer = scoreAttackLocation - local squadChunk = squad.chunk - if squadChunk ~= -1 then - addDeathGenerator(squadChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - end - if chunk ~= -1 then - addSquadToChunk(chunk, squad) - addMovementPenalty(squad, chunk) - if not squad.group.valid then - return - end - end - squad.frenzy = (squad.frenzy and (euclideanDistanceNamed(groupPosition, squad.frenzyPosition) < 100)) - local attackChunk, attackDirection, - nextAttackChunk, nextAttackDirection = scoreNeighborsForAttack(map, - chunk, - getNeighborChunks(map, x, y), - attackScorer) - local cmd - if (attackChunk == -1) then - cmd = Universe.wanderCommand - group.set_command(cmd) - return - end - - if (nextAttackChunk ~= -1) then - attackChunk = nextAttackChunk - position = findMovementPosition( - surface, - positionFromDirectionAndFlat( - nextAttackDirection, - positionFromDirectionAndFlat( - attackDirection, - groupPosition - ) - ) - ) - else - position = findMovementPosition( - surface, - positionFromDirectionAndFlat( - attackDirection, - groupPosition - ) - ) - end - - if not position then - cmd = Universe.wanderCommand - group.set_command(cmd) - return - else - targetPosition.x = position.x - targetPosition.y = position.y - if (nextAttackChunk ~= -1) then - addDeathGenerator(nextAttackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - else - addDeathGenerator(attackChunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - end - end - - if attackChunk.playerBaseGenerator and - (attackChunk[PLAYER_PHEROMONE] >= Universe.attackPlayerThreshold) - then - cmd = Universe.attackCommand - - if not squad.rabid then - squad.frenzy = true - squad.frenzyPosition.x = groupPosition.x - squad.frenzyPosition.y = groupPosition.y - end - else - cmd = Universe.moveCommand - if squad.rabid or squad.frenzy then - cmd.distraction = DEFINES_DISTRACTION_BY_ANYTHING - else - cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY - end - end - setPositionInCommand(cmd, targetPosition) - - group.set_command(cmd) -end - -local function buildMove(map, squad) - local group = squad.group - local groupPosition = group.position - local newGroupPosition = findMovementPosition(map.surface, groupPosition) - - if not newGroupPosition then - setPositionInCommand(Universe.settleCommand, groupPosition) - else - setPositionInCommand(Universe.settleCommand, newGroupPosition) - end - - group.set_command(Universe.compoundSettleCommand) -end - -function SquadAttack.cleanSquads(tick) - local squads = Universe.groupNumberToSquad - local groupId = Universe.squadIterator - local squad - if not groupId then - groupId, squad = next(squads, groupId) - else - squad = squads[groupId] - end - if not groupId then - Universe.squadIterator = nil - if (tableSize(squads) == 0) then - -- this is needed as the next command remembers the max length a table has been - Universe.groupNumberToSquad = {} - end - else - Universe.squadIterator = next(squads, groupId) - local group = squad.group - if not group.valid then - if squad.chunk ~= -1 then - addDeathGenerator(squad.chunk, -FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT) - end - removeSquadFromChunk(squad) - if squad.settlers then - Universe.builderCount = Universe.builderCount - 1 - if Universe.builderCount < 0 then - Universe.builderCount = 0 - end - else - Universe.squadCount = Universe.squadCount - 1 - if Universe.squadCount < 0 then - Universe.squadCount = 0 - end - if squad.type == BASE_AI_STATE_AGGRESSIVE then - local base = squad.base - base.sentAggressiveGroups = base.sentAggressiveGroups - 1 - if base.sentAggressiveGroups < 0 then - base.sentAggressiveGroups = 0 - end - end - end - squads[groupId] = nil - elseif (group.state == 4) then - squad.wanders = 0 - SquadAttack.squadDispatch(squad.map, squad, tick) - elseif (squad.commandTick and (squad.commandTick < tick)) then - if squad.wanders > 5 then - squad.group.destroy() - else - squad.wanders = squad.wanders + 1 - local cmd = Universe.wander2Command - squad.commandTick = tick + COMMAND_TIMEOUT - group.set_command(cmd) - group.start_moving() - end - end - end -end - -function SquadAttack.squadDispatch(map, squad, tick) - local group = squad.group - if group and group.valid then - local status = squad.status - if (status == SQUAD_RAIDING) then - squad.commandTick = tick + COMMAND_TIMEOUT - attackMove(map, squad) - elseif (status == SQUAD_SETTLING) then - squad.commandTick = tick + COMMAND_TIMEOUT - settleMove(map, squad) - elseif (status == SQUAD_RETREATING) then - squad.commandTick = tick + COMMAND_TIMEOUT - if squad.settlers then - squad.status = SQUAD_SETTLING - settleMove(map, squad) - else - squad.status = SQUAD_RAIDING - attackMove(map, squad) - end - elseif (status == SQUAD_BUILDING) then - squad.commandTick = tick + COMMAND_TIMEOUT - removeSquadFromChunk(squad) - buildMove(map, squad) - elseif (status == SQUAD_GUARDING) then - squad.commandTick = tick + COMMAND_TIMEOUT - if squad.settlers then - squad.status = SQUAD_SETTLING - settleMove(map, squad) - else - squad.status = SQUAD_RAIDING - attackMove(map, squad) - end - end - end -end - -function SquadAttack.init(universe) - Universe = universe -end - -SquadAttackG = SquadAttack -return SquadAttack diff --git a/libs/SquadDefense.lua b/libs/SquadDefense.lua deleted file mode 100644 index a154d50..0000000 --- a/libs/SquadDefense.lua +++ /dev/null @@ -1,169 +0,0 @@ --- 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 AiDefenseG then - return AiDefenseG -end -local AiDefense = {} - --- - -local Universe - --- imports - -local Constants = require("Constants") -local MapUtils = require("MapUtils") -local UnitGroupUtils = require("UnitGroupUtils") -local MovementUtils = require("MovementUtils") -local ChunkPropertyUtils = require("ChunkPropertyUtils") - --- Constants - -local PLAYER_PHEROMONE = Constants.PLAYER_PHEROMONE -local BASE_PHEROMONE = Constants.BASE_PHEROMONE - -local PLAYER_PHEROMONE_MULTIPLER = Constants.PLAYER_PHEROMONE_MULTIPLER - -local SQUAD_RETREATING = Constants.SQUAD_RETREATING - -local COOLDOWN_RETREAT = Constants.COOLDOWN_RETREAT - --- imported functions - -local findNearbyBase = ChunkPropertyUtils.findNearbyBase - -local addSquadToChunk = ChunkPropertyUtils.addSquadToChunk - -local positionFromDirectionAndFlat = MapUtils.positionFromDirectionAndFlat -local getNeighborChunks = MapUtils.getNeighborChunks -local findNearbyRetreatingSquad = UnitGroupUtils.findNearbyRetreatingSquad -local createSquad = UnitGroupUtils.createSquad -local scoreNeighborsForRetreat = MovementUtils.scoreNeighborsForRetreat -local findMovementPosition = MovementUtils.findMovementPosition - -local getRetreatTick = ChunkPropertyUtils.getRetreatTick -local setRetreatTick = ChunkPropertyUtils.setRetreatTick -local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount - --- module code - -local function scoreRetreatLocation(map, neighborChunk) - return (-neighborChunk[BASE_PHEROMONE] + - -(neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) + - -((neighborChunk.playerBaseGenerator or 0) * 1000)) -end - -function AiDefense.retreatUnits(chunk, cause, map, tick, radius) - if (tick - getRetreatTick(chunk) > COOLDOWN_RETREAT) and (getEnemyStructureCount(chunk) == 0) then - - setRetreatTick(chunk, tick) - local exitPath,exitDirection, - nextExitPath,nextExitDirection = scoreNeighborsForRetreat(chunk, - getNeighborChunks(map, - chunk.x, - chunk.y), - scoreRetreatLocation, - map) - local position = { - x = chunk.x + 16, - y = chunk.y + 16 - } - local retreatPosition - local surface = map.surface - if (exitPath == -1) then - return - elseif (nextExitPath ~= -1) then - retreatPosition = findMovementPosition( - surface, - positionFromDirectionAndFlat( - nextExitDirection, - positionFromDirectionAndFlat( - exitDirection, - position - ) - ) - ) - exitPath = nextExitPath - else - retreatPosition = findMovementPosition( - surface, - positionFromDirectionAndFlat( - exitDirection, - position - ) - ) - end - - if retreatPosition then - position.x = retreatPosition.x - position.y = retreatPosition.y - else - return - end - - local newSquad = findNearbyRetreatingSquad(map, exitPath) - local created = false - - if not newSquad then - if (Universe.squadCount < Universe.AI_MAX_SQUAD_COUNT) then - created = true - local base = findNearbyBase(chunk) - if not base then - return - end - newSquad = createSquad(position, map, nil, false, base) - else - return - end - end - - Universe.fleeCommand.from = cause - Universe.retreatCommand.group = newSquad.group - - Universe.formRetreatCommand.unit_search_distance = radius - - local foundUnits = surface.set_multi_command(Universe.formRetreatCommand) - - if (foundUnits == 0) then - if created then - newSquad.group.destroy() - end - return - end - - if created then - Universe.groupNumberToSquad[newSquad.groupNumber] = newSquad - Universe.squadCount = Universe.squadCount + 1 - end - - newSquad.status = SQUAD_RETREATING - - addSquadToChunk(chunk, newSquad) - - newSquad.frenzy = true - local squadPosition = newSquad.group.position - newSquad.frenzyPosition.x = squadPosition.x - newSquad.frenzyPosition.y = squadPosition.y - end -end - -function AiDefense.init(universe) - Universe = universe -end - -AiDefenseG = AiDefense -return AiDefense diff --git a/libs/StringUtils.lua b/libs/StringUtils.lua deleted file mode 100644 index a3ae877..0000000 --- a/libs/StringUtils.lua +++ /dev/null @@ -1,60 +0,0 @@ --- 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 stringUtilsG then - return stringUtilsG -end -local stringUtils = {} - -local sSub = string.sub -local sGMatch = string.gmatch - -function stringUtils.isRampantSetting(str) - return sSub(str, 1, #"rampant--") == "rampant--" -end - -function stringUtils.split(str) - local result = {} - for i in sGMatch(str, "[a-zA-Z-]+") do - result[#result+1] = i - end - return result -end - -function stringUtils.isMember(str, set) - for _,s in pairs(set) do - if str == s then - return true - end - end - return false -end - -function stringUtils.intersection(set1, set2) - local result = {} - for s1 in pairs(set1) do - for s2 in pairs(set2) do - if s1 == s2 then - result[#result+1] = s1 - break - end - end - end - return result -end - -stringUtilsG = stringUtils -return stringUtils diff --git a/libs/UnitGroupUtils.lua b/libs/UnitGroupUtils.lua deleted file mode 100644 index 1ca6fe0..0000000 --- a/libs/UnitGroupUtils.lua +++ /dev/null @@ -1,178 +0,0 @@ --- 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 UnitGroupUtilsG then - return UnitGroupUtilsG -end -local UnitGroupUtils = {} - --- - -local Universe - --- imports - -local MapUtils = require("MapUtils") -local Constants = require("Constants") -local ChunkPropertyUtils = require("ChunkPropertyUtils") -local MathUtils = require("MathUtils") - --- Constants - -local MINIMUM_EXPANSION_DISTANCE = Constants.MINIMUM_EXPANSION_DISTANCE -local SQUAD_RETREATING = Constants.SQUAD_RETREATING -local SQUAD_GUARDING = Constants.SQUAD_GUARDING - --- imported functions - -local gaussianRandomRangeRG = MathUtils.gaussianRandomRangeRG - -local getNeighborChunks = MapUtils.getNeighborChunks - --- module code - -function UnitGroupUtils.findNearbyRetreatingSquad(map, chunk) - - if chunk.squads then - for _,squad in pairs(chunk.squads) do - local unitGroup = squad.group - if (squad.status == SQUAD_RETREATING) and unitGroup and unitGroup.valid then - return squad - end - end - end - - local neighbors = getNeighborChunks(map, chunk.x, chunk.y) - - for i=1,#neighbors do - local neighbor = neighbors[i] - if (neighbor ~= -1) and neighbor.squads then - for _,squad in pairs(neighbor.squads) do - local unitGroup = squad.group - if (squad.status == SQUAD_RETREATING) and unitGroup and unitGroup.valid then - return squad - end - end - end - end - return nil -end - -function UnitGroupUtils.findNearbySquad(map, chunk) - - if chunk.squads then - for _,squad in pairs(chunk.squads) do - local unitGroup = squad.group - if unitGroup and unitGroup.valid then - return squad - end - end - end - - local neighbors = getNeighborChunks(map, chunk.x, chunk.y) - - for i=1,#neighbors do - local neighbor = neighbors[i] - if (neighbor ~= -1) and neighbor.squads then - for _,squad in pairs(neighbor.squads) do - local unitGroup = squad.group - if unitGroup and unitGroup.valid then - return squad - end - end - end - end - - return nil -end - -function UnitGroupUtils.calculateSettlerMaxDistance() - local targetDistance - local distanceRoll = Universe.random() - if distanceRoll < 0.05 then - return 0 - elseif distanceRoll < 0.30 then - targetDistance = Universe.expansionLowTargetDistance - elseif distanceRoll < 0.70 then - targetDistance = Universe.expansionMediumTargetDistance - elseif distanceRoll < 0.95 then - targetDistance = Universe.expansionHighTargetDistance - else - return Universe.expansionMaxDistance - end - return gaussianRandomRangeRG(targetDistance, - Universe.expansionDistanceDeviation, - MINIMUM_EXPANSION_DISTANCE, - Universe.expansionMaxDistance, - Universe.random) -end - -function UnitGroupUtils.createSquad(position, map, group, settlers, base) - local unitGroup = group or map.surface.create_unit_group({position=position}) - - local squad = { - group = unitGroup, - status = SQUAD_GUARDING, - rabid = false, - penalties = {}, - base = base, - type = base.stateAI, - frenzy = false, - map = map, - wanders = 0, - settlers = settlers or false, - kamikaze = false, - frenzyPosition = {x = 0, - y = 0}, - maxDistance = 0, - groupNumber = unitGroup.group_number, - originPosition = {x = 0, - y = 0}, - commandTick = nil, - chunk = -1 - } - - if settlers then - squad.maxDistance = UnitGroupUtils.calculateSettlerMaxDistance() - end - - if position then - squad.originPosition.x = position.x - squad.originPosition.y = position.y - elseif group then - squad.originPosition.x = group.position.x - squad.originPosition.y = group.position.y - end - - return squad -end - -function UnitGroupUtils.calculateKamikazeSquadThreshold(memberCount) - local threshold = (memberCount / Universe.attackWaveMaxSize) * 0.2 + (Universe.evolutionLevel * 0.2) - return threshold -end - -function UnitGroupUtils.calculateKamikazeSettlerThreshold(memberCount) - local threshold = (memberCount / Universe.expansionMaxSize) * 0.2 + (Universe.evolutionLevel * 0.2) - return threshold -end - -function UnitGroupUtils.init(universe) - Universe = universe -end - -UnitGroupUtilsG = UnitGroupUtils -return UnitGroupUtils diff --git a/libs/Upgrade.lua b/libs/Upgrade.lua index e3aa017..0452866 100644 --- a/libs/Upgrade.lua +++ b/libs/Upgrade.lua @@ -19,7 +19,7 @@ local Upgrade = {} -- imports local Constants = require("libs/Constants") -local ChunkProcessor = require("libs/ChunkProcessor") +local Processor = require("libs/Processor") local ChunkPropertyUtils = require("libs/ChunkPropertyUtils") local MapUtils = require("libs/MapUtils") @@ -58,7 +58,7 @@ local TICKS_A_MINUTE = Constants.TICKS_A_MINUTE local addBaseResourceChunk = ChunkPropertyUtils.addBaseResourceChunk local sFind = string.find local queueGeneratedChunk = MapUtils.queueGeneratedChunk -local processPendingChunks = ChunkProcessor.processPendingChunks +local processPendingChunks = Processor.processPendingChunks -- module code diff --git a/libs/Utils.lua b/libs/Utils.lua new file mode 100644 index 0000000..e7e6fc8 --- /dev/null +++ b/libs/Utils.lua @@ -0,0 +1,146 @@ +-- 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 UtilsG then + return UtilsG +end +local Utils = {} + +-- + +local Constants = require("Constants") + +-- + +local CHUNK_SIZE = Constants.CHUNK_SIZE + +-- + +local mFloor = math.floor +local sSub = string.sub +local sGMatch = string.gmatch + +-- + +function Utils.isRampantSetting(str) + return sSub(str, 1, #"rampant--") == "rampant--" +end + +function Utils.split(str) + local result = {} + for i in sGMatch(str, "[a-zA-Z-]+") do + result[#result+1] = i + end + return result +end + +function Utils.isMember(str, set) + for _,s in pairs(set) do + if str == s then + return true + end + end + return false +end + +function Utils.intersection(set1, set2) + local result = {} + for s1 in pairs(set1) do + for s2 in pairs(set2) do + if s1 == s2 then + result[#result+1] = s1 + break + end + end + end + return result +end + +function Utils.setPositionInQuery(query, position) + local point = query.position + point[1] = position.x + point[2] = position.y +end + +function Utils.setPositionInCommand(cmd, position) + local point = cmd.destination + point[1] = position.x + point[2] = position.y +end + +function Utils.setPositionXYInQuery(query, x, y) + local point = query.position + point[1] = x + point[2] = y +end + +function Utils.setAreaInQuery(query, topLeftPosition, size) + local area = query.area + area[1][1] = topLeftPosition.x + area[1][2] = topLeftPosition.y + area[2][1] = topLeftPosition.x + size + area[2][2] = topLeftPosition.y + size +end + +function Utils.setAreaInQueryChunkSize(query, topLeftPosition) + local area = query.area + area[1][1] = topLeftPosition.x + area[1][2] = topLeftPosition.y + area[2][1] = topLeftPosition.x + CHUNK_SIZE + area[2][2] = topLeftPosition.y + CHUNK_SIZE +end + +function Utils.setPointAreaInQuery(query, position, size) + local area = query.area + area[1][1] = position.x - size + area[1][2] = position.y - size + area[2][1] = position.x + size + area[2][2] = position.y + size +end + +function Utils.setAreaYInQuery(query, y1, y2) + local area = query.area + area[1][2] = y1 + area[2][2] = y2 +end + +function Utils.setAreaXInQuery(query, x1, x2) + local area = query.area + area[1][1] = x1 + area[2][1] = x2 +end + +function Utils.validPlayer(player) + if player and player.valid then + local char = player.character + return char and char.valid + end + return false +end + +function Utils.getTimeStringFromTick(tick) + + local tickToSeconds = tick / 60 + + local days = mFloor(tickToSeconds / 86400) + local hours = mFloor((tickToSeconds % 86400) / 3600) + local minutes = mFloor((tickToSeconds % 3600) / 60) + local seconds = mFloor(tickToSeconds % 60) + return days .. "d " .. hours .. "h " .. minutes .. "m " .. seconds .. "s" +end + +UtilsG = Utils +return Utils diff --git a/tests.lua b/tests.lua index b2500b8..1867f0b 100644 --- a/tests.lua +++ b/tests.lua @@ -22,7 +22,7 @@ local chunkUtils = require("libs/ChunkUtils") local chunkPropertyUtils = require("libs/ChunkPropertyUtils") local mapUtils = require("libs/MapUtils") local baseUtils = require("libs/BaseUtils") -local queryUtils = require("libs/QueryUtils") +local Utils = require("libs/Utils") -- local tendrilUtils = require("libs/TendrilUtils") function tests.chunkCount() @@ -364,7 +364,7 @@ function tests.scanEnemy() local chunk = mapUtils.getChunkByPosition(map, game.player.character.position) local universe = map.universe local query = universe.filteredEntitiesEnemyStructureQuery - queryUtils.setAreaInQuery(query, chunk, constants.CHUNK_SIZE) + Utils.setAreaInQuery(query, chunk, constants.CHUNK_SIZE) local buildings = map.surface.find_entities_filtered(query) local counts = map.chunkScanCounts for i=1,#constants.HIVE_BUILDINGS_TYPES do