diff --git a/README.md b/README.md index 92eb601..09c7118 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Rampant Tactics -Factorio Mod - Improves the enemies tactics by using potential fields/pheromones allowing probing of defenses, retreats, reinforcements, counterattacking, rallying death cry, and player hunting. Also removes homing biter projectiles. +Factorio Mod - Improves the enemies tactics by using potential fields/pheromones allowing probing of defenses, retreats, reinforcements, counterattacking, breaching, rallying death cry, and player hunting. Also removes homing biter projectiles. # Forum Post @@ -15,16 +15,18 @@ There will be a slight pause the first time this is started up due to indexing a MP should be working -Configure Options: -- Max biter wave size -- Ramp up to max biter wave size -- Use dumb projectiles -- Use Natural Evolution unit launchers (Requires NE) -- Togglable attack wave triggers (pollution, player) -- Attack wave pollution trigger threshold +Configure Options: +- Max biter wave size +- Ramp up to max biter wave size +- Use dumb projectiles +- Use Natural Evolution unit launchers (Requires NE) +- Togglable attack wave triggers (pollution, player) +- Attack wave pollution trigger threshold # Features +- Recycling Biters - When large groups of biters form on the game map and aren't assigned to a unit group or near a base will be periodically removed and refunded to the ai causes attack waves proportional to the number of units removed. +- Breaching - When biters are destroying structures nearby unit groups will come to join them - Frenzy squads - When a unit group gets close to a player or start combat they switch to attacking everything in there path for a set radius or until there is nothing left - Rabid squads - Is in a permanent frenzied state as soon as the group is formed - Tactical Retreats - these will take place when a unit group is in a chunk that has reached a death threshold @@ -50,6 +52,18 @@ Configure Options: # Version History +0.14.13 - +- Reverted: Pheromone generation tweaks from 0.14.11 +- Feature: Recycling large biter swarms that are stuck (causes a big attack wave, and pops up a message) +- Feature: Biters destroying player buildings will attract other unit groups +- Tweak: Reduced unit group max size from 600 to 450 +- Tweak: Reduced unit group radius from 30 to 20 +- Fix: Unit groups merging during combat +- Optimization: Cleaned up regroup squad function to scale better +- Optimization: Retreats only fire once per chunk for every logic cycle +- Improvement: Cleaned up dispersion function for Pheromones +- Improvement: Increased attack wave frequency based on evolution factor (mainly for endgame) + 0.14.12 - - Tweak: Decreased slow map scan chunks scanned per logic cycle from 8 to 6 - Fix: Didn't set new version number forcing chunk recalculation every load diff --git a/control.lua b/control.lua index 23ad19b..80cecef 100644 --- a/control.lua +++ b/control.lua @@ -47,6 +47,7 @@ local planning = aiPlanning.planning local rallyUnits = aiBuilding.rallyUnits local deathScent = pheromoneUtils.deathScent +local victoryScent = pheromoneUtils.victoryScent local regroupSquads = unitGroupUtils.regroupSquads local convertUnitGroupToSquad = unitGroupUtils.convertUnitGroupToSquad @@ -145,7 +146,6 @@ local function onConfigChanged() -- used to rate limit the number of rally cries during a period of time natives.rallyCries = MAX_RALLY_CRIES - natives.retreats = MAX_RETREATS global.version = constants.VERSION_13 end @@ -158,8 +158,14 @@ local function onConfigChanged() game.surfaces[1].print("Rampant - Version 0.14.11") global.version = constants.VERSION_14 end - if (global.version < constants.VERSION_15) then + if (global.version < constants.VERSION_16) then + natives.lastShakeMessage = 0 + --remove version 14 retreat limit, it has been made redundant + natives.retreats = nil + + game.map_settings.unit_group.max_group_radius = 20 + -- clear old regionMap processing Queue -- prevents queue adding duplicate chunks -- chunks are by key, so should overwrite old @@ -178,8 +184,8 @@ local function onConfigChanged() y = chunk.y * 32 }}}) end - game.surfaces[1].print("Rampant - Version 0.14.12") - global.version = constants.VERSION_15 + game.surfaces[1].print("Rampant - Version 0.14.13") + global.version = constants.VERSION_16 end end @@ -192,7 +198,7 @@ local function onTick(event) local players = game.players processPendingChunks(regionMap, surface, pendingChunks) - scanMap(regionMap, surface) + scanMap(regionMap, surface, natives, evolutionFactor, tick) if (tick == regionMap.logicTick) then regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC @@ -242,15 +248,13 @@ local function onDeath(event) local evolutionFactor = game.evolution_factor if (deathChunk[MOVEMENT_PHEROMONE] < -(evolutionFactor * RETREAT_MOVEMENT_PHEROMONE_LEVEL)) then - if (natives.retreats >= 0) then - natives.retreats = natives.retreats - 1 - retreatUnits(deathChunk, - convertUnitGroupToSquad(natives, - entity.unit_group), - regionMap, - surface, - natives) - end + retreatUnits(deathChunk, + convertUnitGroupToSquad(natives, + entity.unit_group), + regionMap, + surface, + natives, + event.tick) local rallyThreshold = BASE_RALLY_CHANCE + (evolutionFactor * BONUS_RALLY_CHANCE) if (natives.rallyCries >= 0) and (math.random() < rallyThreshold) then natives.rallyCries = natives.rallyCries - 1 @@ -272,6 +276,9 @@ local function onDeath(event) local creditNatives = false if (event.force ~= nil) and (event.force.name == "enemy") then creditNatives = true + local entityPosition = entity.position + local victoryChunk = getChunkByPosition(regionMap, entityPosition.x, entityPosition.y) + victoryScent(victoryChunk, entity.type) end if config.safeBuildings and (config.safeEntities[entity.type] or config.safeEntityName[entity.name]) then makeImmortalEntity(surface, entity) diff --git a/info.json b/info.json index 29066d8..451405f 100644 --- a/info.json +++ b/info.json @@ -1,10 +1,10 @@ { "name" : "Rampant", "factorio_version" : "0.14", - "version" : "0.14.12", + "version" : "0.14.13", "title" : "Rampant AI", "author" : "Veden", "homepage" : "https://forums.factorio.com/viewtopic.php?f=94&t=31445", - "description" : "Improves the enemies tactics by using potential fields/pheromones allowing probing of defenses, retreats, reinforcements, counterattacking, rallying death cry, and player hunting. Also removes homing biter projectiles.", + "description" : "Improves the enemies tactics by using potential fields/pheromones allowing probing of defenses, retreats, reinforcements, counterattacking, breaching, rallying death cry, and player hunting. Also removes homing biter projectiles.", "dependencies" : ["base >= 0.13.17", "? bobenemies", "? Natural_Evolution_Enemies"] } diff --git a/libs/AIBuilding.lua b/libs/AIBuilding.lua index 253a295..6444a7a 100644 --- a/libs/AIBuilding.lua +++ b/libs/AIBuilding.lua @@ -54,7 +54,10 @@ local function attackWaveValidCandidate(chunk, surface, evolutionFactor) local total = 0; if CONFIG_USE_PLAYER_PROXIMITY then - total = total + chunk[PLAYER_PHEROMONE] + local playerPheromone = chunk[PLAYER_PHEROMONE] + if (playerPheromone > 7) then + total = total + chunk[PLAYER_PHEROMONE] + end end if CONFIG_USE_POLLUTION_PROXIMITY then total = total + surface.get_pollution({chunk.pX, chunk.pY}) diff --git a/libs/AIDefense.lua b/libs/AIDefense.lua index c9971c8..0994454 100644 --- a/libs/AIDefense.lua +++ b/libs/AIDefense.lua @@ -23,6 +23,10 @@ local SQUAD_RETREATING = constants.SQUAD_RETREATING local RETREAT_FILTER = constants.RETREAT_FILTER +local RETREAT_TRIGGERED = constants.RETREAT_TRIGGERED + +local INTERVAL_LOGIC = constants.INTERVAL_LOGIC + -- imported functions local getNeighborChunksWithDirection = mapUtils.getNeighborChunksWithDirection @@ -41,12 +45,12 @@ end local function scoreRetreatLocation(position, squad, neighborChunk, surface) local safeScore = -neighborChunk[BASE_PHEROMONE] + neighborChunk[MOVEMENT_PHEROMONE] - local dangerScore = surface.get_pollution(position) + (neighborChunk[PLAYER_PHEROMONE] * 25) + (neighborChunk[ENEMY_BASE_GENERATOR] * 50) + local dangerScore = surface.get_pollution(position) + (neighborChunk[PLAYER_PHEROMONE] * 100) + (neighborChunk[ENEMY_BASE_GENERATOR] * 50) return safeScore - dangerScore end -function aiDefense.retreatUnits(chunk, squad, regionMap, surface, natives) - if (chunk[ENEMY_BASE_GENERATOR] == 0) then +function aiDefense.retreatUnits(chunk, squad, regionMap, surface, natives, tick) + if (tick - chunk[RETREAT_TRIGGERED] > INTERVAL_LOGIC) and (chunk[ENEMY_BASE_GENERATOR] == 0) then local performRetreat = false local enemiesToSquad @@ -63,6 +67,7 @@ function aiDefense.retreatUnits(chunk, squad, regionMap, surface, natives) end if performRetreat then + chunk[RETREAT_TRIGGERED] = tick local retreatPosition = {x=0, y=0} local exitPath,_ = scoreNeighborsWithDirection(chunk, getNeighborChunksWithDirection(regionMap, chunk.cX, chunk.cY), diff --git a/libs/AIPlanning.lua b/libs/AIPlanning.lua index 562aaec..9387bcd 100644 --- a/libs/AIPlanning.lua +++ b/libs/AIPlanning.lua @@ -29,7 +29,7 @@ local mMax = math.max function aiPlanning.planning(natives, evolution_factor, tick) local maxPoints = AI_MAX_POINTS * evolution_factor if (natives.points < maxPoints) then - natives.points = natives.points + math.floor(AI_POINT_GENERATOR_AMOUNT * math.random()) + natives.points = natives.points + math.floor((AI_POINT_GENERATOR_AMOUNT * math.random()) + ((AI_POINT_GENERATOR_AMOUNT * 0.7) * (evolution_factor ^ 2.5))) end if (natives.temperamentTick == tick) then diff --git a/libs/ChunkUtils.lua b/libs/ChunkUtils.lua index 2a0da38..6d4b44b 100644 --- a/libs/ChunkUtils.lua +++ b/libs/ChunkUtils.lua @@ -24,6 +24,8 @@ local ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT = constants.ENEMY_BASE_PHEROMONE_GEN local CHUNK_TICK = constants.CHUNK_TICK +local RETREAT_TRIGGERED = constants.RETREAT_TRIGGERED + -- module code function chunkUtils.checkForDeadendTiles(constantCoordinate, iteratingCoordinate, direction, surface) @@ -116,6 +118,7 @@ function chunkUtils.createChunk(topX, topY) chunk[NORTH_SOUTH_PASSABLE] = false chunk[EAST_WEST_PASSABLE] = false chunk[CHUNK_TICK] = 0 + chunk[RETREAT_TRIGGERED] = 0 return chunk end diff --git a/libs/Constants.lua b/libs/Constants.lua index 5f1fb78..4b6b98e 100644 --- a/libs/Constants.lua +++ b/libs/Constants.lua @@ -10,6 +10,7 @@ constants.VERSION_12 = 12 constants.VERSION_13 = 13 constants.VERSION_14 = 14 constants.VERSION_15 = 15 +constants.VERSION_16 = 16 -- misc @@ -37,8 +38,10 @@ constants.AI_BASE_BUILDING_COST = 500 constants.AI_TUNNEL_COST = 100 constants.AI_MAX_POINTS = 10000 +constants.AI_UNIT_REFUND = 2 + constants.AI_MAX_SQUAD_COUNT = 40 -constants.AI_MAX_BITER_GROUP_SIZE = 600 +constants.AI_MAX_BITER_GROUP_SIZE = 450 constants.AI_STATE_PEACEFUL = 1 constants.AI_STATE_AGGRESSIVE = 2 @@ -66,17 +69,16 @@ constants.EAST_WEST = 2 constants.MOVEMENT_PHEROMONE_GENERATOR_AMOUNT = 500 constants.ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT = 30 -constants.DEATH_PHEROMONE_GENERATOR_AMOUNT = 75 -constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT = 100 +constants.DEATH_PHEROMONE_GENERATOR_AMOUNT = 500 +constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT = 150 + +constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT = -30 -- pheromone diffusion amounts -constants.STANDARD_PHERONOME_DIFFUSION_AMOUNT = 0.1 -constants.MOVEMENT_PHEROMONE_DIFFUSION_AMOUNT = 0.02 - constants.MOVEMENT_PHEROMONE_PERSISTANCE = 0.98 -constants.REDUCED_MOVEMENT_PHEROMONE_PERSISTANCE = 0.65 -constants.STANDARD_PHEROMONE_PERSISTANCE = 0.98 +constants.BASE_PHEROMONE_PERSISTANCE = 0.99 +constants.PLAYER_PHEROMONE_PERSISTANCE = 0.98 -- chunk attributes @@ -91,6 +93,7 @@ constants.NORTH_SOUTH_PASSABLE = 6 constants.EAST_WEST_PASSABLE = 7 constants.CHUNK_TICK = 8 +constants.RETREAT_TRIGGERED = 9 -- Squad status @@ -116,26 +119,26 @@ constants.GROUP_MERGE_DISTANCE = 28 -- player building pheromones constants.BUILDING_PHEROMONES = {} -constants.BUILDING_PHEROMONES["generator"] = 12 -constants.BUILDING_PHEROMONES["pump"] = 5 -constants.BUILDING_PHEROMONES["offshore-pump"] = 5 -constants.BUILDING_PHEROMONES["transport-belt"] = 2 -constants.BUILDING_PHEROMONES["accumulator"] = 14 -constants.BUILDING_PHEROMONES["solar-panel"] = 12 -constants.BUILDING_PHEROMONES["boiler"] = 16 -constants.BUILDING_PHEROMONES["assembling-machine"] = 16 -constants.BUILDING_PHEROMONES["roboport"] = 14 -constants.BUILDING_PHEROMONES["beacon"] = 14 -constants.BUILDING_PHEROMONES["furnace"] = 16 -constants.BUILDING_PHEROMONES["mining-drill"] = 19 +constants.BUILDING_PHEROMONES["generator"] = 8 +constants.BUILDING_PHEROMONES["pump"] = 2 +constants.BUILDING_PHEROMONES["offshore-pump"] = 2 +constants.BUILDING_PHEROMONES["transport-belt"] = 1 +constants.BUILDING_PHEROMONES["accumulator"] = 10 +constants.BUILDING_PHEROMONES["solar-panel"] = 8 +constants.BUILDING_PHEROMONES["boiler"] = 12 +constants.BUILDING_PHEROMONES["assembling-machine"] = 10 +constants.BUILDING_PHEROMONES["roboport"] = 10 +constants.BUILDING_PHEROMONES["beacon"] = 10 +constants.BUILDING_PHEROMONES["furnace"] = 12 +constants.BUILDING_PHEROMONES["mining-drill"] = 15 -- player defense pheromones -constants.BUILDING_PHEROMONES["ammo-turret"] = 5 -constants.BUILDING_PHEROMONES["wall"] = 0.55 +constants.BUILDING_PHEROMONES["ammo-turret"] = 2.5 +constants.BUILDING_PHEROMONES["wall"] = 0.25 constants.BUILDING_PHEROMONES["electric-turret"] = 7 constants.BUILDING_PHEROMONES["fluid-turret"] = 9 -constants.BUILDING_PHEROMONES["turret"] = 5 +constants.BUILDING_PHEROMONES["turret"] = 2.5 constants.retreatFilter = {} constants.retreatFilter[constants.SQUAD_RETREATING] = true diff --git a/libs/MapProcessor.lua b/libs/MapProcessor.lua index a740933..d6c2a39 100644 --- a/libs/MapProcessor.lua +++ b/libs/MapProcessor.lua @@ -16,6 +16,7 @@ local RETREAT_MOVEMENT_PHEROMONE_LEVEL = constants.RETREAT_MOVEMENT_PHEROMONE_LE local SCAN_QUEUE_SIZE = constants.SCAN_QUEUE_SIZE +local AI_UNIT_REFUND = constants.AI_UNIT_REFUND local CHUNK_SIZE = constants.CHUNK_SIZE local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR local AI_STATE_AGGRESSIVE = constants.AI_STATE_AGGRESSIVE @@ -23,6 +24,7 @@ local AI_STATE_AGGRESSIVE = constants.AI_STATE_AGGRESSIVE local PROCESS_PLAYER_BOUND = constants.PROCESS_PLAYER_BOUND local CHUNK_TICK = constants.CHUNK_TICK +local AI_MAX_POINTS = constants.AI_MAX_POINTS local AI_SQUAD_COST = constants.AI_SQUAD_COST local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST @@ -30,6 +32,8 @@ local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES +local TICKS_A_MINUTE = constants.TICKS_A_MINUTE + -- imported functions local scents = pheromoneUtils.scents @@ -91,17 +95,17 @@ function mapProcessor.processMap(regionMap, surface, natives, evolution_factor) local endIndex = mMin(index + PROCESS_QUEUE_SIZE, #processQueue) for x=index,endIndex do local chunk = processQueue[x] - - scents(chunk) - + + processPheromone(regionMap, chunk) + if scouts then makeScouts(surface, natives, chunk, evolution_factor) end if squads then formSquads(regionMap, surface, natives, chunk, evolution_factor, AI_SQUAD_COST) - end - - processPheromone(regionMap, chunk) + end + + scents(chunk) end if (endIndex == #processQueue) then @@ -163,7 +167,8 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, evolu if (chunk ~= nil) and (chunk[CHUNK_TICK] ~= tick) then chunk[CHUNK_TICK] = tick - scents(chunk) + + processPheromone(regionMap, chunk) if scouts then makeScouts(surface, natives, chunk, evolution_factor) @@ -174,8 +179,9 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, evolu if vengence then formSquads(regionMap, surface, natives, chunk, evolution_factor, AI_VENGENCE_SQUAD_COST) end - - processPheromone(regionMap, chunk) + + scents(chunk) + end end end @@ -187,7 +193,7 @@ end --[[ Passive scan to find entities that have been generated outside the factorio event system --]] -function mapProcessor.scanMap(regionMap, surface) +function mapProcessor.scanMap(regionMap, surface, natives, evolution_factor, tick) local index = regionMap.scanPointer local processQueue = regionMap.processQueue @@ -196,17 +202,44 @@ function mapProcessor.scanMap(regionMap, surface) local chunk = processQueue[x] local entities = surface.find_entities_filtered({area = {{chunk.pX, chunk.pY}, - {chunk.pX + CHUNK_SIZE, chunk.pY + CHUNK_SIZE}}, - force = "player"}) + {chunk.pX + CHUNK_SIZE, chunk.pY + CHUNK_SIZE}}, + force = "player"}) local spawners = surface.count_entities_filtered({area = {{chunk.pX, chunk.pY}, {chunk.pX + CHUNK_SIZE, chunk.pY + CHUNK_SIZE}}, type = "unit-spawner", force = "enemy"}) + local worms = surface.count_entities_filtered({area = {{chunk.pX, chunk.pY}, {chunk.pX + CHUNK_SIZE, chunk.pY + CHUNK_SIZE}}, type = "turret", force = "enemy"}) + + local unitCount = surface.count_entities_filtered({area = {{chunk.pX, chunk.pY}, + {chunk.pX + CHUNK_SIZE, chunk.pY + CHUNK_SIZE}}, + type = "unit", + force = "enemy"}) + + if (unitCount > 550) then + local weight = AI_UNIT_REFUND * evolution_factor + local units = surface.find_enemy_units({chunk.pX, chunk.pY}, + 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 + + if (tick - natives.lastShakeMessage > TICKS_A_MINUTE * 5) then + natives.lastShakeMessage = tick + surface.print("Rampant: The ground begins to shake") + end + end + local playerBaseGenerator = 0 for i=1,#entities do local entity = entities[i] @@ -219,7 +252,7 @@ function mapProcessor.scanMap(regionMap, surface) chunk[ENEMY_BASE_GENERATOR] = (spawners * ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT) + worms chunk[PLAYER_BASE_GENERATOR] = playerBaseGenerator end - + if (endIndex == #processQueue) then regionMap.scanPointer = 1 else diff --git a/libs/PheromoneUtils.lua b/libs/PheromoneUtils.lua index 3371aa7..5b2ef27 100644 --- a/libs/PheromoneUtils.lua +++ b/libs/PheromoneUtils.lua @@ -11,40 +11,45 @@ local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE local BASE_PHEROMONE = constants.BASE_PHEROMONE local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE +local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES + local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR -local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR local PLAYER_PHEROMONE_GENERATOR_AMOUNT = constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT -local DEATH_PHEROMONE_GENERATOR_AMOUNT = constants.MOVEMENT_PHEROMONE_GENERATOR_AMOUNT - -local STANDARD_PHERONOME_DIFFUSION_AMOUNT = constants.STANDARD_PHERONOME_DIFFUSION_AMOUNT -local MOVEMENT_PHEROMONE_DIFFUSION_AMOUNT = constants.MOVEMENT_PHEROMONE_DIFFUSION_AMOUNT +local DEATH_PHEROMONE_GENERATOR_AMOUNT = constants.DEATH_PHEROMONE_GENERATOR_AMOUNT local MOVEMENT_PHEROMONE_PERSISTANCE = constants.MOVEMENT_PHEROMONE_PERSISTANCE -local REDUCED_MOVEMENT_PHEROMONE_PERSISTANCE = constants.REDUCED_MOVEMENT_PHEROMONE_PERSISTANCE -local STANDARD_PHEROMONE_PERSISTANCE = constants.STANDARD_PHEROMONE_PERSISTANCE +local BASE_PHEROMONE_PERSISTANCE = constants.BASE_PHEROMONE_PERSISTANCE +local PLAYER_PHEROMONE_PERSISTANCE = constants.PLAYER_PHEROMONE_PERSISTANCE local NORTH_SOUTH_PASSABLE = constants.NORTH_SOUTH_PASSABLE local EAST_WEST_PASSABLE = constants.EAST_WEST_PASSABLE +local IMPASSABLE_TERRAIN_GENERATOR_AMOUNT = constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT + -- imported functions -local getChunkByPosition = mapUtils.getChunkByPosition local getCardinalChunks = mapUtils.getCardinalChunks -- module code function pheromoneUtils.scents(chunk) - -- if (chunk[]) then - -- end + if not chunk[NORTH_SOUTH_PASSABLE] and not chunk[EAST_WEST_PASSABLE] then - chunk[BASE_PHEROMONE] = -100; + chunk[BASE_PHEROMONE] = IMPASSABLE_TERRAIN_GENERATOR_AMOUNT; else chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + chunk[PLAYER_BASE_GENERATOR] -- chunk[ENEMY_BASE_GENERATOR] end end +function pheromoneUtils.victoryScent(chunk, entityType) + local value = BUILDING_PHEROMONES[entityType] + if (value ~= nil) then + chunk[MOVEMENT_PHEROMONE] = chunk[MOVEMENT_PHEROMONE] + (value * 10000) + end +end + function pheromoneUtils.deathScent(chunk) chunk[MOVEMENT_PHEROMONE] = chunk[MOVEMENT_PHEROMONE] - DEATH_PHEROMONE_GENERATOR_AMOUNT end @@ -54,52 +59,26 @@ function pheromoneUtils.playerScent(playerChunk) end function pheromoneUtils.processPheromone(regionMap, chunk) - local neighbors - -- pheromone level indexes on chunks are 1 - 3 - -- unrolled loop one level - local diffusionAmount = MOVEMENT_PHEROMONE_DIFFUSION_AMOUNT - local persistence - if (chunk[BASE_PHEROMONE] < 0) then - persistence = REDUCED_MOVEMENT_PHEROMONE_PERSISTANCE - else - persistence = MOVEMENT_PHEROMONE_PERSISTANCE + if not chunk[NORTH_SOUTH_PASSABLE] and not chunk[EAST_WEST_PASSABLE] then + return end - local totalDiffused = 0 - local chunkValue = chunk[MOVEMENT_PHEROMONE] * persistence - local diffusedAmount = chunkValue * diffusionAmount - if (diffusedAmount > 1.5) or (diffusedAmount < -1.5) then - neighbors = getCardinalChunks(regionMap, chunk.cX, chunk.cY) - for i=1,#neighbors do - local neighborChunk = neighbors[i] - if (neighborChunk ~= nil) and (neighborChunk[NORTH_SOUTH_PASSABLE] or neighborChunk[EAST_WEST_PASSABLE]) then - totalDiffused = totalDiffused + diffusedAmount - neighborChunk[MOVEMENT_PHEROMONE] = neighborChunk[MOVEMENT_PHEROMONE] + diffusedAmount - end - end - end - chunk[MOVEMENT_PHEROMONE] = (chunkValue - totalDiffused) - diffusionAmount = STANDARD_PHERONOME_DIFFUSION_AMOUNT - persistence = STANDARD_PHEROMONE_PERSISTANCE - for x=2,3 do - totalDiffused = 0 - chunkValue = chunk[x] * persistence - diffusedAmount = chunkValue * diffusionAmount - if (diffusedAmount > 1.5) or (diffusedAmount < -1.5) then - if (neighbors == nil) then - neighbors = getCardinalChunks(regionMap, chunk.cX, chunk.cY) - end - for i=1,#neighbors do - local neighborChunk = neighbors[i] - if (neighborChunk ~= nil) and (neighborChunk[NORTH_SOUTH_PASSABLE] or neighborChunk[EAST_WEST_PASSABLE]) then - totalDiffused = totalDiffused + diffusedAmount - neighborChunk[x] = neighborChunk[x] + diffusedAmount - end - end + local totalMovement = 0 + local totalBase = 0 + local totalPlayer = 0 + local neighbors = getCardinalChunks(regionMap, chunk.cX, chunk.cY) + for i=1,#neighbors do + local neighborChunk = neighbors[i] + if (neighborChunk ~= nil) then + totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunk[MOVEMENT_PHEROMONE]) + totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunk[BASE_PHEROMONE]) + totalPlayer = totalPlayer + (neighborChunk[PLAYER_PHEROMONE] - chunk[PLAYER_PHEROMONE]) end - chunk[x] = (chunkValue - totalDiffused) end + chunk[MOVEMENT_PHEROMONE] = (chunk[MOVEMENT_PHEROMONE] + (0.125 * totalMovement)) * MOVEMENT_PHEROMONE_PERSISTANCE + chunk[BASE_PHEROMONE] = (chunk[BASE_PHEROMONE] + (0.25 * totalBase)) * BASE_PHEROMONE_PERSISTANCE + chunk[PLAYER_PHEROMONE] = (chunk[PLAYER_PHEROMONE] + (0.25 * totalPlayer)) * PLAYER_PHEROMONE_PERSISTANCE end return pheromoneUtils diff --git a/libs/UnitGroupUtils.lua b/libs/UnitGroupUtils.lua index 888d56a..d35b7c7 100644 --- a/libs/UnitGroupUtils.lua +++ b/libs/UnitGroupUtils.lua @@ -42,7 +42,7 @@ function unitGroupUtils.findNearBySquad(natives, position, distance, filter) for i=1,#squads do local squad = squads[i] local unitGroup = squad.group - if (unitGroup ~= nil) and unitGroup.valid and ((filter == nil) or (filter ~= nil and filter[squad.status])) then + if unitGroup.valid and ((filter == nil) or (filter ~= nil and filter[squad.status])) then if (euclideanDistanceNamed(unitGroup.position, position) <= distance) then return squad end @@ -139,63 +139,91 @@ function unitGroupUtils.calculateKamikazeThreshold(squad, evolution_factor) return kamikazeThreshold + (NO_RETREAT_SQUAD_SIZE_BONUS_MAX * squadSizeBonus) end -local function isAttacking(squad) - return (squad.state == GROUP_STATE_ATTACKING_TARGET) or (squad.state == GROUP_STATE_ATTACKING_DISTRACTION) +local function isAttacking(group) + local state = group.state + return (state == GROUP_STATE_ATTACKING_TARGET) or (state == GROUP_STATE_ATTACKING_DISTRACTION) end function unitGroupUtils.regroupSquads(natives, evolution_factor) local squads = natives.squads - for i=1,#squads do - local squad = squads[i] - if squad.group.valid and not isAttacking(squad) then - local squadPosition = squad.group.position - local mergedSquads = false - for x=i+1, #squads do - local mergeSquad = squads[x] - local mergeGroup = mergeSquad.group - if mergeGroup.valid and ((#mergeGroup.members + #squad.group.members) < AI_MAX_BITER_GROUP_SIZE) and (mergeSquad.status == squad.status) and not isAttacking(mergeSquad) and (euclideanDistanceNamed(squadPosition, mergeGroup.position) < GROUP_MERGE_DISTANCE) then - unitGroupUtils.membersToSquad(squad, mergeGroup.members, true) - if mergeSquad.kamikaze then - squad.kamikaze = true - end - mergedSquads = true - mergeGroup.destroy() - end - end - if mergedSquads and not squad.kamikaze then - local kamikazeThreshold = unitGroupUtils.calculateKamikazeThreshold(squad, evolution_factor) - if (math.random() < kamikazeThreshold) then - squad.kamikaze = true - end + local squadCount = #squads + + for i=1,squadCount do + local squad = squads[i] + local group = squad.group + if group.valid then + local memberCount = #group.members + if (memberCount == 0) or (memberCount > AI_MAX_BITER_GROUP_SIZE) then + group.destroy() end end end - for i=#squads,1,-1 do + local groupThreshold = AI_MAX_BITER_GROUP_SIZE * 0.75 + + for i=1,squadCount do local squad = squads[i] - if (squad.group == nil) then - tableRemove(squads, i) - elseif not squad.group.valid then - tableRemove(squads, i) - elseif (#squad.group.members == 0) then - squad.group.destroy() - tableRemove(squads, i) - elseif (#squad.group.members > AI_MAX_BITER_GROUP_SIZE) then - squad.group.destroy() - tableRemove(squads, i) - else - if (squad.status == SQUAD_RETREATING) and (squad.cycles == 0) then + local group = squad.group + if group.valid then + local status = squad.status + local memberCount = #group.members + local squadPosition = group.position + + if not isAttacking(group) and (memberCount < groupThreshold) then + local mergedSquads = false + for x=i+1,squadCount do + local mergeSquad = squads[x] + local mergeGroup = mergeSquad.group + if mergeGroup.valid then + local mergeMembers = mergeGroup.members + local mergeCount = #mergeMembers + if ((mergeCount + memberCount) < AI_MAX_BITER_GROUP_SIZE) and (euclideanDistanceNamed(squadPosition, mergeGroup.position) < GROUP_MERGE_DISTANCE) and (mergeSquad.status == status) and not isAttacking(mergeGroup) then + for memberIndex=1, mergeCount do + group.add_member(mergeMembers[memberIndex]) + end + if mergeSquad.kamikaze then + squad.kamikaze = true + end + mergedSquads = true + mergeGroup.destroy() + end + memberCount = memberCount + mergeCount + if (memberCount > groupThreshold) then + break + end + end + end + if mergedSquads and not squad.kamikaze then + local kamikazeThreshold = unitGroupUtils.calculateKamikazeThreshold(squad, evolution_factor) + if (math.random() < kamikazeThreshold) then + squad.kamikaze = true + end + end + end + + local cycles = squad.cycles + if (status == SQUAD_RETREATING) and (cycles == 0) then squad.status = SQUAD_GUARDING squad.frenzy = true - squad.frenzyPosition.x = squad.group.position.x - squad.frenzyPosition.y = squad.group.position.y - elseif (squad.group.state == GROUP_STATE_FINISHED) then + squad.frenzyPosition.x = squadPosition.x + squad.frenzyPosition.y = squadPosition.y + elseif (group.state == GROUP_STATE_FINISHED) then squad.status = SQUAD_GUARDING - elseif (squad.cycles > 0) then - squad.cycles = squad.cycles - 1 + elseif (cycles > 0) then + squad.cycles = cycles - 1 end end end + + local cleanSquads = {} + for i=1,squadCount do + local squad = squads[i] + if squad.group.valid then + cleanSquads[#cleanSquads+1] = squad + end + end + + natives.squads = cleanSquads end diff --git a/make.rkt b/make.rkt index 9f5f5db..b5c2ce8 100644 --- a/make.rkt +++ b/make.rkt @@ -71,7 +71,7 @@ (copyDirectory "prototypes" modFolder))) (define (run) -;; (copyFiles modFolder) + ;;(copyFiles modFolder) (makeZip modFolder) ) )