lots of refactoring

This commit is contained in:
Aaron Veden
2017-05-31 18:46:53 -07:00
parent afe7cacb0f
commit 43fb867c15
18 changed files with 419 additions and 307 deletions
+10
View File
@@ -56,9 +56,19 @@ Configure Options not in game menu:
# Version History
0.15.11 -
- Fixed: Player region scan can no longer overlap passive map scan causing double processing
- Tweak: Increased rally cry chance from 0.02(@100 evo) to 0.08(@100 evo) compensate for the once per logic cycle per chunk
- Improvement: Changed biter base detection from slow map scan to event
- Improvement: Added negative score contribution to nest causing biters to move around nests instead of through
- Improvement: Increased player pheromone for weight multipler from 25 to 50 for hunting parties
- Optimization: Player region scan can no longer overlap passive map scan causing double processing
- Optimization: Short circuit rally cry search if not enough points to make a squad
- Optimization: Short circuit vengence squad search if not enough points to make a squad
- Optimization: Short circuit attack groups search if not enough points to make a squad
- Optimization: Precompute retreatThreshold, maxSquads, rallyThreshold, formSquadThreshold, attackWaveSize, attackWaveSizeLowerBound, attackWaveSizeUpperBound, attackWaveSizeDeviation, kamikazeThreshold, attackWaveThreshold once per logic cycle
- Optimization: Reduced garbage generated by pheromone map processing
- Optimization: Reduced garbage generated by attack formation scanning
- Framework: renamed chunk pX to x, so chunks could be used in calls to things like get_pollution
0.15.10 -
- Fix: nil chunk in pheromone utils(https://mods.factorio.com/mods/Veden/Rampant/discussion/13806)
+26 -21
View File
@@ -19,12 +19,6 @@ local roundToNearest = mathUtils.roundToNearest
function upgrade.attempt(natives, regionMap)
local starting = global.version
if (global.version == nil) then
-- removed in version 9
-- regionMap.pQ = {{}} -- processing queue
-- regionMap.pI = 1 -- insertion location for chunk processing
-- regionMap.pP = 1 -- index for the chunk set to process
-- regionMap.pR = -1 -- current processing roll
natives.squads = {}
natives.scouts = {}
natives.tunnels = {}
@@ -32,16 +26,6 @@ function upgrade.attempt(natives, regionMap)
global.version = constants.VERSION_5
end
if (global.version < constants.VERSION_9) then
-- remove version 5 references
regionMap.pQ = nil
regionMap.pI = nil
regionMap.pP = nil
regionMap.pR = nil
global.version = constants.VERSION_9
end
if (global.version < constants.VERSION_10) then
for _,squad in pairs(natives.squads) do
squad.frenzy = false
@@ -98,9 +82,6 @@ function upgrade.attempt(natives, regionMap)
-- been made redundant
natives.rallyCries = nil
-- switched over to tick event
regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
regionMap.processTick = roundToNearest(game.tick + INTERVAL_PROCESS, INTERVAL_PROCESS)
-- needs to be on inner logic tick loop interval
natives.stateTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
natives.temperamentTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
@@ -131,9 +112,24 @@ function upgrade.attempt(natives, regionMap)
end
if (global.version < constants.VERSION_23) then
game.forces.enemy.ai_controllable = false
-- used to precompute some values per logic cycle
natives.retreatThreshold = 0
natives.maxSquads = 0
natives.rallyThreshold = 0
natives.formSquadThreshold = 0
natives.attackWaveSize = 0
natives.attackWaveDeviation = 0
natives.attackWaveLowerBound = 0
natives.attackWaveUpperBound = 0
natives.unitRefundAmount = 0
natives.attackWaveThreshold = 0
natives.useCustomAI = settings.startup["rampant-useCustomAI"].value
if natives.useCustomAI then
game.forces.enemy.ai_controllable = false
else
game.forces.enemy.ai_controllable = true
end
natives.bases = {}
natives.baseDistanceMin = 0
natives.baseIndex = 1
@@ -145,4 +141,13 @@ function upgrade.attempt(natives, regionMap)
return starting ~= global.version
end
function upgrade.compareTable(entities, option, new)
local changed = false
if (entities[option] ~= new) then
entities[option] = new
changed = true
end
return changed, new
end
return upgrade
+13 -6
View File
@@ -1,19 +1,26 @@
local config = {}
-- imported
local mathUtils = require("libs/MathUtils")
-- imported functions
local gaussianRandomRange = mathUtils.gaussianRandomRange
local mCeil = math.ceil
-- configurations
--[[
attackWaveScaling is used to calculate the attack wave size from the evolutionFactor
default is natives.attackWaveMaxSize * (evolutionFactor ^ 1.666667)
DOES NOT affect vanilla biters waves
--]]
config.attackWaveScaling = function (evolutionFactor, natives)
local attackWaveMaxSize = natives.attackWaveMaxSize
return math.ceil(gaussianRandomRange(attackWaveMaxSize * (evolutionFactor ^ 1.66667),
(attackWaveMaxSize * 0.5) * 0.333,
1,
attackWaveMaxSize + (attackWaveMaxSize * 0.25)))
config.attackWaveScaling = function (natives)
return mCeil(gaussianRandomRange(natives.attackWaveSize,
natives.attackWaveDeviation,
natives.attackWaveLowerBound,
natives.attackWaveUpperBound))
end
return config
+91 -68
View File
@@ -16,6 +16,7 @@ local interop = require("libs/Interop")
local tests = require("tests")
local upgrade = require("Upgrade")
local baseUtils = require("libs/BaseUtils")
local mathUtils = require("libs/MathUtils")
-- constants
@@ -24,10 +25,9 @@ local INTERVAL_PROCESS = constants.INTERVAL_PROCESS
local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
local BASE_RALLY_CHANCE = constants.BASE_RALLY_CHANCE
local BONUS_RALLY_CHANCE = constants.BONUS_RALLY_CHANCE
-- imported functions
local RETREAT_MOVEMENT_PHEROMONE_LEVEL = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL
local roundToNearest = mathUtils.roundToNearest
-- imported functions
@@ -57,6 +57,7 @@ local retreatUnits = aiDefense.retreatUnits
local addRemovePlayerEntity = entityUtils.addRemovePlayerEntity
local unregisterEnemyBaseStructure = baseUtils.unregisterEnemyBaseStructure
local registerEnemyBaseStructure = baseUtils.registerEnemyBaseStructure
local makeImmortalEntity = entityUtils.makeImmortalEntity
local processBases = baseProcessor.processBases
@@ -83,25 +84,34 @@ local function onChunkGenerated(event)
end
end
local function reprocessChunks()
game.surfaces[1].print("Rampant - Reindexing chunks, please wait")
local function rebuildRegionMap()
game.surfaces[1].print("Rampant - Reindexing chunks, please wait.")
-- clear old regionMap processing Queue
-- prevents queue adding duplicate chunks
-- chunks are by key, so should overwrite old
global.regionMap = {}
regionMap = global.regionMap
regionMap.processQueue = {}
regionMap.processPointer = 1
regionMap.scanPointer = 1
-- switched over to tick event
regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
regionMap.processTick = roundToNearest(game.tick + INTERVAL_PROCESS, INTERVAL_PROCESS)
-- clear pending chunks, will be added when loop runs below
pendingChunks = {}
-- queue all current chunks that wont be generated during play
local surface = game.surfaces[1]
local tick = game.tick
for chunk in surface.get_chunks() do
onChunkGenerated({ tick = game.tick,
onChunkGenerated({ tick = tick,
surface = surface,
area = { left_top = { x = chunk.x * 32,
y = chunk.y * 32 }}})
end
end
end
local function onModSettingsChange(event)
@@ -110,45 +120,51 @@ local function onModSettingsChange(event)
return false
end
natives.safeBuildings = settings.global["rampant-safeBuildings"].value
upgrade.compareTable(natives, "safeBuildings", settings.global["rampant-safeBuildings"].value)
natives.safeEntities["curved-rail"] = settings.global["rampant-safeBuildings-curvedRail"].value
natives.safeEntities["straight-rail"] = settings.global["rampant-safeBuildings-straightRail"].value
natives.safeEntities["rail-signal"] = settings.global["rampant-safeBuildings-railSignals"].value
natives.safeEntities["rail-chain-signal"] = settings.global["rampant-safeBuildings-railChainSignals"].value
natives.safeEntities["train-stop"] = settings.global["rampant-safeBuildings-trainStops"].value
upgrade.compareTable(natives.safeEntities, "curved-rail", settings.global["rampant-safeBuildings-curvedRail"].value)
upgrade.compareTable(natives.safeEntities, "straight-rail", settings.global["rampant-safeBuildings-straightRail"].value)
upgrade.compareTable(natives.safeEntities, "rail-signal", settings.global["rampant-safeBuildings-railSignals"].value)
upgrade.compareTable(natives.safeEntities, "rail-chain-signal", settings.global["rampant-safeBuildings-railChainSignals"].value)
upgrade.compareTable(natives.safeEntities, "train-stop", settings.global["rampant-safeBuildings-trainStops"].value)
local poles = settings.global["rampant-safeBuildings-bigElectricPole"].value
natives.safeEntityName["big-electric-pole"] = poles
natives.safeEntityName["big-electric-pole-2"] = poles
natives.safeEntityName["big-electric-pole-3"] = poles
natives.safeEntityName["big-electric-pole-4"] = poles
local changed, newValue = upgrade.compareTable(natives.safeEntityName,
"big-electric-pole",
settings.global["rampant-safeBuildings-bigElectricPole"].value)
if changed then
natives.safeEntityName["big-electric-pole"] = newValue
natives.safeEntityName["big-electric-pole-2"] = newValue
natives.safeEntityName["big-electric-pole-3"] = newValue
natives.safeEntityName["big-electric-pole-4"] = newValue
end
natives.attackUsePlayer = settings.global["rampant-attackWaveGenerationUsePlayerProximity"].value
natives.attackUsePollution = settings.global["rampant-attackWaveGenerationUsePollution"].value
upgrade.compareTable(natives, "attackUsePlayer", settings.global["rampant-attackWaveGenerationUsePlayerProximity"].value)
upgrade.compareTable(natives, "attackUsePollution", settings.global["rampant-attackWaveGenerationUsePollution"].value)
natives.attackThresholdMin = settings.global["rampant-attackWaveGenerationThresholdMin"].value
natives.attackThresholdMax = settings.global["rampant-attackWaveGenerationThresholdMax"].value
natives.attackThresholdRange = natives.attackThresholdMax - natives.attackThresholdMin
natives.attackWaveMaxSize = settings.global["rampant-attackWaveMaxSize"].value
natives.attackPlayerThreshold = settings.global["rampant-attackPlayerThreshold"].value
natives.aiNocturnalMode = settings.global["rampant-permanentNocturnal"].value
natives.aiPointsScaler = settings.global["rampant-aiPointsScaler"].value
upgrade.compareTable(natives, "attackThresholdMin", settings.global["rampant-attackWaveGenerationThresholdMin"].value)
upgrade.compareTable(natives, "attackThresholdMax", settings.global["rampant-attackWaveGenerationThresholdMax"].value)
upgrade.compareTable(natives, "attackThresholdRange", natives.attackThresholdMax - natives.attackThresholdMin)
upgrade.compareTable(natives, "attackWaveMaxSize", settings.global["rampant-attackWaveMaxSize"].value)
upgrade.compareTable(natives, "attackPlayerThreshold", settings.global["rampant-attackPlayerThreshold"].value)
upgrade.compareTable(natives, "aiNocturnalMode", settings.global["rampant-permanentNocturnal"].value)
upgrade.compareTable(natives, "aiPointsScaler", settings.global["rampant-aiPointsScaler"].value)
local useCustomAI = natives.useCustomAI
natives.useCustomAI = settings.startup["rampant-useCustomAI"].value
if not useCustomAI and natives.useCustomAI then
reprocessChunks()
changed, newValue = upgrade.compareTable(natives, "useCustomAI", settings.startup["rampant-useCustomAI"].value)
if natives.useCustomAI then
game.forces.enemy.ai_controllable = false
else
game.forces.enemy.ai_controllable = true
end
if changed and newValue then
rebuildRegionMap()
return false
end
return true
end
local function onConfigChanged()
if upgrade.attempt(natives, regionMap) and onModSettingsChange(nil) then
reprocessChunks()
rebuildRegionMap()
end
end
@@ -157,31 +173,34 @@ local function onTick(event)
if (tick == regionMap.processTick) then
regionMap.processTick = regionMap.processTick + INTERVAL_PROCESS
local surface = game.surfaces[1]
local evolutionFactor = game.forces.enemy.evolution_factor
local players = game.players
processPendingChunks(natives, regionMap, surface, pendingChunks, tick)
scanMap(regionMap, surface, natives, evolutionFactor)
scanMap(regionMap, surface, natives)
if (tick == regionMap.logicTick) then
regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
planning(natives, evolutionFactor, tick, surface)
cleanSquads(natives, evolutionFactor)
regroupSquads(natives, evolutionFactor)
processPlayers(players, regionMap, surface, natives, evolutionFactor, tick)
local players = game.players
if (natives.useCustomAI) then
processBases(regionMap, surface, natives, tick)
end
planning(natives,
game.forces.enemy.evolution_factor,
tick,
surface)
squadBeginAttack(natives, players, evolutionFactor)
cleanSquads(natives)
-- regroupSquads(natives)
processPlayers(players, regionMap, surface, natives, tick)
-- if (natives.useCustomAI) then
-- processBases(regionMap, surface, natives, tick)
-- end
squadBeginAttack(natives, players)
squadAttack(regionMap, surface, natives)
end
processMap(regionMap, surface, natives, evolutionFactor)
processMap(regionMap, surface, natives, tick)
end
end
@@ -212,27 +231,24 @@ local function onDeath(event)
-- drop death pheromone where unit died
deathScent(deathChunk)
if event.force and (event.force.name == "player") then
local evolutionFactor = entity.force.evolution_factor
if event.force and (event.force.name == "player") and (deathChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold) then
local tick = event.tick
if (deathChunk[MOVEMENT_PHEROMONE] < -(evolutionFactor * RETREAT_MOVEMENT_PHEROMONE_LEVEL)) then
retreatUnits(deathChunk,
convertUnitGroupToSquad(natives,
entity.unit_group),
regionMap,
surface,
natives,
tick)
local rallyThreshold = BASE_RALLY_CHANCE + (evolutionFactor * BONUS_RALLY_CHANCE)
if (math.random() < rallyThreshold) then
rallyUnits(deathChunk,
regionMap,
surface,
natives,
evolutionFactor,
tick)
end
retreatUnits(deathChunk,
convertUnitGroupToSquad(natives,
entity.unit_group),
regionMap,
surface,
natives,
tick)
if (math.random() < natives.rallyThreshold) and not surface.peaceful_mode then
local tempNeighbors = {false, false, false, false, false, false, false, false}
rallyUnits(deathChunk,
regionMap,
surface,
natives,
tick,
tempNeighbors)
end
end
end
@@ -259,6 +275,11 @@ local function onDeath(event)
end
end
local function onEnemyBaseBuild(event)
local entity = event.entity
registerEnemyBaseStructure(regionMap, entity, nil)
end
local function onSurfaceTileChange(event)
local player = game.players[event.player_index]
if (player.surface.index == 1) then
@@ -288,6 +309,8 @@ script.on_configuration_changed(onConfigChanged)
script.on_event(defines.events.on_player_built_tile, onSurfaceTileChange)
script.on_event(defines.events.on_biter_base_built,
onEnemyBaseBuild)
script.on_event({defines.events.on_preplayer_mined_item,
defines.events.on_robot_pre_mined},
onPickUp)
+12 -10
View File
@@ -62,8 +62,10 @@ function aiAttack.squadAttack(regionMap, surface, natives)
local squads = natives.squads
local attackPosition
local attackCmd
local tempNeighbors
if (#squads > 0) then
tempNeighbors = {false, false, false, false, false, false, false, false}
attackPosition = {x=0, y=0}
attackCmd = { type = DEFINES_COMMAND_ATTACK_AREA,
destination = attackPosition,
@@ -80,7 +82,7 @@ function aiAttack.squadAttack(regionMap, surface, natives)
local chunk = getChunkByPosition(regionMap, groupPosition.x, groupPosition.y)
if chunk then
local attackChunk, attackDirection = scoreNeighborsWithDirection(chunk,
getNeighborChunksWithDirection(regionMap, chunk.cX, chunk.cY),
getNeighborChunksWithDirection(regionMap, chunk.cX, chunk.cY, tempNeighbors),
validLocation,
scoreAttackLocation,
squad,
@@ -124,24 +126,24 @@ function aiAttack.squadAttack(regionMap, surface, natives)
end
end
function aiAttack.squadBeginAttack(natives, players, evolution_factor)
function aiAttack.squadBeginAttack(natives, players)
local squads = natives.squads
for i=1,#squads do
local squad = squads[i]
if (squad.status == SQUAD_GUARDING) and squad.group.valid then
local kamikazeThreshold = calculateKamikazeThreshold(squad, natives, evolution_factor)
local group = squad.group
if (squad.status == SQUAD_GUARDING) and group.valid then
local groupPosition = group.position
local kamikazeThreshold = calculateKamikazeThreshold(squad, natives)
local playerNearby = playersWithinProximityToPosition(players, squad.group.position, 100)
local playerNearby = playersWithinProximityToPosition(players, groupPosition, 100)
if playerNearby then
squad.frenzy = true
squad.frenzyPosition.x = squad.group.position.x
squad.frenzyPosition.y = squad.group.position.y
squad.frenzyPosition.x = groupPosition.x
squad.frenzyPosition.y = groupPosition.y
end
if (math.random() < 0.70) then
if (math.random() < kamikazeThreshold) then
squad.kamikaze = true
end
squad.kamikaze = math.random() < kamikazeThreshold
squad.status = SQUAD_RAIDING
end
end
+38 -45
View File
@@ -15,8 +15,6 @@ local BASE_PHEROMONE = constants.BASE_PHEROMONE
local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE
local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
local AI_MAX_SQUAD_COUNT = constants.AI_MAX_SQUAD_COUNT
local AI_SQUAD_COST = constants.AI_SQUAD_COST
local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST
@@ -24,7 +22,7 @@ local RALLY_TRIGGERED = constants.RALLY_TRIGGERED
local INTERVAL_LOGIC = constants.INTERVAL_LOGIC
local HALF_CHUNK_SIZE = constants.HALF_CHUNK_SIZE
local CHUNK_SIZE = constants.CHUNK_SIZE
local TRIPLE_CHUNK_SIZE = constants.TRIPLE_CHUNK_SIZE
local NORTH_SOUTH_PASSABLE = constants.NORTH_SOUTH_PASSABLE
local EAST_WEST_PASSABLE = constants.EAST_WEST_PASSABLE
@@ -43,27 +41,22 @@ local scoreNeighbors = neighborUtils.scoreNeighbors
local createSquad = unitGroupUtils.createSquad
local attackWaveScaling = config.attackWaveScaling
local mMax = math.max
-- module code
local function attackWaveValidCandidate(chunk, natives, surface, evolutionFactor)
local function attackWaveValidCandidate(chunk, natives, surface)
local total = 0;
if natives.attackUsePlayer then
local playerPheromone = chunk[PLAYER_PHEROMONE]
if (playerPheromone > natives.attackPlayerThreshold) and (playerPheromone > 0) then
if (playerPheromone > natives.attackPlayerThreshold) then
total = total + chunk[PLAYER_PHEROMONE]
end
end
if natives.attackUsePollution then
total = total + surface.get_pollution(chunk)
end
local threshold = natives.attackThresholdRange
local delta = threshold * evolutionFactor
return total > ((threshold - delta) + natives.attackThresholdMin)
return total > natives.attackWaveThreshold
end
local function scoreUnitGroupLocation(squad, neighborChunk, surface)
@@ -71,57 +64,57 @@ local function scoreUnitGroupLocation(squad, neighborChunk, surface)
end
local function validUnitGroupLocation(x, chunk, neighborChunk)
return neighborChunk[NORTH_SOUTH_PASSABLE] and neighborChunk[EAST_WEST_PASSABLE] and neighborChunk[NEST_COUNT] ~= 0
return neighborChunk[NORTH_SOUTH_PASSABLE] and neighborChunk[EAST_WEST_PASSABLE] and (neighborChunk[NEST_COUNT] ~= 0)
end
function aiBuilding.rallyUnits(chunk, regionMap, surface, natives, evolutionFactor, tick)
if (tick - chunk[RALLY_TRIGGERED] > INTERVAL_LOGIC) then
function aiBuilding.rallyUnits(chunk, regionMap, surface, natives, tick, tempNeighbors)
if (tick - chunk[RALLY_TRIGGERED] > INTERVAL_LOGIC) and (natives.points >= AI_VENGENCE_SQUAD_COST) then
chunk[RALLY_TRIGGERED] = tick
local cX = chunk.cX
local cY = chunk.cY
for x=cX - RALLY_CRY_DISTANCE, cX + RALLY_CRY_DISTANCE do
for y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE do
local rallyChunk = getChunkByIndex(regionMap, x, y)
if rallyChunk and (x ~= cX) and (y ~= cY) and (rallyChunk[NEST_COUNT] ~= 0) then
aiBuilding.formSquads(regionMap, surface, natives, rallyChunk, evolutionFactor, AI_VENGENCE_SQUAD_COST)
if rallyChunk and (rallyChunk[NEST_COUNT] ~= 0) and (x ~= cX) and (y ~= cY) then
aiBuilding.formSquads(regionMap, surface, natives, rallyChunk, AI_VENGENCE_SQUAD_COST, tempNeighbors)
if (natives.points < AI_VENGENCE_SQUAD_COST) then
return
end
end
end
end
end
end
function aiBuilding.formSquads(regionMap, surface, natives, chunk, evolution_factor, cost)
if (natives.points > cost) and (chunk[NEST_COUNT] ~= 0) and (#natives.squads < (AI_MAX_SQUAD_COUNT * evolution_factor)) then
local valid = not surface.peaceful_mode and
((cost == AI_VENGENCE_SQUAD_COST) or
((cost == AI_SQUAD_COST) and attackWaveValidCandidate(chunk, natives, surface, evolution_factor)))
function aiBuilding.formSquads(regionMap, surface, natives, chunk, cost, tempNeighbors)
local valid = ((cost == AI_VENGENCE_SQUAD_COST) or
((cost == AI_SQUAD_COST) and attackWaveValidCandidate(chunk, natives, surface)))
if valid and (math.random() < mMax((0.25 * evolution_factor), 0.10)) then
if valid and (math.random() < natives.formSquadThreshold) then
local squadPath, _ = scoreNeighbors(chunk,
getNeighborChunks(regionMap, chunk.cX, chunk.cY, tempNeighbors),
validUnitGroupLocation,
scoreUnitGroupLocation,
nil,
surface,
false)
if squadPath then
local squadPosition = { x = squadPath.x + HALF_CHUNK_SIZE,
y = squadPath.y + HALF_CHUNK_SIZE }
local squadPath, _ = scoreNeighbors(chunk,
getNeighborChunks(regionMap, chunk.cX, chunk.cY),
validUnitGroupLocation,
scoreUnitGroupLocation,
nil,
surface,
false)
if squadPath then
local squadPosition = { x = squadPath.x + HALF_CHUNK_SIZE,
y = squadPath.y + HALF_CHUNK_SIZE }
local squad = createSquad(squadPosition, surface, natives)
squad.rabid = math.random() < 0.03
local squad = createSquad(squadPosition, surface, natives)
squad.rabid = math.random() < 0.03
local scaledWaveSize = attackWaveScaling(evolution_factor, natives)
local foundUnits = surface.set_multi_command({ command = { type = DEFINES_COMMAND_GROUP,
group = squad.group,
distraction = DEFINES_DISTRACTION_NONE },
unit_count = scaledWaveSize,
unit_search_distance = (CHUNK_SIZE * 3)})
if (foundUnits > 0) then
natives.points = natives.points - cost
end
local scaledWaveSize = attackWaveScaling(natives)
local foundUnits = surface.set_multi_command({ command = { type = DEFINES_COMMAND_GROUP,
group = squad.group,
distraction = DEFINES_DISTRACTION_NONE },
unit_count = scaledWaveSize,
unit_search_distance = TRIPLE_CHUNK_SIZE })
if (foundUnits > 0) then
natives.points = natives.points - cost
end
end
end
+2 -1
View File
@@ -54,6 +54,7 @@ function aiDefense.retreatUnits(chunk, squad, regionMap, surface, natives, tick)
if (tick - chunk[RETREAT_TRIGGERED] > INTERVAL_LOGIC) and (chunk[NEST_COUNT] == 0) and (chunk[WORM_COUNT] == 0) then
local performRetreat = false
local enemiesToSquad = nil
local tempNeighbors = {false, false, false, false, false, false, false, false}
if not squad then
enemiesToSquad = surface.find_enemy_units(chunk, RETREAT_GRAB_RADIUS)
@@ -65,7 +66,7 @@ function aiDefense.retreatUnits(chunk, squad, regionMap, surface, natives, tick)
if performRetreat then
chunk[RETREAT_TRIGGERED] = tick
local exitPath,_ = scoreNeighborsWithDirection(chunk,
getNeighborChunksWithDirection(regionMap, chunk.cX, chunk.cY),
getNeighborChunksWithDirection(regionMap, chunk.cX, chunk.cY, tempNeighbors),
validRetreatLocation,
scoreRetreatLocation,
nil,
+29 -2
View File
@@ -5,13 +5,18 @@ local aiPlanning = {}
local constants = require("Constants")
local mathUtils = require("MathUtils")
local aiPredicates = require("AIPredicates")
-- constants
local NO_RETREAT_BASE_PERCENT = constants.NO_RETREAT_BASE_PERCENT
local NO_RETREAT_EVOLUTION_BONUS_MAX = constants.NO_RETREAT_EVOLUTION_BONUS_MAX
local AI_STATE_PEACEFUL = constants.AI_STATE_PEACEFUL
local AI_STATE_AGGRESSIVE = constants.AI_STATE_AGGRESSIVE
local AI_STATE_NOCTURNAL = constants.AI_STATE_NOCTURNAL
local AI_UNIT_REFUND = constants.AI_UNIT_REFUND
local AI_MAX_POINTS = constants.AI_MAX_POINTS
local AI_POINT_GENERATOR_AMOUNT = constants.AI_POINT_GENERATOR_AMOUNT
@@ -22,6 +27,11 @@ local AI_MAX_TEMPERAMENT_DURATION = constants.AI_MAX_TEMPERAMENT_DURATION
local AI_MAX_SQUAD_COUNT = constants.AI_MAX_SQUAD_COUNT
local BASE_RALLY_CHANCE = constants.BASE_RALLY_CHANCE
local BONUS_RALLY_CHANCE = constants.BONUS_RALLY_CHANCE
local RETREAT_MOVEMENT_PHEROMONE_LEVEL = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL
local TICKS_A_MINUTE = constants.TICKS_A_MINUTE
-- imported functions
@@ -44,9 +54,25 @@ end
function aiPlanning.planning(natives, evolution_factor, tick, surface)
local maxPoints = AI_MAX_POINTS * evolution_factor
if natives.aiNocturnalMode then
maxPoints = maxPoints * 0.85
end
local attackWaveMaxSize = natives.attackWaveMaxSize
natives.retreatThreshold = -(evolution_factor * RETREAT_MOVEMENT_PHEROMONE_LEVEL)
natives.maxSquads = AI_MAX_SQUAD_COUNT * evolution_factor
natives.rallyThreshold = BASE_RALLY_CHANCE + (evolution_factor * BONUS_RALLY_CHANCE)
natives.formSquadThreshold = mMax((0.25 * evolution_factor), 0.10)
natives.attackWaveSize = attackWaveMaxSize * (evolution_factor ^ 1.66667)
natives.attackWaveDeviation = (attackWaveMaxSize * 0.5) * 0.333
natives.attackWaveLowerBound = 1
natives.attackWaveUpperBound = attackWaveMaxSize + (attackWaveMaxSize * 0.25)
natives.unitRefundAmount = AI_UNIT_REFUND * evolution_factor
natives.kamikazeThreshold = NO_RETREAT_BASE_PERCENT + (evolution_factor * NO_RETREAT_EVOLUTION_BONUS_MAX)
local threshold = natives.attackThresholdRange
natives.attackWaveThreshold = (threshold - (threshold * evolution_factor)) + natives.attackThresholdMin
if (natives.points < maxPoints) then
natives.points = natives.points + math.floor((AI_POINT_GENERATOR_AMOUNT * math.random()) +
((AI_POINT_GENERATOR_AMOUNT * 0.7) * (evolution_factor ^ 2.5)) * natives.aiPointsScaler)
@@ -61,7 +87,7 @@ function aiPlanning.planning(natives, evolution_factor, tick, surface)
local roll = math.random() * mMax(1 - evolution_factor, 0.15)
if (roll > natives.temperament) then
natives.state = AI_STATE_PEACEFUL
elseif (natives.aiNocturnalMode) then
elseif natives.aiNocturnalMode then
natives.state = AI_STATE_NOCTURNAL
else
natives.state = AI_STATE_AGGRESSIVE
@@ -73,6 +99,7 @@ function aiPlanning.planning(natives, evolution_factor, tick, surface)
natives.lastShakeMessage = tick
surface.print("Rampant: The ground begins to shake")
end
end
return aiPlanning
+3 -1
View File
@@ -16,7 +16,9 @@ local canAttackNocturnal = nocturnalUtils.canAttack
-- module code
function aiPredicates.canAttack(natives, surface)
return (natives.state == AI_STATE_AGGRESSIVE) or canAttackNocturnal(natives, surface)
return ((natives.state == AI_STATE_AGGRESSIVE) or
canAttackNocturnal(natives, surface)) and not surface.peaceful_mode and
(#natives.squads < natives.maxSquads)
end
return aiPredicates
+34 -10
View File
@@ -23,7 +23,7 @@ local WORM_BASE = constants.WORM_BASE
local AI_NEST_COST = constants.AI_NEST_COST
local AI_WORM_COST = constants.AI_WORM_COST
local CHUNK_SIZE = constants.CHUNK_SIZE
local DOUBLE_CHUNK_SIZE = constants.DOUBLE_CHUNK_SIZE
local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER
local MAGIC_MAXIMUM_BASE_NUMBER = constants.MAGIC_MAXIMUM_BASE_NUMBER
@@ -36,6 +36,10 @@ local getEntityOverlapChunks = entityUtils.getEntityOverlapChunks
local gaussianRandomRange = mathUtils.gaussianRandomRange
local mFloor = math.floor
-- module code
function baseUtils.findNearbyBase(natives, position)
@@ -45,7 +49,7 @@ function baseUtils.findNearbyBase(natives, position)
for i=1,#bases do
local base = bases[i]
local distance = euclideanDistancePoints(base.x, base.y, position.x, position.y)
if (distance <= BASE_DISTANCE_THRESHOLD) and (distance < closest) then
if (distance <= (BASE_DISTANCE_THRESHOLD + (base.level * 100))) and (distance < closest) then
closest = distance
foundBase = base
end
@@ -55,11 +59,16 @@ end
function baseUtils.buildHive(regionMap, base, surface)
local valid = false
local position = surface.find_non_colliding_position("biter-spawner-hive", {x=base.x, y=base.y}, 2*CHUNK_SIZE, 2)
local position = surface.find_non_colliding_position("biter-spawner-hive", base, DOUBLE_CHUNK_SIZE, 2)
if position then
local biterSpawner = {name="biter-spawner-hive", position=position}
base.hives[#base.hives+1] = surface.create_entity(biterSpawner)
baseUtils.registerEnemyBaseStructure(regionMap, base.hive, base)
local hive = surface.create_entity(biterSpawner)
if (#base.hives == 0) then
base.x = hive.position.x
base.y = hive.position.y
end
base.hives[#base.hives+1] = hive
baseUtils.registerEnemyBaseStructure(regionMap, hive, base)
valid = true
end
return valid
@@ -74,7 +83,12 @@ function baseUtils.buildTendril(natives, base, surface, tick, startPosition, end
end
function baseUtils.buildOrder(regionMap, natives, base, surface, tick)
if (#base.hives == 0) or (base.upgradePoints < 10) then
local foundHive = false
for _,_ in pairs(base.hives) do
foundHive = true
break
end
if not foundHive or (base.upgradePoints < 10) then
return
end
@@ -83,6 +97,7 @@ function baseUtils.buildOrder(regionMap, natives, base, surface, tick)
for level=0,base.level do
local slices = (level * 3)
slices = gaussianRandomRange(slices, slices * 0.1, slices * 0.6, slices * 1.4, generator)
local slice = (2 * math.pi) / slices
local pos = 0
local thing
@@ -106,7 +121,7 @@ function baseUtils.buildOrder(regionMap, natives, base, surface, tick)
local nestPosition = {x = base.x + (distortion * math.cos(pos)),
y = base.y + (distortion * math.sin(pos))}
local biterSpawner = {name=thing, position=nestPosition}
if surface.can_place_entity(biterSpawner) then
if surface.can_place_entity(biterSpawner) then
baseUtils.registerEnemyBaseStructure(regionMap, surface.create_entity(biterSpawner), base)
base.upgradePoints = base.upgradePoints - cost
end
@@ -117,6 +132,7 @@ end
function baseUtils.createBase(regionMap, natives, position, surface, tick)
local bases = natives.bases
local distance = euclideanDistancePoints(position.x, position.y, 0, 0)
local base = {
x = position.x,
y = position.y,
@@ -129,7 +145,7 @@ function baseUtils.createBase(regionMap, natives, position, surface, tick)
upgradePoints = 0,
growth = tick,
pattern = math.random(MAGIC_MAXIMUM_BASE_NUMBER),
level = 3
level = mFloor(distance / 200)
}
if not baseUtils.buildHive(regionMap, base, surface) then
return nil
@@ -145,7 +161,11 @@ function baseUtils.addEnemyStructureToChunk(chunk, entity, base)
if (entity.type == "unit-spawner") then
indexChunk = chunk[NEST_BASE]
if base then
indexBase = base.nests
if base.hives[entity.unit_number] then
indexBase = base.hives
else
indexBase = base.nests
end
end
countChunk = NEST_COUNT
elseif (entity.type == "turret") then
@@ -176,7 +196,11 @@ function baseUtils.removeEnemyStructureFromChunk(chunk, entity)
local indexBase
if base then
if (entity.type == "unit-spawner") then
indexBase = base.nests
if base.hives[entity.unit_number] then
indexBase = base.hives
else
indexBase = base.nests
end
elseif (entity.type == "turret") then
indexBase = base.worms
end
+30 -5
View File
@@ -3,6 +3,11 @@ local chunkProcessor = {}
-- imports
local chunkUtils = require("ChunkUtils")
local constants = require("Constants")
-- constants
local CHUNK_SIZE = constants.CHUNK_SIZE
-- imported functions
@@ -14,13 +19,27 @@ local scoreChunk = chunkUtils.scoreChunk
function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack, tick)
local processQueue = regionMap.processQueue
local offset = {0, 0}
local areaBoundingBox = {false, offset}
local query = {area=areaBoundingBox,
force=false}
for _=#pendingStack, 1, -1 do
local event = pendingStack[#pendingStack]
pendingStack[#pendingStack] = nil
local count = 0
local start = #pendingStack
for i=start, 1, -1 do
local event = pendingStack[i]
count = count + 1
pendingStack[i] = nil
local chunk = createChunk(event.area.left_top.x, event.area.left_top.y)
local x = event.area.left_top.x
local y = event.area.left_top.y
local chunk = createChunk(x, y)
areaBoundingBox[1] = chunk
offset[1] = x + CHUNK_SIZE
offset[2] = y + CHUNK_SIZE
local chunkX = chunk.cX
if regionMap[chunkX] == nil then
@@ -29,8 +48,14 @@ function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendin
regionMap[chunkX][chunk.cY] = chunk
checkChunkPassability(chunk, surface)
scoreChunk(regionMap, chunk, surface, natives, tick)
scoreChunk(regionMap, chunk, surface, natives, tick, query)
processQueue[#processQueue+1] = chunk
if (count >= 3500) then
break
end
end
if (count >= 3500) and (start > 0) then
surface.print("Rampant " .. (start - count) .. " Remaining chunks to process")
end
end
+15 -19
View File
@@ -39,9 +39,9 @@ local addEnemyStructureToChunk = baseUtils.addEnemyStructureToChunk
-- module code
local function checkForDeadendTiles(constantCoordinate, iteratingCoordinate, direction, get_tile)
local tile
for x=iteratingCoordinate, iteratingCoordinate + 31 do
local tile
if (direction == NORTH_SOUTH) then
tile = get_tile(constantCoordinate, x)
else
@@ -62,13 +62,13 @@ function chunkUtils.checkChunkPassability(chunk, surface)
local passableNorthSouth = false
local passableEastWest = false
for xi=x, x + 31 do
if (not checkForDeadendTiles(xi, y, NORTH_SOUTH, get_tile)) then
if not checkForDeadendTiles(xi, y, NORTH_SOUTH, get_tile) then
passableNorthSouth = true
break
end
end
for yi=y, y + 31 do
if (not checkForDeadendTiles(yi, x, EAST_WEST, get_tile)) then
if not checkForDeadendTiles(yi, x, EAST_WEST, get_tile) then
passableEastWest = true
break
end
@@ -78,21 +78,11 @@ function chunkUtils.checkChunkPassability(chunk, surface)
chunk[NORTH_SOUTH_PASSABLE] = passableNorthSouth
end
function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, tick)
local x = chunk.x
local y = chunk.y
local areaBoundingBox = {
chunk,
{x + 32, y + 32}
}
local enemyChunkQuery = {area=areaBoundingBox,
force="enemy"}
local playerChunkQuery = {area=areaBoundingBox,
force="player"}
function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, tick, tempQuery)
local useCustomAI = natives.useCustomAI
local enemies = surface.find_entities_filtered(enemyChunkQuery)
tempQuery.force = "enemy"
local enemies = surface.find_entities_filtered(tempQuery)
local nests = 0
local worms = 0
@@ -112,7 +102,12 @@ function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, tick)
if useCustomAI then
if (nests > 0) or (worms > 0) or (biters > 0) then
for f=1, #enemies do
enemies[f].destroy()
local enemy = enemies[f]
if (enemy.name ~= "biter-spawner-hive") then
enemy.destroy()
else
nests = nests - 1
end
end
local foundBase = findNearbyBase(natives, chunk) or createBase(regionMap, natives, chunk, surface, tick)
if foundBase then
@@ -129,7 +124,8 @@ function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, tick)
end
end
local entities = surface.find_entities_filtered(playerChunkQuery)
tempQuery.force = "player"
local entities = surface.find_entities_filtered(tempQuery)
local playerObjects = 0
local safeBuildings = natives.safeBuildings
+7 -9
View File
@@ -3,19 +3,12 @@ local constants = {}
-- versions
constants.VERSION_5 = 5
constants.VERSION_9 = 9
constants.VERSION_10 = 10
constants.VERSION_11 = 11
constants.VERSION_12 = 12
constants.VERSION_13 = 13
constants.VERSION_14 = 14
constants.VERSION_15 = 15
constants.VERSION_16 = 16
constants.VERSION_17 = 17
constants.VERSION_18 = 18
constants.VERSION_19 = 19
constants.VERSION_20 = 20
constants.VERSION_21 = 21
constants.VERSION_22 = 22
constants.VERSION_23 = 23
@@ -25,9 +18,9 @@ constants.MAGIC_MAXIMUM_NUMBER = 1e99 -- used in loops trying to find the lowest
constants.MAGIC_MAXIMUM_BASE_NUMBER = 100000000
constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL = 10000
constants.PROCESS_QUEUE_SIZE = 500
constants.PROCESS_QUEUE_SIZE = 450
constants.SCAN_QUEUE_SIZE = 6
constants.BASE_QUEUE_SIZE = 10
constants.BASE_QUEUE_SIZE = 1
constants.PROCESS_PLAYER_BOUND = 4
constants.TICKS_A_SECOND = 60
@@ -39,8 +32,11 @@ constants.INTERVAL_LOGIC = 38
-- chunk properties
constants.CHUNK_SIZE = 32
constants.DOUBLE_CHUNK_SIZE = constants.CHUNK_SIZE * 2
constants.TRIPLE_CHUNK_SIZE = constants.CHUNK_SIZE * 3
constants.HALF_CHUNK_SIZE = constants.CHUNK_SIZE / 2
constants.QUARTER_CHUNK_SIZE = constants.HALF_CHUNK_SIZE / 2
constants.NORTH_SOUTH = 1
constants.EAST_WEST = 2
@@ -56,6 +52,8 @@ constants.AI_SETTLER_COST = 75
constants.AI_BASE_BUILDING_COST = 500
constants.AI_TUNNEL_COST = 100
constants.AI_MAX_POINTS = 10000
constants.AI_MAX_OVERFLOW_POINTS = constants.AI_MAX_POINTS * 3
constants.AI_UNIT_REFUND = 3
+67 -63
View File
@@ -13,12 +13,11 @@ local playerUtils = require("PlayerUtils")
local PROCESS_QUEUE_SIZE = constants.PROCESS_QUEUE_SIZE
local RETREAT_MOVEMENT_PHEROMONE_LEVEL = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL
local SCAN_QUEUE_SIZE = constants.SCAN_QUEUE_SIZE
local AI_UNIT_REFUND = constants.AI_UNIT_REFUND
local CHUNK_SIZE = constants.CHUNK_SIZE
local DOUBLE_CHUNK_SIZE = constants.DOUBLE_CHUNK_SIZE
local TRIPLE_CHUNK_SIZE = constants.TRIPLE_CHUNK_SIZE
local PROCESS_PLAYER_BOUND = constants.PROCESS_PLAYER_BOUND
local CHUNK_TICK = constants.CHUNK_TICK
@@ -26,7 +25,7 @@ local CHUNK_TICK = constants.CHUNK_TICK
local NEST_COUNT = constants.NEST_COUNT
local WORM_COUNT = constants.WORM_COUNT
local AI_MAX_POINTS = constants.AI_MAX_POINTS
local AI_MAX_OVERFLOW_POINTS = constants.AI_MAX_OVERFLOW_POINTS
local AI_SQUAD_COST = constants.AI_SQUAD_COST
local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST
@@ -77,7 +76,7 @@ end
In theory, this might be fine as smaller bases have less surface to attack and need to have
pheromone dissipate at a faster rate.
--]]
function mapProcessor.processMap(regionMap, surface, natives, evolution_factor)
function mapProcessor.processMap(regionMap, surface, natives, tick)
local roll = regionMap.processRoll
local index = regionMap.processPointer
@@ -85,21 +84,29 @@ function mapProcessor.processMap(regionMap, surface, natives, evolution_factor)
roll = math.random()
regionMap.processRoll = roll
end
local tempNeighbors = {false, false, false, false}
local tempNeighborsAll = {false, false, false, false, false, false, false, false}
local squads = canAttack(natives, surface) and (0.11 <= roll) and (roll <= 0.35)
local squads = canAttack(natives, surface) and (0.11 <= roll) and (roll <= 0.35) and (natives.points >= AI_SQUAD_COST)
local processQueue = regionMap.processQueue
local endIndex = mMin(index + PROCESS_QUEUE_SIZE, #processQueue)
for x=index,endIndex do
local chunk = processQueue[x]
processPheromone(regionMap, chunk)
if (chunk[CHUNK_TICK] ~= tick) then
chunk[CHUNK_TICK] = tick
processPheromone(regionMap, chunk, tempNeighbors)
if squads then
formSquads(regionMap, surface, natives, chunk, evolution_factor, AI_SQUAD_COST)
end
scents(chunk)
if squads and (chunk[NEST_COUNT] ~= 0) then
formSquads(regionMap, surface, natives, chunk, AI_SQUAD_COST, tempNeighborsAll)
squads = natives.points >= AI_SQUAD_COST
end
scents(chunk)
end
end
if (endIndex == #processQueue) then
@@ -115,15 +122,19 @@ end
vs
the slower passive version processing the entire map in multiple passes.
--]]
function mapProcessor.processPlayers(players, regionMap, surface, natives, evolution_factor, tick)
function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
-- put down player pheromone for player hunters
-- randomize player order to ensure a single player isn't singled out
local playerOrdering = nonRepeatingRandom(players)
local vengenceThreshold = -(evolution_factor * RETREAT_MOVEMENT_PHEROMONE_LEVEL)
local roll = math.random()
local roll = math.random()
local squads = canAttack(natives, surface) and (0.11 <= roll) and (roll <= 0.20)
local allowingAttacks = canAttack(natives, surface)
local squads = allowingAttacks and (0.11 <= roll) and (roll <= 0.20) and (natives.points >= AI_SQUAD_COST)
local tempNeighbors = {false, false, false, false}
local tempNeighborsAll = {false, false, false, false, false, false, false, false}
for i=1,#playerOrdering do
local player = players[playerOrdering[i]]
@@ -143,21 +154,23 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, evolu
local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y)
if playerChunk then
local vengence = canAttack(natives, surface) and ((playerChunk[NEST_COUNT] ~= 0) or (playerChunk[WORM_COUNT] ~= 0) or (playerChunk[MOVEMENT_PHEROMONE] < vengenceThreshold))
local vengence = allowingAttacks and ((playerChunk[NEST_COUNT] ~= 0) or (playerChunk[WORM_COUNT] ~= 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold)) and (natives.points >= AI_VENGENCE_SQUAD_COST)
for x=playerChunk.cX - PROCESS_PLAYER_BOUND, playerChunk.cX + PROCESS_PLAYER_BOUND do
for y=playerChunk.cY - PROCESS_PLAYER_BOUND, playerChunk.cY + PROCESS_PLAYER_BOUND do
local chunk = getChunkByIndex(regionMap, x, y)
if chunk and (chunk[CHUNK_TICK] ~= tick) then
chunk[CHUNK_TICK] = tick
processPheromone(regionMap, chunk)
processPheromone(regionMap, chunk, tempNeighbors)
if squads then
formSquads(regionMap, surface, natives, chunk, evolution_factor, AI_SQUAD_COST)
formSquads(regionMap, surface, natives, chunk, AI_SQUAD_COST, tempNeighborsAll)
squads = natives.points >= AI_SQUAD_COST
end
if vengence then
formSquads(regionMap, surface, natives, chunk, evolution_factor, AI_VENGENCE_SQUAD_COST)
formSquads(regionMap, surface, natives, chunk, AI_VENGENCE_SQUAD_COST, tempNeighborsAll)
vengence = natives.points >= AI_VENGENCE_SQUAD_COST
end
scents(chunk)
@@ -173,67 +186,60 @@ end
--[[
Passive scan to find entities that have been generated outside the factorio event system
--]]
function mapProcessor.scanMap(regionMap, surface, natives, evolution_factor)
function mapProcessor.scanMap(regionMap, surface, natives)
local index = regionMap.scanPointer
local chunkBox = {false,
{x=0,
y=0}}
local offset = {0, 0}
local chunkBox = {false, offset}
local playerQuery = {area = chunkBox,
force = "player"}
local spawnerQuery = {area = chunkBox,
type = "unit-spawner",
force = "enemy"}
local wormsQuery = {area = chunkBox,
type = "turret",
force = "enemy"}
local unitCountQuery = {area = chunkBox,
type = "unit",
force = "enemy"}
local unitCountQuery = { area = chunkBox,
type = "unit",
force = "enemy",
limit = 301 }
local processQueue = regionMap.processQueue
local endIndex = mMin(index + SCAN_QUEUE_SIZE, #processQueue)
local unitRefundAmount = natives.unitRefundAmount
for x=index,endIndex do
local chunk = processQueue[x]
chunkBox[1] = chunk
chunkBox[2].x = chunk.x + CHUNK_SIZE
chunkBox[2].y = chunk.y + CHUNK_SIZE
local entities = surface.find_entities_filtered(playerQuery)
local spawners = surface.count_entities_filtered(spawnerQuery)
local worms = surface.count_entities_filtered(wormsQuery)
offset[1] = chunk.x + CHUNK_SIZE
offset[2] = chunk.y + CHUNK_SIZE
local unitCount = surface.count_entities_filtered(unitCountQuery)
local closeBy = false
if (unitCount > 300) then
for i=1,#natives.squads do
local squadGroup = natives.squads[i].group
if squadGroup.valid and (euclideanDistanceNamed(squadGroup.position, chunk) < CHUNK_SIZE * 2) then
local closeBy = false
local squads = natives.squads
for i=1, #squads do
local squadGroup = squads[i].group
if squadGroup.valid and (euclideanDistanceNamed(squadGroup.position, chunk) < DOUBLE_CHUNK_SIZE) then
closeBy = true
break
end
end
if not closeBy then
local units = surface.find_enemy_units(chunk, TRIPLE_CHUNK_SIZE)
unitCount = #units
for i=1,unitCount do
units[i].destroy()
end
natives.points = natives.points + (unitCount * unitRefundAmount)
if (natives.points > AI_MAX_OVERFLOW_POINTS) then
natives.points = AI_MAX_OVERFLOW_POINTS
end
end
end
if (unitCount > 300) and not closeBy then
local weight = AI_UNIT_REFUND * evolution_factor
local units = surface.find_enemy_units(chunk, CHUNK_SIZE * 3)
for i=1,#units do
units[i].destroy()
natives.points = natives.points + weight
end
if (natives.points > (AI_MAX_POINTS * 3)) then
natives.points = (AI_MAX_POINTS * 3)
end
end
local entities = surface.find_entities_filtered(playerQuery)
local playerBaseGenerator = 0
local safeBuildings = natives.safeBuildings
for i=1,#entities do
@@ -249,8 +255,6 @@ function mapProcessor.scanMap(regionMap, surface, natives, evolution_factor)
end
end
chunk[NEST_COUNT] = spawners
chunk[WORM_COUNT] = worms
chunk[PLAYER_BASE_GENERATOR] = playerBaseGenerator
end
+6 -6
View File
@@ -44,8 +44,8 @@ end
/|\
6 7 8
]]--
function mapUtils.getNeighborChunks(regionMap, chunkX, chunkY)
local neighbors = {false,false,false,false,false,false,false,false}
function mapUtils.getNeighborChunks(regionMap, chunkX, chunkY, neighbors)
--local neighbors = {false,false,false,false,false,false,false,false}
local chunkYRow1 = chunkY - 1
local chunkYRow3 = chunkY + 1
local xChunks = regionMap[chunkX-1]
@@ -83,8 +83,8 @@ function mapUtils.canMoveChunkDirection(direction, startChunk, endChunk)
return canMove
end
function mapUtils.getNeighborChunksWithDirection(regionMap, chunkX, chunkY)
local neighbors = {false,false,false,false,false,false,false,false}
function mapUtils.getNeighborChunksWithDirection(regionMap, chunkX, chunkY, neighbors)
--local neighbors = {false,false,false,false,false,false,false,false}
local chunkYRow1 = chunkY - 1
local chunkYRow3 = chunkY + 1
@@ -137,8 +137,8 @@ function mapUtils.getCardinalChunksWithDirection(regionMap, chunkX, chunkY)
return neighbors
end
function mapUtils.getCardinalChunks(regionMap, chunkX, chunkY)
local neighbors = {false,false,false,false}
function mapUtils.getCardinalChunks(regionMap, chunkX, chunkY, neighbors)
--local neighbors = {false,false,false,false}
local xChunks = regionMap[chunkX]
if xChunks then
neighbors[1] = xChunks[chunkY-1]
+4 -3
View File
@@ -58,13 +58,14 @@ function pheromoneUtils.playerScent(playerChunk)
playerChunk[PLAYER_PHEROMONE] = playerChunk[PLAYER_PHEROMONE] + PLAYER_PHEROMONE_GENERATOR_AMOUNT
end
function pheromoneUtils.processPheromone(regionMap, chunk)
function pheromoneUtils.processPheromone(regionMap, chunk, tempNeighbors)
if not chunk[NORTH_SOUTH_PASSABLE] and not chunk[EAST_WEST_PASSABLE] then
return
end
getCardinalChunks(regionMap, chunk.cX, chunk.cY, tempNeighbors)
local neighbors = getCardinalChunks(regionMap, chunk.cX, chunk.cY)
local chunkMovement = chunk[MOVEMENT_PHEROMONE]
local chunkBase = chunk[BASE_PHEROMONE]
local chunkPlayer = chunk[PLAYER_PHEROMONE]
@@ -74,7 +75,7 @@ function pheromoneUtils.processPheromone(regionMap, chunk)
local totalPlayer = 0
for i=1,4 do
local neighborChunk = neighbors[i]
local neighborChunk = tempNeighbors[i]
if neighborChunk then
totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunkMovement)
totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunkBase)
+31 -37
View File
@@ -19,12 +19,9 @@ local SQUAD_RETREATING = constants.SQUAD_RETREATING
local SQUAD_GUARDING = constants.SQUAD_GUARDING
local GROUP_MERGE_DISTANCE = constants.GROUP_MERGE_DISTANCE
local NO_RETREAT_BASE_PERCENT = constants.NO_RETREAT_BASE_PERCENT
local NO_RETREAT_EVOLUTION_BONUS_MAX = constants.NO_RETREAT_EVOLUTION_BONUS_MAX
local NO_RETREAT_SQUAD_SIZE_BONUS_MAX = constants.NO_RETREAT_SQUAD_SIZE_BONUS_MAX
local AI_MAX_POINTS = constants.AI_MAX_POINTS
local AI_UNIT_REFUND = constants.AI_UNIT_REFUND
local AI_MAX_OVERFLOW_POINTS = constants.AI_MAX_OVERFLOW_POINTS
local AI_MAX_BITER_GROUP_SIZE = constants.AI_MAX_BITER_GROUP_SIZE
@@ -32,7 +29,6 @@ local AI_MAX_BITER_GROUP_SIZE = constants.AI_MAX_BITER_GROUP_SIZE
local mLog = math.log10
local tableRemove = table.remove
local tableInsert = table.insert
local euclideanDistanceNamed = mapUtils.euclideanDistanceNamed
@@ -41,6 +37,7 @@ local euclideanDistanceNamed = mapUtils.euclideanDistanceNamed
function unitGroupUtils.findNearBySquad(natives, position, distance, filter)
local squads = natives.squads
for i=1,#squads do
local squad = squads[i]
local unitGroup = squad.group
@@ -83,27 +80,26 @@ function unitGroupUtils.membersToSquad(squad, members, overwriteGroup)
end
function unitGroupUtils.convertUnitGroupToSquad(natives, unitGroup)
local returnSquad
if (unitGroup ~= nil) then
local squads = natives.squads
for i=1,#squads do
local squad = squads[i]
if (squad.group == unitGroup) then
return squad
end
end
returnSquad = { group = unitGroup,
status = SQUAD_GUARDING,
penalties = {},
rabid = false,
frenzy = false,
kamikaze = false,
frenzyPosition = {x = 0,
y = 0},
cycles = 0 }
squads[#squads+1] = returnSquad
if not unitGroup then
return nil
end
local squads = natives.squads
for i=1,#squads do
local squad = squads[i]
if (squad.group == unitGroup) then
return squad
end
end
local returnSquad = { group = unitGroup,
status = SQUAD_GUARDING,
penalties = {},
rabid = false,
frenzy = false,
kamikaze = false,
frenzyPosition = {x = 0,
y = 0},
cycles = 0 }
squads[#squads+1] = returnSquad
return returnSquad
end
@@ -135,11 +131,9 @@ function unitGroupUtils.lookupSquadMovementPenalty(squad, chunkX, chunkY)
return 0
end
function unitGroupUtils.calculateKamikazeThreshold(squad, natives, evolution_factor)
local maxSize = natives.attackWaveMaxSize
local kamikazeThreshold = NO_RETREAT_BASE_PERCENT + (evolution_factor * NO_RETREAT_EVOLUTION_BONUS_MAX)
local squadSizeBonus = mLog((#squad.group.members / maxSize) + 0.1) + 1
return kamikazeThreshold + (NO_RETREAT_SQUAD_SIZE_BONUS_MAX * squadSizeBonus)
function unitGroupUtils.calculateKamikazeThreshold(squad, natives)
local squadSizeBonus = mLog((#squad.group.members / natives.attackWaveMaxSize) + 0.1) + 1
return natives.kamikazeThreshold + (NO_RETREAT_SQUAD_SIZE_BONUS_MAX * squadSizeBonus)
end
local function isAttacking(group)
@@ -147,11 +141,11 @@ local function isAttacking(group)
return (state == DEFINES_GROUP_STATE_ATTACKING_TARGET) or (state == DEFINES_GROUP_STATE_ATTACKING_DISTRACTION)
end
function unitGroupUtils.cleanSquads(natives, evolution_factor)
function unitGroupUtils.cleanSquads(natives)
local squads = natives.squads
local squadCount = #squads
local weight = AI_UNIT_REFUND * evolution_factor
local weight = natives.unitRefundAmount
local cleanSquads = {}
@@ -166,11 +160,11 @@ function unitGroupUtils.cleanSquads(natives, evolution_factor)
local members = group.members
for x=1,memberCount do
members[x].destroy()
natives.points = natives.points + weight
end
natives.points = natives.points + (memberCount * weight)
if (natives.points > (AI_MAX_POINTS * 3)) then
natives.points = (AI_MAX_POINTS * 3)
if (natives.points > AI_MAX_OVERFLOW_POINTS) then
natives.points = AI_MAX_OVERFLOW_POINTS
end
group.destroy()
else
@@ -197,7 +191,7 @@ function unitGroupUtils.cleanSquads(natives, evolution_factor)
end
function unitGroupUtils.regroupSquads(natives, evolution_factor)
function unitGroupUtils.regroupSquads(natives)
local groupThreshold = AI_MAX_BITER_GROUP_SIZE * 0.75
local squads = natives.squads
@@ -235,7 +229,7 @@ function unitGroupUtils.regroupSquads(natives, evolution_factor)
end
end
if mergedSquads and not squad.kamikaze then
local kamikazeThreshold = unitGroupUtils.calculateKamikazeThreshold(squad, natives, evolution_factor)
local kamikazeThreshold = unitGroupUtils.calculateKamikazeThreshold(squad, natives)
if (math.random() < kamikazeThreshold) then
squad.kamikaze = true
end
+1 -1
View File
@@ -179,7 +179,7 @@ data:extend({
name = "rampant-useCustomAI",
description = "rampant-useCustomAI",
setting_type = 'startup',
default_value = false,
default_value = true,
order = "h[total]-a[ai]",
per_user = false
}