From ab7aaedac557224f16c8c6c390ebe08a4e503486 Mon Sep 17 00:00:00 2001 From: veden Date: Sat, 6 Aug 2016 20:38:47 -0700 Subject: [PATCH] death pheromone working --- control.lua | 39 ++++++--- libs/AI.lua | 182 +++++++++++++++++++--------------------- libs/Constants.lua | 18 +++- libs/PheromoneUtils.lua | 62 +++++++------- libs/UnitGroupUtils.lua | 89 ++++++++++++++++++-- libs/chunkUtils.lua | 13 +-- tests.lua | 21 ++++- 7 files changed, 268 insertions(+), 156 deletions(-) diff --git a/control.lua b/control.lua index 43ae42f..5dcdae6 100755 --- a/control.lua +++ b/control.lua @@ -1,5 +1,6 @@ local chunkUtils = require("libs/ChunkUtils") local mapUtils = require("libs/MapUtils") +local unitGroupUtils = require("libs/UnitGroupUtils") local chunkProcessor = require("libs/ChunkProcessor") local mapProcessor = require("libs/MapProcessor") local constants = require("libs/Constants") @@ -65,26 +66,34 @@ end function onTick(event) if (event.tick % 40 == 0) then -- using coroutines to keep the cpu load time managable will still being able to work large maps + local working, errorMsg = true, nil if (chunkRoutine ~= nil) and (coroutine.status(chunkRoutine) ~= "dead") then - coroutine.resume(chunkRoutine) + working, errorMsg = coroutine.resume(chunkRoutine) elseif (#pendingChunks > 0) then -- coroutines start suspended, so you have to resume them after creation chunkRoutine = coroutine.create(chunkProcessor.processPendingChunks) - coroutine.resume(chunkRoutine, regionMap, surface, natives, pendingChunks) + working, errorMsg = coroutine.resume(chunkRoutine, regionMap, surface, natives, pendingChunks) + end + if not working then + error(errorMsg) end -- put down player pheromone for player hunters - -- pheromoneUtils.playerScent(regionMap, game.players) + pheromoneUtils.playerScent(regionMap, game.players) -- ai.attackPlayerNearNest(regionMap, surface, natives, game.players) if (mapRoutine ~= nil) and (coroutine.status(mapRoutine) ~= "dead") then - coroutine.resume(mapRoutine) + working, errorMsg = coroutine.resume(mapRoutine) elseif (mapRoutine == nil) or (coroutine.status(mapRoutine) == "dead") then mapRoutine = coroutine.create(mapProcessor.processMap) - coroutine.resume(mapRoutine, regionMap, surface, natives) + working, errorMsg = coroutine.resume(mapRoutine, regionMap, surface, natives) + end + if not working then + error(errorMsg) end + unitGroupUtils.regroupSquads(natives) end end @@ -96,12 +105,11 @@ function onDeath(event) pheromoneUtils.deathScent(regionMap, entityPosition.x, entityPosition.y, - 50) + 200) - ai.addAutonomousUnitGroup(entity.unit_group, natives) + local squad = unitGroupUtils.convertUnitGroupToSquad(natives, entity.unit_group) - --ai.purgeUnitGroups(natives) - ai.retreatUnitGroup(entityPosition, entity.unit_group, regionMap, surface, natives) + ai.retreatUnits(entityPosition, squad, regionMap, surface, natives) end end @@ -111,12 +119,19 @@ function onInitialTick(event) if (surface == nil) then surface = game.surfaces[1] end + + game.forces.player.research_all_technologies() + game.players[1].cheat_mode = true + + -- turn off base expansion + game.forces.enemy.ai_controllable = false + -- add processing handler into generated chunk event loop - -- chunkProcessor.install(chunkUtils.checkChunkPassability) - -- chunkProcessor.install(chunkUtils.scoreChunk) + chunkProcessor.install(chunkUtils.checkChunkPassability) + chunkProcessor.install(chunkUtils.scoreChunk) -- add processing handler into chunk map processing - -- mapProcessor.install(pheromoneUtils.enemyBaseScent) + mapProcessor.install(pheromoneUtils.enemyBaseScent) -- mapProcessor.install(ai.sendScouts) mapProcessor.install(pheromoneUtils.processPheromone) diff --git a/libs/AI.lua b/libs/AI.lua index 31a1f14..27fc67b 100755 --- a/libs/AI.lua +++ b/libs/AI.lua @@ -1,137 +1,131 @@ local ai = {} -local retreatNeighbors = {1,2,3,4,5,6,7,8} +local retreatNeighbors = {1,2,3,4,5,6,7,8} -- used to minimize garbage generation +local retreatPosition = {x=0, y=0} -- used to minimize garbage generation + local constants = require("Constants") local mapUtils = require("MapUtils") local unitGroupUtils = require("UnitGroupUtils") ---[[ function ai.attackPlayerNearNest(regionMap, surface, natives, players) + local ENEMY_BASE_PHEROMONE = constants.ENEMY_BASE_PHEROMONE + for i=1,#players do local player = players[i] local chunk = mapUtils.getChunkByPosition(regionMap, player.position.x, player.position.y) - if (chunk ~= nil) and (chunk[constants.BASE_PHEROMONE] > 125) and (chunk[constants.DEATH_PHEROMONE] < 800) then + if (chunk ~= nil) and (chunk[ENEMY_BASE_PHEROMONE] > 125) then -- TODO scaled base local enemies = surface.find_enemy_units(player.position, 30) if (#enemies > 0) then local unitGroup local enemyIndex = 1 - while (enemyIndex <= #enemies) and (unitGroup == nil) do - local enemy = enemies[enemyIndex] - if (enemy.unit_group ~= nil) then - unitGroup = enemy.unit_group - end - enemyIndex = enemyIndex + 1 - end + -- while (enemyIndex <= #enemies) and (unitGroup == nil) do + -- local enemy = enemies[enemyIndex] + -- if (enemy.unit_group ~= nil) thena + -- unitGroup = enemy.unit_group + -- end + -- enemyIndex = enemyIndex + 1 + -- end if (unitGroup == nil) then unitGroup = surface.create_unit_group({position=enemies[1].position}) - -- natives.squads[#natives.squads+1] = unitGroup + natives.squads[#natives.squads+1] = unitGroup end - -- print("before " .. tostring(unitGroup.state)) + for x=1,#enemies do local enemy = enemies[x] if (enemy.unit_group == nil) then - -- unitGroup.add_member(enemy) - enemy.set_command({type=defines.command.group, - group=unitGroup, - distraction=defines.distraction.none}) + unitGroup.add_member(enemy) + -- enemy.set_command({type=defines.command.group, + -- group=unitGroup, + -- distraction=defines.distraction.none}) end end - -- print("after " .. tostring(unitGroup.state)) + if (unitGroup.state == defines.group_state.gathering) then unitGroup.set_command({type=defines.command.attack, target=player.character}) - unitGroup.start_moving() end + unitGroup.start_moving() end end end end -]]-- -function ai.retreatUnitGroup(position, unitGroup, regionMap, surface, natives) - if (unitGroup ~= nil) then - local memberCount = #unitGroup.members - if (memberCount > 0) then - local chunk = mapUtils.getChunkByPosition(regionMap, position.x, position.y) - if (chunk ~= nil) then - if (chunk[constants.DEATH_PHEROMONE] > 1500) and (memberCount > 5) then - mapUtils.getNeighborChunks(regionMap, - chunk.cX, - chunk.cY, - retreatNeighbors) - local exitPath - local exitScore = constants.MAX_PHEROMONE + 1 - local exitDirection - for i=1, 8 do - local neighborChunk = retreatNeighbors[i] - if (neighborChunk ~= nil) then - if (neighborChunk[constants.DEATH_PHEROMONE] < exitScore) then - exitScore = neighborChunk[constants.DEATH_PHEROMONE] - exitPath = neighborChunk - exitDirection = i - end - end - end - if (exitPath ~= nil) then - local exitPosition = {x=exitPath.pX, - y=exitPath.pY} - local nearGroup = unitGroupUtils.findNearByUnitGroup(natives, exitPosition, 50) - - if (nearGroup == nil) then - nearGroup = surface.create_unit_group({position=exitPosition}) - natives.squads[#natives.squads+1] = nearGroup - end - for i=1, memberCount do - local member = unitGroup.members[i] - if (member ~= nil) and member.valid then - member.set_command({type=defines.command.group, - group=nearGroup, - distraction=defines.distraction.none}) - end - end - -- unitGroup.destroy() +function ai.retreatUnits(position, squad, regionMap, surface, natives) + local DEATH_PHEROMONE = constants.DEATH_PHEROMONE + + local chunk = mapUtils.getChunkByPosition(regionMap, position.x, position.y) + if (chunk ~= nil) and (chunk[DEATH_PHEROMONE] > 1500) then -- TODO sliding scale of death based on evolution + local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE + local ENEMY_BASE_PHEROMONE = constants.ENEMY_BASE_PHEROMONE + + local performRetreat = false + local enemiesToSquad + + if (squad == nil) then + enemiesToSquad = surface.find_enemy_units(position, 20) + if (#enemiesToSquad > 0) then + performRetreat = true + end + elseif (squad ~= nil) and squad.group.valid and (squad.status ~= constants.SQUAD_RETREATING) and (squad.status ~= constants.SQUAD_SUICIDE) then + if (#squad.group.members == 0) then + squad.group.destroy() + else + performRetreat = true + end + end + + if performRetreat then + mapUtils.getNeighborChunks(regionMap, + chunk.cX, + chunk.cY, + retreatNeighbors) + local exitPath + local exitScore = constants.MAGIC_MAXIMUM_NUMBER + local exitDirection + for i=1, 8 do + local neighborChunk = retreatNeighbors[i] + if (neighborChunk ~= nil) then + retreatPosition.x = neighborChunk.pX + retreatPosition.y = neighborChunk.pY + + local dangerScore = neighborChunk[DEATH_PHEROMONE] + surface.get_pollution(retreatPosition) + neighborChunk[PLAYER_PHEROMONE] - neighborChunk[ENEMY_BASE_PHEROMONE] + if (dangerScore < exitScore) then + exitScore = dangerScore + exitPath = neighborChunk + exitDirection = i end end end - elseif (memberCount == 0) then - unitGroup.destroy() - end - end -end - -function ai.addAutonomousUnitGroup(unitGroup, natives) - if (unitGroup ~= nil) then - local squads = natives.squads - local addUnitGroup = true - local i = 1 - while (i <= #squads) and addUnitGroup do - local squad = squads[i] - if (squad == unitGroup) then - addUnitGroup = false + + -- center position in chunk for retreat + retreatPosition.x = exitPath.pX + constants.HALF_CHUNK_SIZE + retreatPosition.y = exitPath.pY + constants.HALF_CHUNK_SIZE + + -- in order for units in a group attacking to retreat, we have to create a new group and give the command to join + -- to each unit + local newSquad = unitGroupUtils.findNearBySquad(natives, + retreatPosition, + 18) + if (newSquad == nil) then + newSquad = unitGroupUtils.createSquad(retreatPosition, surface, natives) end - i = i + 1 - end - if addUnitGroup then - squads[#squads+1] = unitGroup + if (enemiesToSquad ~= nil) then + unitGroupUtils.membersToSquad(newSquad, enemiesToSquad, false) + else + unitGroupUtils.membersToSquad(newSquad, squad.group.members, true) + end + newSquad.status = constants.SQUAD_RETREATING + newSquad.cycles = 5 + -- unitGroupUtils.setSquadCommand(newSquad, + -- {type=defines.command.go_to_location, + -- destination=retreatPosition, + -- distraction=defines.distraction.by_enemy}, + -- constants.SQUAD_RETREATING) end end end --- function ai.purgeUnitGroups(natives) - -- for i=1, #natives.squads do - -- local squad = natives.squads[i] - -- if (squad == nil) then - -- table.remove(natives.squads, i) - -- elseif (not squad.valid) then - -- table.remove(natives.squads, i) - -- elseif (#squad.members == 0) then - -- squad.destroy() - -- table.remove(natives.squads, i) - -- end - -- end --- end - -- function ai.sendScouts(regionMap, surface, natives, chunk, neighbors, validNeighbors) -- return validNeighbors diff --git a/libs/Constants.lua b/libs/Constants.lua index 1702f01..7586b09 100755 --- a/libs/Constants.lua +++ b/libs/Constants.lua @@ -1,13 +1,27 @@ local constants = {} +constants.MAGIC_MAXIMUM_NUMBER = 1e99 -- used in loops trying to find the lowest score + constants.MAX_PHEROMONE = 7000 -constants.DIFFUSION_AMOUNT = 0.04 +constants.BASE_PHEROMONE_PRODUCTION = 35 +constants.DIFFUSION_AMOUNT = 0.02 +constants.DEATH_DIFFUSION_AMOUNT = 0.005 constants.CHUNK_SIZE = 32 +constants.HALF_CHUNK_SIZE = constants.CHUNK_SIZE / 2 constants.NORTH_SOUTH = true constants.EAST_WEST = false constants.DEATH_PHEROMONE = 1 -constants.BASE_PHEROMONE = 2 +constants.ENEMY_BASE_PHEROMONE = 2 constants.PLAYER_PHEROMONE = 3 +constants.SQUAD_RETREATING = 1 +constants.SQUAD_GUARDING = 2 +constants.SQUAD_SIEGE = 3 +constants.SQUAD_HUNTING = 4 +constants.SQUAD_SUICIDE = 5 +constants.SQUAD_ATTACKING = 6 +constants.SQUAD_BURROWING = 7 +constants.SQUAD_SCOUTING = 8 + return constants \ No newline at end of file diff --git a/libs/PheromoneUtils.lua b/libs/PheromoneUtils.lua index 20c1dc9..a688ed0 100755 --- a/libs/PheromoneUtils.lua +++ b/libs/PheromoneUtils.lua @@ -6,28 +6,25 @@ local mMin = math.min local mFloor = math.floor function pheromoneUtils.deathScent(regionMap, x, y, amount) - pheromoneUtils.placePheromoneByPosition(regionMap, - x, - y, - constants.DEATH_PHEROMONE, - 200) + local mathFloor = mFloor + local DEATH_PHEROMONE = constants.DEATH_PHEROMONE + + local chunk = regionMap[mathFloor(x * 0.03125)] + if (chunk ~= nil) then + chunk = chunk[mathFloor(y * 0.03125)] + if (chunk ~= nil) then + chunk[DEATH_PHEROMONE] = chunk[DEATH_PHEROMONE] + amount + end + end end function pheromoneUtils.enemyBaseScent(regionMap, surface, natives, chunk, neighbors, validNeighbors) - local BASE_PHEROMONE = constants.BASE_PHEROMONE - local x = chunk.cX - local y = chunk.cY - - local nativeBase = natives.bases[x] - if (nativeBase ~= nil) then - nativeBase = nativeBase[y] - if (nativeBase ~= nil) then - local spawners = nativeBase.bG - if (spawners > 0) then - local nativeChunk = regionMap[x][y] - nativeChunk[BASE_PHEROMONE] = nativeChunk[BASE_PHEROMONE] + (spawners * 35) - end - end + local BASE_PHEROMONE_PRODUCTION = constants.BASE_PHEROMONE_PRODUCTION + local ENEMY_BASE_PHEROMONE = constants.ENEMY_BASE_PHEROMONE + + local spawners = chunk.bG + if (spawners > 0) then + chunk[ENEMY_BASE_PHEROMONE] = chunk[ENEMY_BASE_PHEROMONE] + (spawners * constants.BASE_PHEROMONE_PRODUCTION) end return validNeighbors end @@ -44,26 +41,25 @@ function pheromoneUtils.playerScent(regionMap, players) end end -function pheromoneUtils.placePheromoneByPosition(regionMap, x, y, pType, amount) - local chunk = mapUtils.getChunkByPosition(regionMap, x, y) - if (chunk~=nil) then - chunk[pType] = mMin(constants.MAX_PHEROMONE, chunk[pType] + amount) - end -end - -function pheromoneUtils.placePheromoneByChunk(regionMap, x, y, pType, amount) - local chunk = regionMap[x][y] - chunk[pType] = mMin(constants.MAX_PHEROMONE, chunk[pType] + amount) -end - function pheromoneUtils.processPheromone(regionMap, surface, natives, chunk, neighbors, validNeighbors) local mathMin = mMin local getNeighborChunks = mapUtils.getNeighborChunks local DIFFUSION_AMOUNT = constants.DIFFUSION_AMOUNT + local DEATH_DIFFUSION_AMOUNT = constants.DEATH_DIFFUSION_AMOUNT local MAX_PHEROMONE = constants.MAX_PHEROMONE + local DEATH_PHEROMONE = constants.DEATH_PHEROMONE for x=1,3 do - if ((x ~= DEATH_PHEROMONE) and (chunk[x] > 75)) or ((x == DEATH_PHEROMONE) and (chunk[x] > 125)) then + local threshold + local diffusionAmount + if (x == DEATH_PHEROMONE) then + threshold = 125 + diffusionAmount = DEATH_DIFFUSION_AMOUNT + else + threshold = 75 + diffusionAmount = DIFFUSION_AMOUNT + end + if (chunk[x] > threshold) then if not validNeighbors then getNeighborChunks(regionMap, chunk.cX, chunk.cY, neighbors) validNeighbors = true @@ -72,7 +68,7 @@ function pheromoneUtils.processPheromone(regionMap, surface, natives, chunk, nei for i=1,8 do local neighborChunk = neighbors[i] if (neighborChunk ~= nil) then - local diffusedAmount = (chunk[x] * DIFFUSION_AMOUNT) + local diffusedAmount = (chunk[x] * diffusionAmount) totalDiffused = totalDiffused + diffusedAmount neighborChunk[x] = mathMin(MAX_PHEROMONE, neighborChunk[x] + diffusedAmount) end diff --git a/libs/UnitGroupUtils.lua b/libs/UnitGroupUtils.lua index 0e10bff..2f42694 100755 --- a/libs/UnitGroupUtils.lua +++ b/libs/UnitGroupUtils.lua @@ -1,14 +1,17 @@ local unitGroupUtils = {} local utils = require("Utils") +local constants = require("Constants") -function unitGroupUtils.findNearByUnitGroup(natives, position, distance) +function unitGroupUtils.findNearBySquad(natives, position, distance) + local getDistance = utils.euclideanDistanceNamed local squads = natives.squads local i = 1 while (i <= #squads) do local squad = squads[i] - if (squad ~= nil) and squad.valid then - if (utils.euclideanDistanceNamed(squad.position, position) <= distance) then + local unitGroup = squad.group + if (unitGroup ~= nil) and unitGroup.valid then + if (getDistance(unitGroup.position, position) <= distance) then return squad end end @@ -16,8 +19,84 @@ function unitGroupUtils.findNearByUnitGroup(natives, position, distance) end end -function unitGroupUtils.mergeNearByUnitGroup(natives, unitGroup) - +function unitGroupUtils.createSquad(position, surface, natives) + local unitGroup = surface.create_unit_group({position=position}) + + local squad = { group = unitGroup, + status = constants.SQUAD_GUARDING, + cycles = -1 } + natives.squads[#natives.squads+1] = squad + return squad +end + +function unitGroupUtils.membersToSquad(squad, members, overwriteGroup) + if members ~= nil then + local group = squad.group + for i=1,#members do + local member = members[i] + if (member ~= nil) and member.valid and (overwriteGroup or (not overwriteGroup and (member.unit_group == nil))) then + member.set_command({type=defines.command.group, + group=group, + distraction=defines.distraction.none}) + end + end + end +end + +function unitGroupUtils.convertUnitGroupToSquad(natives, unitGroup) + local returnSquad + if (unitGroup ~= nil) then + local squads = natives.squads + local addUnitGroup = true + local i = 1 + while (i <= #squads) and addUnitGroup do + local squad = squads[i] + if (squad.group == unitGroup) then + addUnitGroup = false + returnSquad = squad + end + i = i + 1 + end + if addUnitGroup then + returnSquad = { group = unitGroup, + status = constants.SQUAD_GUARDING, + cycles = -1 } + squads[#squads+1] = returnSquad + end + end + + return returnSquad +end + +function unitGroupUtils.setSquadCommand(squad, command, state, cycles) + local group = squad.group + if (group ~= nil) and group.valid then + squad.status = state + squad.cycles = cycles + group.set_command(command) + -- group.start_moving() + end +end + +function unitGroupUtils.regroupSquads(natives) + local SQUAD_RETREATING = constants.SQUAD_RETREATING + local SQUAD_GUARDING = constants.SQUAD_GUARDING + + for i=#natives.squads,1,-1 do + local squad = natives.squads[i] + if (squad.group == nil) then + table.remove(natives.squads, i) + elseif not squad.group.valid then + table.remove(natives.squads, i) + elseif (#squad.group.members == 0) then + squad.group.destroy() + table.remove(natives.squads, i) + elseif (squad.status == SQUAD_RETREATING) and (squad.cycles == 0) then + squad.status = SQUAD_GUARDING + elseif (squad.cycles > 0) then + squad.cycles = squad.cycles - 1 + end + end end return unitGroupUtils \ No newline at end of file diff --git a/libs/chunkUtils.lua b/libs/chunkUtils.lua index 3c24472..6653b96 100755 --- a/libs/chunkUtils.lua +++ b/libs/chunkUtils.lua @@ -69,6 +69,8 @@ function chunkUtils.checkChunkPassability(chunk, surface, natives) chunk.nS = passableNorthSouth end +local spawnCount = 0 + function chunkUtils.scoreChunk(chunk, surface, natives) local x = chunk.pX local y = chunk.pY @@ -80,14 +82,7 @@ function chunkUtils.scoreChunk(chunk, surface, natives) {x+CHUNK_SIZE, y+CHUNK_SIZE}}, type="unit-spawner", force="enemy"}) - if (natives.bases[cX] == nil) then - natives.bases[cX] = {} - end - local nativeX = natives.bases[cX] - if (nativeX[cY] == nil) then - nativeX[cY] = {} - end - nativeX[cY].bG = spawners + chunk.bG = spawners end function chunkUtils.createChunk(topX, topY) @@ -97,7 +92,7 @@ function chunkUtils.createChunk(topX, topY) cX = topX * 0.03125, cY = topY * 0.03125 } - chunk[constants.BASE_PHEROMONE] = 0 + chunk[constants.ENEMY_BASE_PHEROMONE] = 0 chunk[constants.PLAYER_PHEROMONE] = 0 chunk[constants.DEATH_PHEROMONE] = 0 return chunk diff --git a/tests.lua b/tests.lua index aa1d00f..516c1fa 100755 --- a/tests.lua +++ b/tests.lua @@ -25,7 +25,26 @@ function tests.test1() end end -function test2() +function tests.test2() + local position = game.players[1].position + + local spawners = game.surfaces[1].find_entities_filtered({type="unit-spawner"}) + for i=1, #spawners do + local spawner = spawners[i] + if (spawner ~= nil) and spawner.valid then + spawner.destroy() + end + end + + game.forces.enemy.kill_all_units() + + position.x = position.x + 10 + position.y = position.y - 40 + + for i=position.x, position.x+30, 5 do + game.surfaces[1].create_entity({name="biter-spawner", + position={i, position.y}}) + end -- local playerPosition = game.players[1].position -- playerPosition.x = playerPosition.x + 10 -- local turret = game.surfaces[1].create_entity({name="small-worm-turret", position=playerPosition})