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