513 lines
17 KiB
Lua
513 lines
17 KiB
Lua
if (squadAttackG) then
|
|
return squadAttackG
|
|
end
|
|
local squadAttack = {}
|
|
|
|
-- imports
|
|
|
|
local constants = require("Constants")
|
|
local mapUtils = require("MapUtils")
|
|
local movementUtils = require("MovementUtils")
|
|
local mathUtils = require("MathUtils")
|
|
local chunkPropertyUtils = require("ChunkPropertyUtils")
|
|
local scoreChunks = require("ScoreChunks")
|
|
local squadCompression = require("SquadCompression")
|
|
|
|
-- constants
|
|
|
|
local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE
|
|
local BASE_PHEROMONE = constants.BASE_PHEROMONE
|
|
local BASE_DETECTION_PHEROMONE = constants.BASE_DETECTION_PHEROMONE
|
|
local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE
|
|
local PLAYER_PHEROMONE_GENERATOR_AMOUNT = constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT
|
|
|
|
local TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT = constants.TEN_DEATH_PHEROMONE_GENERATOR_AMOUNT
|
|
local FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT = constants.FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT
|
|
local SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT = constants.SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT
|
|
|
|
|
|
local SQUAD_BUILDING = constants.SQUAD_BUILDING
|
|
|
|
local SQUAD_RAIDING = constants.SQUAD_RAIDING
|
|
local SQUAD_SETTLING = constants.SQUAD_SETTLING
|
|
local SQUAD_GUARDING = constants.SQUAD_GUARDING
|
|
local SQUAD_RETREATING = constants.SQUAD_RETREATING
|
|
|
|
local AI_STATE_SIEGE = constants.AI_STATE_SIEGE
|
|
|
|
local PLAYER_PHEROMONE_MULTIPLER = constants.PLAYER_PHEROMONE_MULTIPLER
|
|
|
|
local DEFINES_DISTRACTION_NONE = defines.distraction.none
|
|
local DEFINES_DISTRACTION_BY_ENEMY = defines.distraction.by_enemy
|
|
local DEFINES_DISTRACTION_BY_ANYTHING = defines.distraction.by_anything
|
|
local DEFINES_DISTRACTION_BY_DAMAGE = defines.distraction.by_damage -- + !КДА
|
|
|
|
-- imported functions
|
|
|
|
local euclideanDistancePoints = mathUtils.euclideanDistancePoints
|
|
|
|
local findMovementPosition = movementUtils.findMovementPosition
|
|
|
|
local removeSquadFromChunk = chunkPropertyUtils.removeSquadFromChunk
|
|
local addDeathGenerator = chunkPropertyUtils.addDeathGenerator
|
|
local getDeathGenerator = chunkPropertyUtils.getDeathGenerator
|
|
|
|
local getNestCount = chunkPropertyUtils.getNestCount
|
|
|
|
local getNeighborChunks = mapUtils.getNeighborChunks
|
|
local addSquadToChunk = chunkPropertyUtils.addSquadToChunk
|
|
local getChunkByXY = mapUtils.getChunkByXY
|
|
local positionToChunkXY = mapUtils.positionToChunkXY
|
|
local addMovementPenalty = movementUtils.addMovementPenalty
|
|
local positionFromDirectionAndFlat = mapUtils.positionFromDirectionAndFlat
|
|
|
|
local euclideanDistanceNamed = mathUtils.euclideanDistanceNamed
|
|
|
|
|
|
local getPlayerBaseGenerator = chunkPropertyUtils.getPlayerBaseGenerator
|
|
local getResourceGenerator = chunkPropertyUtils.getResourceGenerator
|
|
|
|
local scoreNeighborsForAttack = movementUtils.scoreNeighborsForAttack
|
|
local scoreNeighborsForSettling = movementUtils.scoreNeighborsForSettling
|
|
|
|
local scoreResourceLocationKamikaze = scoreChunks.scoreResourceLocationKamikaze
|
|
local scoreSiegeLocation = scoreChunks.scoreSiegeLocation
|
|
local scoreSiegeLocationKamikaze = scoreChunks.scoreSiegeLocationKamikaze
|
|
local scoreResourceLocation = scoreChunks.scoreResourceLocation
|
|
local scoreAttackLocation = scoreChunks.scoreAttackLocation
|
|
local scoreAttackKamikazeLocation = scoreChunks.scoreAttackKamikazeLocation
|
|
|
|
local validSiegeLocation = scoreChunks.validSiegeLocation
|
|
local validSettlerLocation = scoreChunks.validSettlerLocation
|
|
|
|
local processCompression = squadCompression.processCompression
|
|
local clearComresseData = squadCompression.clearComresseData
|
|
|
|
local mCeil = math.ceil
|
|
|
|
------------------squadCompression---------------------------------------
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
local function settleHere(map, group, squad, dontDistract)
|
|
if not squad then
|
|
return
|
|
end
|
|
local universe = map.universe
|
|
local targetPosition = universe.position
|
|
local surface = map.surface
|
|
local groupPosition = group.position
|
|
position = findMovementPosition(surface, groupPosition)
|
|
|
|
if not position then
|
|
position = groupPosition
|
|
end
|
|
|
|
targetPosition.x = position.x
|
|
targetPosition.y = position.y
|
|
|
|
cmd = universe.settleCommand
|
|
if squad.kamikaze or dontDistract then
|
|
cmd.distraction = DEFINES_DISTRACTION_NONE
|
|
else
|
|
cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY
|
|
end
|
|
|
|
squad.status = SQUAD_BUILDING
|
|
|
|
group.set_command(cmd)
|
|
squad.checkTick = game.tick + 36000 -- some groups get stuck
|
|
|
|
end
|
|
|
|
local function goToLastvalidSettlerLocation(universe, group, squad)
|
|
cmd = universe.moveCommand
|
|
cmd.destination.x = squad.lastValidChunk.x
|
|
cmd.destination.y = squad.lastValidChunk.y
|
|
group.set_command(cmd)
|
|
|
|
squad.settleFirstChance = true
|
|
--game.print("settle ("..groupPosition.x..","..groupPosition.y..") - maxDistance, last valid chunk ("..squad.lastValidChunk.x..","..squad.lastValidChunk.y..")")
|
|
squad.lastValidChunk = nil
|
|
squad.checkTick = game.tick + 18000
|
|
end
|
|
|
|
local function settleMove(map, squad)
|
|
if not squad then
|
|
return
|
|
end
|
|
|
|
local universe = map.universe
|
|
local targetPosition = universe.position
|
|
local targetPosition2 = universe.position2
|
|
local group = squad.group
|
|
|
|
local groupPosition = group.position
|
|
local x, y = positionToChunkXY(groupPosition)
|
|
local chunk = getChunkByXY(map, x, y)
|
|
local scoreFunction = scoreResourceLocation
|
|
local validFunction = validSettlerLocation
|
|
if squad.siege then
|
|
validFunction = validSiegeLocation
|
|
if squad.kamikaze then
|
|
scoreFunction = scoreSiegeLocationKamikaze
|
|
else
|
|
scoreFunction = scoreSiegeLocation
|
|
end
|
|
elseif squad.kamikaze then
|
|
scoreFunction = scoreResourceLocationKamikaze
|
|
end
|
|
|
|
if chunk==-1 then --bitters found unregistered chunk, and stopped on it... yes, this is really possible...
|
|
chunk = squad.prevChunk
|
|
end
|
|
addDeathGenerator(map, chunk, SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT) -- FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT
|
|
addSquadToChunk(map, chunk, squad)
|
|
if squad.prevChunk and (chunk == squad.prevChunk) then
|
|
--game.print("settleMove, chunk = squad.prevChunk ("..chunk.x..","..chunk.y..")")
|
|
else
|
|
if validFunction(map, chunk) then
|
|
squad.lastValidChunk = chunk
|
|
end
|
|
addMovementPenalty(squad, chunk)
|
|
end
|
|
local distance = euclideanDistancePoints(groupPosition.x,
|
|
groupPosition.y,
|
|
squad.originPosition.x,
|
|
squad.originPosition.y)
|
|
local cmd
|
|
local position
|
|
local surface = map.surface
|
|
|
|
if squad.settleFirstChance
|
|
and squad.lastValidChunk
|
|
and ((squad.lastValidChunk.x == chunk.x) and (squad.lastValidChunk.y == chunk.y))
|
|
then
|
|
settleHere(map, group, squad, true)
|
|
elseif distance >= squad.maxDistance then
|
|
if getNestCount(map, chunk) == 0 then
|
|
settleHere(map, group, squad)
|
|
elseif squad.lastValidChunk then
|
|
goToLastvalidSettlerLocation(universe, group, squad)
|
|
else
|
|
squad.settleFirstChance = true --------------------------------------------------------------
|
|
squad.maxDistance = squad.maxDistance + 1
|
|
end
|
|
elseif (getResourceGenerator(map, chunk) ~= 0) and (getNestCount(map, chunk) == 0) then
|
|
settleHere(map, group, squad)
|
|
else
|
|
local attackChunk,
|
|
attackDirection,
|
|
nextAttackChunk,
|
|
nextAttackDirection = scoreNeighborsForSettling(map,
|
|
chunk,
|
|
getNeighborChunks(map, x, y),
|
|
scoreFunction)
|
|
local prevChunk
|
|
if not (squad.prevChunk and (squad.prevChunk ~= -1)) then
|
|
prevChunk = {x = 1, y = 1} -- we never find chunk(x=1, y=1) becouse coordinates are multiples of 32
|
|
else
|
|
prevChunk = squad.prevChunk
|
|
end
|
|
if (attackDirection ~= 0) and (prevChunk.x == attackChunk.x) and (prevChunk.y == attackChunk.y) then
|
|
if (getNestCount(map, chunk) == 0) then
|
|
settleHere(map, group, squad, true)
|
|
elseif squad.lastValidChunk then
|
|
goToLastvalidSettlerLocation(universe, group, squad)
|
|
else
|
|
settleHere(map, group, squad, true)
|
|
end
|
|
else
|
|
if (attackChunk == -1) then
|
|
settleHere(map, group, squad, true)
|
|
elseif (attackDirection ~= 0) then
|
|
local attackPlayerThreshold = universe.attackPlayerThreshold
|
|
|
|
if (nextAttackChunk ~= -1) then
|
|
attackChunk = nextAttackChunk
|
|
positionFromDirectionAndFlat(attackDirection, groupPosition, targetPosition)
|
|
positionFromDirectionAndFlat(nextAttackDirection, targetPosition, targetPosition2)
|
|
position = findMovementPosition(surface, targetPosition2)
|
|
else
|
|
positionFromDirectionAndFlat(attackDirection, groupPosition, targetPosition)
|
|
position = findMovementPosition(surface, targetPosition)
|
|
end
|
|
|
|
if position then
|
|
targetPosition.x = position.x
|
|
targetPosition.y = position.y
|
|
if nextAttackChunk then
|
|
addDeathGenerator(map, nextAttackChunk, SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT) -- FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT
|
|
else
|
|
addDeathGenerator(map, attackChunk, SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT) -- FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT
|
|
end
|
|
end
|
|
|
|
if (getPlayerBaseGenerator(map, attackChunk) ~= 0) or
|
|
(attackChunk[PLAYER_PHEROMONE] >= attackPlayerThreshold)
|
|
then
|
|
cmd = universe.attackCommand
|
|
|
|
if not squad.rabid then
|
|
squad.frenzy = true
|
|
squad.frenzyPosition.x = groupPosition.x
|
|
squad.frenzyPosition.y = groupPosition.y
|
|
end
|
|
else
|
|
cmd = universe.moveCommand
|
|
if squad.rabid or squad.kamikaze then
|
|
cmd.distraction = DEFINES_DISTRACTION_NONE
|
|
else
|
|
cmd.distraction = DEFINES_DISTRACTION_BY_DAMAGE -- DEFINES_DISTRACTION_BY_ENEMY
|
|
end
|
|
end
|
|
group.set_command(cmd)
|
|
squad.checkTick = game.tick + 18000
|
|
else
|
|
settleHere(map, group, squad, true)
|
|
end
|
|
end
|
|
squad.prevChunk = chunk -- prevent 1-2-1-2-1-2-1.. moving
|
|
end
|
|
end
|
|
|
|
local function attackMove(map, squad)
|
|
if not squad then
|
|
return
|
|
end
|
|
|
|
local universe = map.universe
|
|
local targetPosition = universe.position
|
|
local targetPosition2 = universe.position2
|
|
|
|
local group = squad.group
|
|
|
|
local surface = map.surface
|
|
local position
|
|
local groupPosition = group.position
|
|
local x, y = positionToChunkXY(groupPosition)
|
|
local chunk = getChunkByXY(map, x, y)
|
|
local attackScorer = scoreAttackLocation
|
|
if squad.kamikaze then
|
|
attackScorer = scoreAttackKamikazeLocation
|
|
end
|
|
|
|
if (chunk ~= -1) and (chunk[PLAYER_PHEROMONE] == 0) and (chunk[BASE_DETECTION_PHEROMONE] == 0) then
|
|
if not squad.checkTick or ((game.tick + 600) < squad.checkTick) then
|
|
squad.checkTick = game.tick + 600
|
|
end
|
|
return
|
|
end
|
|
|
|
|
|
processCompression(map, squad, chunk, true)
|
|
|
|
if squad.chunk and (squad.chunk ~= -1) then
|
|
addDeathGenerator(map, squad.chunk, SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT) --FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT)
|
|
end
|
|
|
|
addSquadToChunk(map, chunk, squad)
|
|
addMovementPenalty(squad, chunk)
|
|
squad.frenzy = (squad.frenzy and (euclideanDistanceNamed(groupPosition, squad.frenzyPosition) < 100))
|
|
local attackChunk, attackDirection,
|
|
nextAttackChunk, nextAttackDirection = scoreNeighborsForAttack(map,
|
|
chunk,
|
|
getNeighborChunks(map, x, y),
|
|
attackScorer)
|
|
local cmd
|
|
if (attackChunk == -1) then
|
|
cmd = universe.wonderCommand
|
|
group.set_command(cmd)
|
|
return
|
|
elseif (nextAttackChunk ~= -1) then
|
|
attackChunk = nextAttackChunk
|
|
positionFromDirectionAndFlat(attackDirection, groupPosition, targetPosition)
|
|
positionFromDirectionAndFlat(nextAttackDirection, targetPosition, targetPosition2)
|
|
position = findMovementPosition(surface, targetPosition2)
|
|
else
|
|
positionFromDirectionAndFlat(attackDirection, groupPosition, targetPosition)
|
|
position = findMovementPosition(surface, targetPosition)
|
|
end
|
|
|
|
if not position then
|
|
cmd = universe.wonderCommand
|
|
group.set_command(cmd)
|
|
return
|
|
else
|
|
targetPosition.x = position.x
|
|
targetPosition.y = position.y
|
|
if nextAttackChunk then
|
|
addDeathGenerator(map, nextAttackChunk, SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT) --FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT)
|
|
else
|
|
addDeathGenerator(map, attackChunk, SQUAD_PATH_DEATH_PHEROMONE_GENERATOR_AMOUNT) --FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT)
|
|
end
|
|
end
|
|
|
|
if (getPlayerBaseGenerator(map, attackChunk) ~= 0) and
|
|
(attackChunk[PLAYER_PHEROMONE] >= universe.attackPlayerThreshold)
|
|
then
|
|
cmd = universe.attackCommand
|
|
|
|
if not squad.rabid then
|
|
squad.frenzy = true
|
|
squad.frenzyPosition.x = groupPosition.x
|
|
squad.frenzyPosition.y = groupPosition.y
|
|
end
|
|
else
|
|
cmd = universe.moveCommand
|
|
if squad.rabid or squad.frenzy then
|
|
cmd.distraction = DEFINES_DISTRACTION_BY_ANYTHING
|
|
else
|
|
cmd.distraction = DEFINES_DISTRACTION_BY_ENEMY
|
|
end
|
|
end
|
|
-- DEBUG
|
|
-- if not squad.debugPath then
|
|
-- squad.debugPath = {}
|
|
-- end
|
|
|
|
-- local lineColor = {0, 1, 0}
|
|
-- if squad.frenzy then
|
|
-- lineColor = {1, 0.5, 0}
|
|
-- elseif squad.rabid then
|
|
-- lineColor = {1, 1, 0}
|
|
-- elseif squad.kamikaze then
|
|
-- lineColor = {1, 0, 0}
|
|
-- end
|
|
-- squad.debugPath[#squad.debugPath+1] = rendering.draw_line{surface = group.surface, from = group.position, to = targetPosition, color = lineColor, width = 2}
|
|
-- squad.debugPath[#squad.debugPath+1] = rendering.draw_text{text = tostring((#squad.debugPath+1)*0.5), surface = group.surface, target = targetPosition, color = lineColor, scale = 3}
|
|
-- DEBUG
|
|
|
|
squad.checkTick = game.tick + 18000 -- some groups get stuck
|
|
group.set_command(cmd)
|
|
end
|
|
|
|
local function buildMove(map, squad)
|
|
if not squad then
|
|
return
|
|
end
|
|
local group = squad.group
|
|
local universe = map.universe
|
|
local position = universe.position
|
|
local groupPosition = findMovementPosition(map.surface, group.position)
|
|
|
|
if not groupPosition then
|
|
groupPosition = group.position
|
|
end
|
|
|
|
position.x = groupPosition.x
|
|
position.y = groupPosition.y
|
|
|
|
group.set_command(universe.compoundSettleCommand)
|
|
squad.checkTick = game.tick + 18000 -- some groups get stuck
|
|
|
|
end
|
|
|
|
-- DEBUG
|
|
local function clearDebugLines(squad)
|
|
if not squad.debugPath then
|
|
return
|
|
end
|
|
local debugLen = #squad.debugPath + 0
|
|
for i=1,debugLen do
|
|
rendering.destroy(squad.debugPath[i])
|
|
end
|
|
squad.debugPath = {}
|
|
end
|
|
-- DEBUG
|
|
|
|
local function disbandSquad(universe, squads, groupId)
|
|
local squad = squads[groupId]
|
|
if squad.settlers then
|
|
universe.builderCount = universe.builderCount - 1
|
|
else
|
|
universe.squadCount = universe.squadCount - 1
|
|
end
|
|
if squad.map then
|
|
removeSquadFromChunk(squad.map, squad)
|
|
local group = squad.group
|
|
if group.valid then
|
|
group.destroy()
|
|
end
|
|
clearDebugLines(squad) -- DEBUG
|
|
clearComresseData(universe, squad)
|
|
end
|
|
squads[groupId] = nil
|
|
end
|
|
|
|
function squadAttack.cleanSquads(universe)
|
|
local squads = universe.groupNumberToSquad
|
|
local groupId = universe.squadIterator
|
|
local squad
|
|
if not groupId then
|
|
groupId, squad = next(squads, groupId)
|
|
else
|
|
squad = squads[groupId]
|
|
end
|
|
if not groupId then
|
|
universe.squadIterator = nil
|
|
if (table_size(squads) == 0) then
|
|
-- this is needed as the next command remembers the max length a table has been
|
|
universe.groupNumberToSquad = {}
|
|
end
|
|
else
|
|
local map = squad.map
|
|
local group = squad.group
|
|
if not map then
|
|
disbandSquad(universe, squads, groupId)
|
|
elseif not group.valid then
|
|
if squad.chunk and (squad.chunk ~= -1) then
|
|
addDeathGenerator(map, squad.chunk, FIVE_DEATH_PHEROMONE_GENERATOR_AMOUNT)
|
|
end
|
|
disbandSquad(universe, squads, groupId)
|
|
elseif squad.disbandTick < game.tick then
|
|
disbandSquad(universe, squads, groupId)
|
|
elseif (group.state == 4) then
|
|
squadAttack.squadDispatch(map, squad)
|
|
elseif not squad.checkTick then
|
|
squad.checkTick = game.tick + 18000
|
|
elseif squad.checkTick < game.tick then -- some groups get stuck. stuck + ignoring new commands!
|
|
disbandSquad(universe, squads, groupId)
|
|
end
|
|
universe.squadIterator = next(squads, groupId)
|
|
end
|
|
end
|
|
|
|
function squadAttack.squadDispatch(map, squad)
|
|
if not squad then
|
|
return
|
|
end
|
|
local group = squad.group
|
|
if group and group.valid then
|
|
local status = squad.status
|
|
if (status == SQUAD_RAIDING) then
|
|
attackMove(map, squad)
|
|
elseif (status == SQUAD_SETTLING) then
|
|
settleMove(map, squad)
|
|
elseif (status == SQUAD_RETREATING) then
|
|
squad.compressed = true -- some compressed units can join while retreating
|
|
if squad.settlers then
|
|
squad.status = SQUAD_SETTLING
|
|
settleMove(map, squad)
|
|
else
|
|
squad.status = SQUAD_RAIDING
|
|
attackMove(map, squad)
|
|
end
|
|
elseif (status == SQUAD_BUILDING) then
|
|
removeSquadFromChunk(map, squad)
|
|
buildMove(map, squad)
|
|
elseif (status == SQUAD_GUARDING) then
|
|
if squad.settlers then
|
|
squad.status = SQUAD_SETTLING
|
|
settleMove(map, squad)
|
|
else
|
|
squad.status = SQUAD_RAIDING
|
|
attackMove(map, squad)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
squadAttackG = squadAttack
|
|
return squadAttack
|