1
0
mirror of https://github.com/veden/Rampant.git synced 2025-09-16 09:16:43 +02:00

FACTO-258: Consolidated most libraries

This commit is contained in:
Aaron Veden
2023-03-11 20:51:13 -08:00
parent a7cdf98ce2
commit e143f4bbbe
22 changed files with 2428 additions and 2988 deletions

View File

@@ -63,6 +63,8 @@ Version: 3.2.0
- Reduced corpse and particle variation to optimize increased entity times due to high entity counts - Reduced corpse and particle variation to optimize increased entity times due to high entity counts
- Removed unneeded iterators - Removed unneeded iterators
- Moved map properties directly into chunk object - Moved map properties directly into chunk object
Framework:
- Consolidated most libraries
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
Version: 3.1.2 Version: 3.1.2

View File

@@ -18,24 +18,15 @@
local ChunkPropertyUtils = require("libs/ChunkPropertyUtils") local ChunkPropertyUtils = require("libs/ChunkPropertyUtils")
local UnitUtils = require("libs/UnitUtils") local UnitUtils = require("libs/UnitUtils")
local BaseUtils = require("libs/BaseUtils") local BaseUtils = require("libs/BaseUtils")
local MapUtils = require("libs/MapUtils")
local MathUtils = require("libs/MathUtils") local MathUtils = require("libs/MathUtils")
local UnitGroupUtils = require("libs/UnitGroupUtils") local Processor = require("libs/Processor")
local ChunkProcessor = require("libs/ChunkProcessor")
local MapProcessor = require("libs/MapProcessor")
local Constants = require("libs/Constants") local Constants = require("libs/Constants")
local PheromoneUtils = require("libs/PheromoneUtils") local MapUtils = require("libs/MapUtils")
local SquadDefense = require("libs/SquadDefense") local Squad = require("libs/Squad")
local SquadAttack = require("libs/SquadAttack")
local AiAttackWave = require("libs/AIAttackWave")
local AiPlanning = require("libs/AIPlanning")
local MovementUtils = require("libs/MovementUtils")
local tests = require("tests") local tests = require("tests")
local ChunkUtils = require("libs/ChunkUtils") local ChunkUtils = require("libs/ChunkUtils")
local Upgrade = require("libs/Upgrade") local Upgrade = require("libs/Upgrade")
local AiPredicates = require("libs/AIPredicates") local Utils = require("libs/Utils")
local stringUtils = require("libs/StringUtils")
local queryUtils = require("libs/QueryUtils")
-- Constants -- Constants
@@ -68,18 +59,18 @@ local SETTLE_CLOUD_WARMUP = Constants.SETTLE_CLOUD_WARMUP
-- imported functions -- imported functions
local isMember = stringUtils.isMember local isMember = Utils.isMember
local split = stringUtils.split local split = Utils.split
local planning = AiPlanning.planning local planning = BaseUtils.planning
local addBasesToAllEnemyStructures = ChunkUtils.addBasesToAllEnemyStructures local addBasesToAllEnemyStructures = ChunkUtils.addBasesToAllEnemyStructures
local setPointAreaInQuery = queryUtils.setPointAreaInQuery local setPointAreaInQuery = Utils.setPointAreaInQuery
local nextMap = MapUtils.nextMap local nextMap = MapUtils.nextMap
local processClouds = MapProcessor.processClouds local processClouds = Processor.processClouds
local distortPosition = MathUtils.distortPosition local distortPosition = MathUtils.distortPosition
local linearInterpolation = MathUtils.linearInterpolation local linearInterpolation = MathUtils.linearInterpolation
@@ -88,26 +79,26 @@ local prepMap = Upgrade.prepMap
local findBaseInitialAlignment = BaseUtils.findBaseInitialAlignment local findBaseInitialAlignment = BaseUtils.findBaseInitialAlignment
local processBaseAIs = AiPlanning.processBaseAIs local processBaseAIs = BaseUtils.processBaseAIs
local registerEnemyBaseStructure = ChunkUtils.registerEnemyBaseStructure local registerEnemyBaseStructure = ChunkUtils.registerEnemyBaseStructure
local queueGeneratedChunk = MapUtils.queueGeneratedChunk local queueGeneratedChunk = MapUtils.queueGeneratedChunk
local isRampantSetting = stringUtils.isRampantSetting local isRampantSetting = Utils.isRampantSetting
local processPendingUpgrades = ChunkProcessor.processPendingUpgrades local processPendingUpgrades = Processor.processPendingUpgrades
local canMigrate = AiPredicates.canMigrate 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 positionToChunkXY = MapUtils.positionToChunkXY
local processVengence = MapProcessor.processVengence local processVengence = Processor.processVengence
local processAttackWaves = MapProcessor.processAttackWaves local processAttackWaves = Processor.processAttackWaves
local disperseVictoryScent = PheromoneUtils.disperseVictoryScent local disperseVictoryScent = MapUtils.disperseVictoryScent
local getChunkByPosition = MapUtils.getChunkByPosition local getChunkByPosition = MapUtils.getChunkByPosition
@@ -116,31 +107,31 @@ local getChunkByXY = MapUtils.getChunkByXY
local entityForPassScan = ChunkUtils.entityForPassScan local entityForPassScan = ChunkUtils.entityForPassScan
local processPendingChunks = ChunkProcessor.processPendingChunks local processPendingChunks = Processor.processPendingChunks
local processScanChunks = ChunkProcessor.processScanChunks local processScanChunks = Processor.processScanChunks
local processMap = MapProcessor.processMap local processMap = Processor.processMap
local processPlayers = MapProcessor.processPlayers local processPlayers = Processor.processPlayers
local scanEnemyMap = MapProcessor.scanEnemyMap local scanEnemyMap = Processor.scanEnemyMap
local scanPlayerMap = MapProcessor.scanPlayerMap local scanPlayerMap = Processor.scanPlayerMap
local scanResourceMap = MapProcessor.scanResourceMap 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 recycleBases = BaseUtils.recycleBases
local deathScent = PheromoneUtils.deathScent local deathScent = MapUtils.deathScent
local victoryScent = PheromoneUtils.victoryScent local victoryScent = MapUtils.victoryScent
local createSquad = UnitGroupUtils.createSquad local createSquad = Squad.createSquad
local createBase = BaseUtils.createBase local createBase = BaseUtils.createBase
local findNearbyBaseByPosition = ChunkPropertyUtils.findNearbyBaseByPosition local findNearbyBaseByPosition = ChunkPropertyUtils.findNearbyBaseByPosition
local findNearbyBase = ChunkPropertyUtils.findNearbyBase local findNearbyBase = ChunkPropertyUtils.findNearbyBase
local processActiveNests = MapProcessor.processActiveNests local processActiveNests = Processor.processActiveNests
local removeDrainPylons = ChunkPropertyUtils.removeDrainPylons local removeDrainPylons = ChunkPropertyUtils.removeDrainPylons
local getDrainPylonPair = ChunkPropertyUtils.getDrainPylonPair local getDrainPylonPair = ChunkPropertyUtils.getDrainPylonPair
@@ -151,7 +142,7 @@ local isDrained = ChunkPropertyUtils.isDrained
local setDrainedTick = ChunkPropertyUtils.setDrainedTick local setDrainedTick = ChunkPropertyUtils.setDrainedTick
local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating
local retreatUnits = SquadDefense.retreatUnits local retreatUnits = Squad.retreatUnits
local accountPlayerEntity = ChunkUtils.accountPlayerEntity local accountPlayerEntity = ChunkUtils.accountPlayerEntity
local unregisterEnemyBaseStructure = ChunkUtils.unregisterEnemyBaseStructure local unregisterEnemyBaseStructure = ChunkUtils.unregisterEnemyBaseStructure
@@ -160,7 +151,7 @@ local makeImmortalEntity = ChunkUtils.makeImmortalEntity
local registerResource = ChunkUtils.registerResource local registerResource = ChunkUtils.registerResource
local unregisterResource = ChunkUtils.unregisterResource local unregisterResource = ChunkUtils.unregisterResource
local cleanSquads = SquadAttack.cleanSquads local cleanSquads = Squad.cleanSquads
local queueUpgrade = BaseUtils.queueUpgrade local queueUpgrade = BaseUtils.queueUpgrade
@@ -221,20 +212,13 @@ local function hookEvents()
end end
local function initializeLibraries() local function initializeLibraries()
MapProcessor.init(Universe)
AiPlanning.init(Universe)
AiAttackWave.init(Universe)
AiPredicates.init(Universe)
BaseUtils.init(Universe) BaseUtils.init(Universe)
ChunkProcessor.init(Universe) Squad.init(Universe)
BaseUtils.init(Universe)
Processor.init(Universe)
ChunkPropertyUtils.init(Universe) ChunkPropertyUtils.init(Universe)
ChunkUtils.init(Universe) ChunkUtils.init(Universe)
MapUtils.init(Universe) MapUtils.init(Universe)
MovementUtils.init(Universe)
PheromoneUtils.init(Universe)
SquadAttack.init(Universe)
SquadDefense.init(Universe)
UnitGroupUtils.init(Universe)
end end
local function onLoad() local function onLoad()

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -25,57 +25,99 @@ local Universe
-- imports -- imports
local stringUtils = require("StringUtils") local Utils = require("Utils")
local mathUtils = require("MathUtils") local MathUtils = require("MathUtils")
local constants = require("Constants") local Constants = require("Constants")
local chunkPropertyUtils = require("ChunkPropertyUtils") local MapUtils = require("MapUtils")
local mapUtils = require("MapUtils")
local queryUtils = require("QueryUtils")
-- constants -- Constants
local TIERS = constants.TIERS local TIERS = Constants.TIERS
local EVO_TO_TIER_MAPPING = constants.EVO_TO_TIER_MAPPING local EVO_TO_TIER_MAPPING = Constants.EVO_TO_TIER_MAPPING
local PROXY_ENTITY_LOOKUP = constants.PROXY_ENTITY_LOOKUP local PROXY_ENTITY_LOOKUP = Constants.PROXY_ENTITY_LOOKUP
local BUILDING_HIVE_TYPE_LOOKUP = constants.BUILDING_HIVE_TYPE_LOOKUP local BUILDING_HIVE_TYPE_LOOKUP = Constants.BUILDING_HIVE_TYPE_LOOKUP
local COST_LOOKUP = constants.COST_LOOKUP local COST_LOOKUP = Constants.COST_LOOKUP
local UPGRADE_LOOKUP = constants.UPGRADE_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 EVOLUTION_TABLE_ALIGNMENT = Constants.EVOLUTION_TABLE_ALIGNMENT
local BUILDING_EVOLVE_LOOKUP = constants.BUILDING_EVOLVE_LOOKUP local BUILDING_EVOLVE_LOOKUP = Constants.BUILDING_EVOLVE_LOOKUP
local MINIMUM_BUILDING_COST = constants.MINIMUM_BUILDING_COST local BASE_AI_STATE_RAIDING = Constants.BASE_AI_STATE_RAIDING
local FACTION_MUTATION_MAPPING = constants.FACTION_MUTATION_MAPPING 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_GENERATION_STATE_ACTIVE = Constants.BASE_GENERATION_STATE_ACTIVE
local BASE_DISTANCE_LEVEL_BONUS = constants.BASE_DISTANCE_LEVEL_BONUS
local BASE_DISTANCE_TO_EVO_INDEX = constants.BASE_DISTANCE_TO_EVO_INDEX
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 -- 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 mFloor = math.floor
local mCeil = math.ceil
local tableSize = table_size
local tableRemove = table.remove local tableRemove = table.remove
local mMin = math.min local mMin = math.min
@@ -303,6 +345,50 @@ function BaseUtils.recycleBases()
end end
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) function BaseUtils.queueUpgrade(entity, base, disPos, evolve, register, timeDelay)
Universe.pendingUpgrades[entity.unit_number] = { Universe.pendingUpgrades[entity.unit_number] = {
["position"] = disPos, ["position"] = disPos,
@@ -402,7 +488,7 @@ local function pickMutationFromDamageType(damageType, roll, base)
Universe.MAX_BASE_MUTATIONS}) Universe.MAX_BASE_MUTATIONS})
end end
end end
local alignmentCount = table_size(base.alignmentHistory) local alignmentCount = tableSize(base.alignmentHistory)
while (alignmentCount > Universe.MAX_BASE_ALIGNMENT_HISTORY) do while (alignmentCount > Universe.MAX_BASE_ALIGNMENT_HISTORY) do
tableRemove(base.alignmentHistory, 1) tableRemove(base.alignmentHistory, 1)
alignmentCount = alignmentCount - 1 alignmentCount = alignmentCount - 1
@@ -604,6 +690,488 @@ function BaseUtils.modifyBaseSpecialPoints(base, points, tag, x, y)
end end
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) function BaseUtils.init(universe)
Universe = universe Universe = universe
end end

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -31,7 +31,7 @@ local Constants = require("Constants")
local MapUtils = require("MapUtils") local MapUtils = require("MapUtils")
local ChunkPropertyUtils = require("ChunkPropertyUtils") local ChunkPropertyUtils = require("ChunkPropertyUtils")
local MathUtils = require("MathUtils") local MathUtils = require("MathUtils")
local QueryUtils = require("QueryUtils") local Utils = require("Utils")
-- Constants -- Constants
@@ -73,9 +73,9 @@ local GENERATOR_PHEROMONE_LEVEL_6 = Constants.GENERATOR_PHEROMONE_LEVEL_6
local removeBaseResourceChunk = ChunkPropertyUtils.removeBaseResourceChunk local removeBaseResourceChunk = ChunkPropertyUtils.removeBaseResourceChunk
local addBaseResourceChunk = ChunkPropertyUtils.addBaseResourceChunk local addBaseResourceChunk = ChunkPropertyUtils.addBaseResourceChunk
local setAreaInQueryChunkSize = QueryUtils.setAreaInQueryChunkSize local setAreaInQueryChunkSize = Utils.setAreaInQueryChunkSize
local setAreaXInQuery = QueryUtils.setAreaXInQuery local setAreaXInQuery = Utils.setAreaXInQuery
local setAreaYInQuery = QueryUtils.setAreaYInQuery local setAreaYInQuery = Utils.setAreaYInQuery
local setPlayerBaseGenerator = ChunkPropertyUtils.setPlayerBaseGenerator local setPlayerBaseGenerator = ChunkPropertyUtils.setPlayerBaseGenerator
local addPlayerBaseGenerator = ChunkPropertyUtils.addPlayerBaseGenerator local addPlayerBaseGenerator = ChunkPropertyUtils.addPlayerBaseGenerator

View File

@@ -28,9 +28,24 @@ local NeighborChunks
local Constants = require("Constants") local Constants = require("Constants")
local ChunkPropertyUtils = require("ChunkPropertyUtils") local ChunkPropertyUtils = require("ChunkPropertyUtils")
local MathUtils = require("MathUtils")
-- Constants -- 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_NORTH_SOUTH = Constants.CHUNK_NORTH_SOUTH
local CHUNK_EAST_WEST = Constants.CHUNK_EAST_WEST local CHUNK_EAST_WEST = Constants.CHUNK_EAST_WEST
local CHUNK_IMPASSABLE = Constants.CHUNK_IMPASSABLE local CHUNK_IMPASSABLE = Constants.CHUNK_IMPASSABLE
@@ -42,6 +57,19 @@ local CHUNK_SIZE_DIVIDER = Constants.CHUNK_SIZE_DIVIDER
-- imported functions -- 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 mFloor = math.floor
local getPassable = ChunkPropertyUtils.getPassable local getPassable = ChunkPropertyUtils.getPassable
local tRemove = table.remove local tRemove = table.remove
@@ -355,6 +383,124 @@ function MapUtils.positionFromDirectionAndFlat(direction, startPosition, multipl
} }
end 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) function MapUtils.init(universe)
Universe = universe Universe = universe
NeighborChunks = universe.neighbors NeighborChunks = universe.neighbors

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -14,10 +14,10 @@
-- along with this program. If not, see <https://www.gnu.org/licenses/>. -- along with this program. If not, see <https://www.gnu.org/licenses/>.
if MapProcessorG then if ProcessorG then
return MapProcessorG return ProcessorG
end end
local MapProcessor = {} local Processor = {}
-- --
@@ -25,21 +25,18 @@ local Universe
-- imports -- imports
local QueryUtils = require("QueryUtils") local Utils = require("Utils")
local PheromoneUtils = require("PheromoneUtils")
local AiAttackWave = require("AIAttackWave")
local AiPredicates = require("AIPredicates")
local Constants = require("Constants")
local MapUtils = require("MapUtils") local MapUtils = require("MapUtils")
local PlayerUtils = require("PlayerUtils") local Squad = require("Squad")
local Constants = require("Constants")
local ChunkUtils = require("ChunkUtils") local ChunkUtils = require("ChunkUtils")
local ChunkPropertyUtils = require("ChunkPropertyUtils") local ChunkPropertyUtils = require("ChunkPropertyUtils")
local BaseUtils = require("BaseUtils") local BaseUtils = require("BaseUtils")
local MathUtils = require("MathUtils")
-- Constants -- Constants
local PLAYER_PHEROMONE_GENERATOR_AMOUNT = Constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT 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 PROCESS_QUEUE_SIZE = Constants.PROCESS_QUEUE_SIZE
local RESOURCE_QUEUE_SIZE = Constants.RESOURCE_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_DRAIN = Constants.COOLDOWN_DRAIN
local COOLDOWN_RALLY = Constants.COOLDOWN_RALLY local COOLDOWN_RALLY = Constants.COOLDOWN_RALLY
local COOLDOWN_RETREAT = Constants.COOLDOWN_RETREAT 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 -- 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 addPlayerGenerator = ChunkPropertyUtils.addPlayerGenerator
local findNearbyBase = ChunkPropertyUtils.findNearbyBase local findNearbyBase = ChunkPropertyUtils.findNearbyBase
local removeChunkToNest = MapUtils.removeChunkToNest local removeChunkToNest = MapUtils.removeChunkToNest
local processPheromone = PheromoneUtils.processPheromone local processPheromone = MapUtils.processPheromone
local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating local getCombinedDeathGeneratorRating = ChunkPropertyUtils.getCombinedDeathGeneratorRating
local processBaseMutation = BaseUtils.processBaseMutation local processBaseMutation = BaseUtils.processBaseMutation
local processNestActiveness = ChunkPropertyUtils.processNestActiveness local processNestActiveness = ChunkPropertyUtils.processNestActiveness
local formSquads = AiAttackWave.formSquads local formSquads = Squad.formSquads
local formVengenceSquad = AiAttackWave.formVengenceSquad local formVengenceSquad = Squad.formVengenceSquad
local formVengenceSettler = AiAttackWave.formVengenceSettler local formVengenceSettler = Squad.formVengenceSettler
local formSettlers = AiAttackWave.formSettlers local formSettlers = Squad.formSettlers
local getChunkByPosition = MapUtils.getChunkByPosition local getChunkByPosition = MapUtils.getChunkByPosition
local getChunkByXY = MapUtils.getChunkByXY local getChunkByXY = MapUtils.getChunkByXY
local getChunkById = MapUtils.getChunkById local getChunkById = MapUtils.getChunkById
local validPlayer = PlayerUtils.validPlayer local validPlayer = Utils.validPlayer
local mapScanEnemyChunk = ChunkUtils.mapScanEnemyChunk local mapScanEnemyChunk = ChunkUtils.mapScanEnemyChunk
local mapScanPlayerChunk = ChunkUtils.mapScanPlayerChunk local mapScanPlayerChunk = ChunkUtils.mapScanPlayerChunk
@@ -94,13 +107,14 @@ local mapScanResourceChunk = ChunkUtils.mapScanResourceChunk
local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount local getEnemyStructureCount = ChunkPropertyUtils.getEnemyStructureCount
local canAttack = AiPredicates.canAttack local canAttack = BaseUtils.canAttack
local canMigrate = AiPredicates.canMigrate local canMigrate = BaseUtils.canMigrate
local tableSize = table_size local tableSize = table_size
local mMin = math.min local mMin = math.min
local mMax = math.max local mMax = math.max
local tableInsert = table.insert
local next = next 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 In theory, this might be fine as smaller bases have less surface to attack and need to have
pheromone dissipate at a faster rate. pheromone dissipate at a faster rate.
--]] --]]
function MapProcessor.processMap(map, tick) function Processor.processMap(map, tick)
local processQueue = map.processQueue local processQueue = map.processQueue
local processQueueLength = #processQueue local processQueueLength = #processQueue
@@ -156,7 +170,7 @@ end
vs vs
the slower passive version processing the entire map in multiple passes. 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 -- put down player pheromone for player hunters
-- randomize player order to ensure a single player isn't singled out -- randomize player order to ensure a single player isn't singled out
-- not looping everyone because the cost is high enough already in multiplayer -- not looping everyone because the cost is high enough already in multiplayer
@@ -229,7 +243,7 @@ local function processCleanUp(chunks, tick, duration)
end end
end end
function MapProcessor.cleanUpMapTables(tick) function Processor.cleanUpMapTables(tick)
local retreats = Universe.chunkToRetreats local retreats = Universe.chunkToRetreats
local rallys = Universe.chunkToRallys local rallys = Universe.chunkToRallys
local drained = Universe.chunkToDrained local drained = Universe.chunkToDrained
@@ -246,7 +260,7 @@ end
--[[ --[[
Passive scan to find entities that have been generated outside the factorio event system 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 if (map.nextProcessMap == tick) or (map.nextPlayerScan == tick) or
(map.nextEnemyScan == tick) or (map.nextChunkProcess == tick) (map.nextEnemyScan == tick) or (map.nextChunkProcess == tick)
then then
@@ -274,7 +288,7 @@ function MapProcessor.scanPlayerMap(map, tick)
end end
end end
function MapProcessor.scanEnemyMap(map, tick) function Processor.scanEnemyMap(map, tick)
local index = map.scanEnemyIndex local index = map.scanEnemyIndex
local processQueue = map.processQueue local processQueue = map.processQueue
@@ -297,7 +311,7 @@ function MapProcessor.scanEnemyMap(map, tick)
end end
end end
function MapProcessor.scanResourceMap(map, tick) function Processor.scanResourceMap(map, tick)
local index = map.scanResourceIndex local index = map.scanResourceIndex
local processQueue = map.processQueue local processQueue = map.processQueue
@@ -320,7 +334,7 @@ function MapProcessor.scanResourceMap(map, tick)
end end
end end
function MapProcessor.processVengence() function Processor.processVengence()
local vengenceQueue = Universe.vengenceQueue local vengenceQueue = Universe.vengenceQueue
local chunkId, chunk = next(vengenceQueue, nil) local chunkId, chunk = next(vengenceQueue, nil)
if not chunkId then if not chunkId then
@@ -343,7 +357,7 @@ function MapProcessor.processVengence()
end end
end end
function MapProcessor.processNests(tick) function Processor.processNests(tick)
local chunkId = Universe.processNestIterator local chunkId = Universe.processNestIterator
local chunkPack local chunkPack
if not chunkId then if not chunkId then
@@ -383,7 +397,7 @@ local function processSpawnersBody(iterator, chunks)
Universe[iterator] = nil Universe[iterator] = nil
else else
Universe[iterator] = next(chunks, chunkId) Universe[iterator] = next(chunks, chunkId)
local map = chunkPack.map local map = chunkPack.map -- error
if not map.surface.valid then if not map.surface.valid then
if (iterator == "processMigrationIterator") then if (iterator == "processMigrationIterator") then
removeChunkToNest(chunkId) removeChunkToNest(chunkId)
@@ -411,8 +425,8 @@ local function processSpawnersBody(iterator, chunks)
end end
end end
local migrate = canMigrate(map, base) local migrate = canMigrate(base)
local attack = canAttack(map, base) local attack = canAttack(base)
if migrate then if migrate then
formSettlers(chunk, base) formSettlers(chunk, base)
end end
@@ -422,7 +436,7 @@ local function processSpawnersBody(iterator, chunks)
end end
end end
function MapProcessor.processAttackWaves() function Processor.processAttackWaves()
processSpawnersBody("processActiveSpawnerIterator", processSpawnersBody("processActiveSpawnerIterator",
Universe.chunkToActiveNest) Universe.chunkToActiveNest)
processSpawnersBody("processActiveRaidSpawnerIterator", processSpawnersBody("processActiveRaidSpawnerIterator",
@@ -431,7 +445,7 @@ function MapProcessor.processAttackWaves()
Universe.chunkToNests) Universe.chunkToNests)
end end
function MapProcessor.processClouds(tick) function Processor.processClouds(tick)
local eventId, builderPack = next(Universe.settlePurpleCloud, nil) local eventId, builderPack = next(Universe.settlePurpleCloud, nil)
if builderPack and (builderPack.tick <= tick) then if builderPack and (builderPack.tick <= tick) then
Universe.settlePurpleCloud[eventId] = nil Universe.settlePurpleCloud[eventId] = nil
@@ -446,9 +460,180 @@ function MapProcessor.processClouds(tick)
end end
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 Universe = universe
end end
MapProcessorG = MapProcessor ProcessorG = Processor
return MapProcessor return Processor

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

1270
libs/Squad.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -19,7 +19,7 @@ local Upgrade = {}
-- imports -- imports
local Constants = require("libs/Constants") local Constants = require("libs/Constants")
local ChunkProcessor = require("libs/ChunkProcessor") local Processor = require("libs/Processor")
local ChunkPropertyUtils = require("libs/ChunkPropertyUtils") local ChunkPropertyUtils = require("libs/ChunkPropertyUtils")
local MapUtils = require("libs/MapUtils") local MapUtils = require("libs/MapUtils")
@@ -58,7 +58,7 @@ local TICKS_A_MINUTE = Constants.TICKS_A_MINUTE
local addBaseResourceChunk = ChunkPropertyUtils.addBaseResourceChunk local addBaseResourceChunk = ChunkPropertyUtils.addBaseResourceChunk
local sFind = string.find local sFind = string.find
local queueGeneratedChunk = MapUtils.queueGeneratedChunk local queueGeneratedChunk = MapUtils.queueGeneratedChunk
local processPendingChunks = ChunkProcessor.processPendingChunks local processPendingChunks = Processor.processPendingChunks
-- module code -- module code

146
libs/Utils.lua Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
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

View File

@@ -22,7 +22,7 @@ local chunkUtils = require("libs/ChunkUtils")
local chunkPropertyUtils = require("libs/ChunkPropertyUtils") local chunkPropertyUtils = require("libs/ChunkPropertyUtils")
local mapUtils = require("libs/MapUtils") local mapUtils = require("libs/MapUtils")
local baseUtils = require("libs/BaseUtils") local baseUtils = require("libs/BaseUtils")
local queryUtils = require("libs/QueryUtils") local Utils = require("libs/Utils")
-- local tendrilUtils = require("libs/TendrilUtils") -- local tendrilUtils = require("libs/TendrilUtils")
function tests.chunkCount() function tests.chunkCount()
@@ -364,7 +364,7 @@ function tests.scanEnemy()
local chunk = mapUtils.getChunkByPosition(map, game.player.character.position) local chunk = mapUtils.getChunkByPosition(map, game.player.character.position)
local universe = map.universe local universe = map.universe
local query = universe.filteredEntitiesEnemyStructureQuery 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 buildings = map.surface.find_entities_filtered(query)
local counts = map.chunkScanCounts local counts = map.chunkScanCounts
for i=1,#constants.HIVE_BUILDINGS_TYPES do for i=1,#constants.HIVE_BUILDINGS_TYPES do