2016-08-05 06:47:51 +02:00
|
|
|
local unitGroupUtils = {}
|
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
-- imports
|
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
local mapUtils = require("MapUtils")
|
2017-06-16 03:30:26 +02:00
|
|
|
local mathUtils = require("MathUtils")
|
2016-08-07 05:38:47 +02:00
|
|
|
local constants = require("Constants")
|
2018-01-15 09:41:55 +02:00
|
|
|
local chunkPropertyUtils = require("ChunkPropertyUtils")
|
2016-08-05 06:47:51 +02:00
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
-- constants
|
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
local HALF_CHUNK_SIZE = constants.HALF_CHUNK_SIZE
|
|
|
|
|
2017-06-01 04:48:59 +02:00
|
|
|
local SQUAD_QUEUE_SIZE = constants.SQUAD_QUEUE_SIZE
|
|
|
|
|
2017-05-27 02:58:33 +02:00
|
|
|
local DEFINES_GROUP_STATE_FINISHED = defines.group_state.finished
|
|
|
|
local DEFINES_GROUP_STATE_ATTACKING_TARGET = defines.group_state.attacking_target
|
|
|
|
local DEFINES_GROUP_STATE_ATTACKING_DISTRACTION = defines.group_state.attacking_distraction
|
2016-08-18 07:55:08 +02:00
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
local SQUAD_RETREATING = constants.SQUAD_RETREATING
|
|
|
|
local SQUAD_GUARDING = constants.SQUAD_GUARDING
|
2018-02-13 09:10:17 +02:00
|
|
|
local SQUAD_SETTLING = constants.SQUAD_SETTLING
|
2017-01-20 07:58:36 +02:00
|
|
|
local GROUP_MERGE_DISTANCE = constants.GROUP_MERGE_DISTANCE
|
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
local RETREAT_FILTER = constants.RETREAT_FILTER
|
|
|
|
|
2017-01-20 07:58:36 +02:00
|
|
|
local NO_RETREAT_SQUAD_SIZE_BONUS_MAX = constants.NO_RETREAT_SQUAD_SIZE_BONUS_MAX
|
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local AI_MAX_OVERFLOW_POINTS = constants.AI_MAX_OVERFLOW_POINTS
|
2017-05-24 08:46:23 +02:00
|
|
|
|
2017-04-16 08:04:22 +02:00
|
|
|
local AI_MAX_BITER_GROUP_SIZE = constants.AI_MAX_BITER_GROUP_SIZE
|
2017-06-01 09:03:07 +02:00
|
|
|
local AI_SQUAD_MERGE_THRESHOLD = constants.AI_SQUAD_MERGE_THRESHOLD
|
2017-04-16 08:04:22 +02:00
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
-- imported functions
|
|
|
|
|
2017-07-01 06:36:23 +02:00
|
|
|
local mRandom = math.random
|
|
|
|
|
2017-01-20 07:58:36 +02:00
|
|
|
local mLog = math.log10
|
|
|
|
|
2017-06-01 04:48:59 +02:00
|
|
|
local mMin = math.min
|
|
|
|
|
2018-01-15 09:41:55 +02:00
|
|
|
local getSquadsOnChunk = chunkPropertyUtils.getSquadsOnChunk
|
|
|
|
local removeSquadFromChunk = chunkPropertyUtils.removeSquadFromChunk
|
2018-01-14 07:48:21 +02:00
|
|
|
|
|
|
|
local getNeighborChunks = mapUtils.getNeighborChunks
|
|
|
|
|
2017-06-16 03:30:26 +02:00
|
|
|
local euclideanDistanceNamed = mathUtils.euclideanDistanceNamed
|
2016-08-20 04:52:27 +02:00
|
|
|
|
|
|
|
-- module code
|
2017-01-20 07:58:36 +02:00
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
function unitGroupUtils.findNearbySquadFiltered(map, chunk, position)
|
2018-02-01 06:01:28 +02:00
|
|
|
for _,squad in pairs(getSquadsOnChunk(map, chunk)) do
|
2018-01-14 07:48:21 +02:00
|
|
|
local unitGroup = squad.group
|
2018-02-07 09:57:41 +02:00
|
|
|
if unitGroup and unitGroup.valid and RETREAT_FILTER[squad.status] then
|
2018-01-14 07:48:21 +02:00
|
|
|
if (euclideanDistanceNamed(unitGroup.position, position) <= HALF_CHUNK_SIZE) then
|
|
|
|
return squad
|
2017-08-08 10:19:51 +02:00
|
|
|
end
|
|
|
|
end
|
2018-01-14 07:48:21 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local neighbors = getNeighborChunks(map, chunk.x, chunk.y)
|
|
|
|
|
|
|
|
for i=1,#neighbors do
|
2018-02-01 06:01:28 +02:00
|
|
|
for _,squad in pairs(getSquadsOnChunk(map, neighbors[i])) do
|
2017-08-08 10:19:51 +02:00
|
|
|
local unitGroup = squad.group
|
2018-02-07 09:57:41 +02:00
|
|
|
if unitGroup and unitGroup.valid and RETREAT_FILTER[squad.status] then
|
2018-01-14 07:48:21 +02:00
|
|
|
if (euclideanDistanceNamed(unitGroup.position, position) <= HALF_CHUNK_SIZE) then
|
2017-08-08 10:19:51 +02:00
|
|
|
return squad
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-08-05 06:47:51 +02:00
|
|
|
end
|
2018-02-01 06:01:28 +02:00
|
|
|
return nil
|
2016-08-05 06:47:51 +02:00
|
|
|
end
|
|
|
|
|
2018-02-01 06:01:28 +02:00
|
|
|
function unitGroupUtils.findNearbySquad(map, chunk, position)
|
|
|
|
|
|
|
|
for _,squad in pairs(getSquadsOnChunk(map, chunk)) do
|
|
|
|
local unitGroup = squad.group
|
2018-02-07 09:57:41 +02:00
|
|
|
if unitGroup and unitGroup.valid then
|
2018-02-01 06:01:28 +02:00
|
|
|
if (euclideanDistanceNamed(unitGroup.position, position) <= HALF_CHUNK_SIZE) then
|
|
|
|
return squad
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local neighbors = getNeighborChunks(map, chunk.x, chunk.y)
|
2018-01-14 07:48:21 +02:00
|
|
|
|
2018-02-01 06:01:28 +02:00
|
|
|
for i=1,#neighbors do
|
|
|
|
for _,squad in pairs(getSquadsOnChunk(map, neighbors[i])) do
|
|
|
|
local unitGroup = squad.group
|
2018-02-07 09:57:41 +02:00
|
|
|
if unitGroup and unitGroup.valid then
|
2018-02-01 06:01:28 +02:00
|
|
|
if (euclideanDistanceNamed(unitGroup.position, position) <= HALF_CHUNK_SIZE) then
|
|
|
|
return squad
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
2018-01-14 07:48:21 +02:00
|
|
|
|
2018-01-15 02:14:38 +02:00
|
|
|
function unitGroupUtils.createSquad(position, surface, natives, group)
|
|
|
|
local unitGroup = group or surface.create_unit_group({position=position})
|
2016-08-07 05:38:47 +02:00
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
local squad = {
|
|
|
|
group = unitGroup,
|
|
|
|
status = SQUAD_GUARDING,
|
|
|
|
penalties = {},
|
2018-02-13 09:10:17 +02:00
|
|
|
settlers = false,
|
2018-01-14 07:48:21 +02:00
|
|
|
rabid = false,
|
|
|
|
frenzy = false,
|
|
|
|
kamikaze = false,
|
|
|
|
frenzyPosition = {x = 0,
|
|
|
|
y = 0},
|
|
|
|
cycles = 0,
|
2018-02-14 10:28:42 +02:00
|
|
|
maxDistance = 0,
|
|
|
|
originPosition = {x = 0,
|
|
|
|
y = 0},
|
2018-01-14 07:48:21 +02:00
|
|
|
chunk = nil
|
|
|
|
}
|
2016-08-07 05:38:47 +02:00
|
|
|
natives.squads[#natives.squads+1] = squad
|
|
|
|
return squad
|
|
|
|
end
|
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
function unitGroupUtils.membersToSquad(cmd, members, overwriteGroup)
|
2016-08-08 03:35:36 +02:00
|
|
|
if (members ~= nil) then
|
2016-09-13 01:23:26 +02:00
|
|
|
for i=1,#members do
|
2016-08-07 05:38:47 +02:00
|
|
|
local member = members[i]
|
2017-05-28 06:50:37 +02:00
|
|
|
if member.valid and (overwriteGroup or (not overwriteGroup and not member.unit_group)) then
|
2016-09-14 19:12:29 +02:00
|
|
|
member.set_command(cmd)
|
2016-08-07 05:38:47 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function unitGroupUtils.convertUnitGroupToSquad(natives, unitGroup)
|
2017-06-01 03:46:53 +02:00
|
|
|
if not unitGroup then
|
|
|
|
return nil
|
2016-08-07 05:38:47 +02:00
|
|
|
end
|
2017-06-01 03:46:53 +02:00
|
|
|
local squads = natives.squads
|
|
|
|
for i=1,#squads do
|
|
|
|
local squad = squads[i]
|
|
|
|
if (squad.group == unitGroup) then
|
|
|
|
return squad
|
|
|
|
end
|
|
|
|
end
|
2018-01-15 02:14:38 +02:00
|
|
|
local squad = unitGroupUtils.createSquad(nil,nil,natives,unitGroup)
|
|
|
|
squad.kamikaze = mRandom() < unitGroupUtils.calculateKamikazeThreshold(#unitGroup.members, natives)
|
|
|
|
return squad
|
2016-08-07 05:38:47 +02:00
|
|
|
end
|
|
|
|
|
2018-01-15 02:14:38 +02:00
|
|
|
function unitGroupUtils.calculateKamikazeThreshold(memberCount, natives)
|
|
|
|
local squadSizeBonus = mLog((memberCount / natives.attackWaveMaxSize) + 0.1) + 1
|
2017-06-01 03:46:53 +02:00
|
|
|
return natives.kamikazeThreshold + (NO_RETREAT_SQUAD_SIZE_BONUS_MAX * squadSizeBonus)
|
2017-01-20 07:58:36 +02:00
|
|
|
end
|
|
|
|
|
2017-04-22 01:14:04 +02:00
|
|
|
local function isAttacking(group)
|
|
|
|
local state = group.state
|
2017-05-27 02:58:33 +02:00
|
|
|
return (state == DEFINES_GROUP_STATE_ATTACKING_TARGET) or (state == DEFINES_GROUP_STATE_ATTACKING_DISTRACTION)
|
2017-01-20 07:58:36 +02:00
|
|
|
end
|
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
function unitGroupUtils.cleanSquads(natives, map)
|
2016-08-09 04:18:19 +02:00
|
|
|
local squads = natives.squads
|
2017-04-22 01:14:04 +02:00
|
|
|
local squadCount = #squads
|
|
|
|
|
2017-05-24 08:46:23 +02:00
|
|
|
local cleanSquads = {}
|
2017-06-01 09:03:07 +02:00
|
|
|
for i=1, squadCount do
|
2017-04-22 01:14:04 +02:00
|
|
|
local squad = squads[i]
|
|
|
|
local group = squad.group
|
2018-01-28 03:02:33 +02:00
|
|
|
if group and group.valid then
|
2017-04-22 01:14:04 +02:00
|
|
|
local memberCount = #group.members
|
2017-05-24 08:46:23 +02:00
|
|
|
if (memberCount == 0) then
|
2018-01-14 07:48:21 +02:00
|
|
|
removeSquadFromChunk(map, squad)
|
2017-05-24 08:46:23 +02:00
|
|
|
group.destroy()
|
|
|
|
elseif (memberCount > AI_MAX_BITER_GROUP_SIZE) then
|
|
|
|
local members = group.members
|
2017-06-11 02:59:06 +02:00
|
|
|
unitGroupUtils.recycleBiters(natives, members)
|
2018-01-14 07:48:21 +02:00
|
|
|
removeSquadFromChunk(map, squad)
|
2017-04-22 01:14:04 +02:00
|
|
|
group.destroy()
|
2017-05-24 08:46:23 +02:00
|
|
|
else
|
|
|
|
local status = squad.status
|
|
|
|
local cycles = squad.cycles
|
|
|
|
if (status == SQUAD_RETREATING) and (cycles == 0) then
|
2018-02-13 09:10:17 +02:00
|
|
|
if squad.settlers then
|
|
|
|
squad.status = SQUAD_SETTLING
|
|
|
|
else
|
|
|
|
squad.status = SQUAD_GUARDING
|
|
|
|
end
|
|
|
|
|
2017-05-24 08:46:23 +02:00
|
|
|
squad.frenzy = true
|
2017-06-01 04:48:59 +02:00
|
|
|
local squadPosition = group.position
|
2017-05-24 08:46:23 +02:00
|
|
|
squad.frenzyPosition.x = squadPosition.x
|
|
|
|
squad.frenzyPosition.y = squadPosition.y
|
2017-05-27 02:58:33 +02:00
|
|
|
elseif (group.state == DEFINES_GROUP_STATE_FINISHED) then
|
2018-02-13 09:10:17 +02:00
|
|
|
if squad.settlers then
|
|
|
|
squad.status = SQUAD_SETTLING
|
|
|
|
else
|
|
|
|
squad.status = SQUAD_GUARDING
|
|
|
|
end
|
2017-05-24 08:46:23 +02:00
|
|
|
elseif (cycles > 0) then
|
|
|
|
squad.cycles = cycles - 1
|
|
|
|
end
|
|
|
|
|
|
|
|
cleanSquads[#cleanSquads+1] = squad
|
2017-01-20 07:58:36 +02:00
|
|
|
end
|
2018-02-01 06:01:28 +02:00
|
|
|
else
|
|
|
|
removeSquadFromChunk(map, squad)
|
2017-01-20 07:58:36 +02:00
|
|
|
end
|
2016-08-09 04:18:19 +02:00
|
|
|
end
|
2017-05-24 08:46:23 +02:00
|
|
|
|
|
|
|
natives.squads = cleanSquads
|
|
|
|
end
|
|
|
|
|
2017-06-11 02:59:06 +02:00
|
|
|
function unitGroupUtils.recycleBiters(natives, biters)
|
|
|
|
local unitCount = #biters
|
|
|
|
for i=1,unitCount do
|
|
|
|
biters[i].destroy()
|
|
|
|
end
|
|
|
|
natives.points = natives.points + (unitCount * natives.unitRefundAmount)
|
|
|
|
|
|
|
|
if (natives.points > AI_MAX_OVERFLOW_POINTS) then
|
|
|
|
natives.points = AI_MAX_OVERFLOW_POINTS
|
|
|
|
end
|
|
|
|
end
|
2017-01-20 07:58:36 +02:00
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
local function mergeGroups(squads, squad, group, status, position, memberCount)
|
|
|
|
local merge = false
|
|
|
|
local maxed = false
|
2018-02-10 07:28:41 +02:00
|
|
|
for _,mergeSquad in pairs(squads) do
|
|
|
|
if (mergeSquad ~= squad) then
|
|
|
|
local mergeGroup = mergeSquad.group
|
|
|
|
if mergeGroup and mergeGroup.valid and (euclideanDistanceNamed(position, mergeGroup.position) < GROUP_MERGE_DISTANCE) and (mergeSquad.status == status) and not isAttacking(mergeGroup) then
|
|
|
|
local mergeMembers = mergeGroup.members
|
|
|
|
local mergeCount = #mergeMembers
|
|
|
|
if ((mergeCount + memberCount) < AI_MAX_BITER_GROUP_SIZE) then
|
|
|
|
for memberIndex=1, mergeCount do
|
|
|
|
group.add_member(mergeMembers[memberIndex])
|
|
|
|
end
|
|
|
|
if mergeSquad.kamikaze then
|
|
|
|
squad.kamikaze = true
|
|
|
|
end
|
|
|
|
merge = true
|
|
|
|
mergeGroup.destroy()
|
2018-01-14 07:48:21 +02:00
|
|
|
end
|
2018-02-10 07:28:41 +02:00
|
|
|
memberCount = memberCount + mergeCount
|
|
|
|
if (memberCount > AI_SQUAD_MERGE_THRESHOLD) then
|
|
|
|
maxed = true
|
|
|
|
break
|
2018-01-14 07:48:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return merge, memberCount, maxed
|
|
|
|
end
|
2017-05-24 08:46:23 +02:00
|
|
|
|
2018-01-14 07:48:21 +02:00
|
|
|
function unitGroupUtils.regroupSquads(natives, map)
|
2017-05-24 08:46:23 +02:00
|
|
|
local squads = natives.squads
|
|
|
|
local squadCount = #squads
|
2017-06-01 04:48:59 +02:00
|
|
|
|
|
|
|
local startIndex = natives.regroupIndex
|
2018-01-14 07:48:21 +02:00
|
|
|
|
2017-06-01 04:48:59 +02:00
|
|
|
local maxSquadIndex = mMin(startIndex + SQUAD_QUEUE_SIZE, squadCount)
|
|
|
|
for i=startIndex,maxSquadIndex do
|
2017-01-20 07:58:36 +02:00
|
|
|
local squad = squads[i]
|
2017-04-22 01:14:04 +02:00
|
|
|
local group = squad.group
|
2018-02-07 09:57:41 +02:00
|
|
|
if group and group.valid and not isAttacking(group) then
|
2017-06-01 04:48:59 +02:00
|
|
|
local memberCount = #group.members
|
2018-01-14 07:48:21 +02:00
|
|
|
if (memberCount < AI_SQUAD_MERGE_THRESHOLD) then
|
|
|
|
local status = squad.status
|
2017-05-24 08:46:23 +02:00
|
|
|
local squadPosition = group.position
|
2018-01-14 07:48:21 +02:00
|
|
|
local mergedSquads
|
|
|
|
local maxed
|
|
|
|
local chunk = squad.chunk
|
|
|
|
|
|
|
|
if chunk then
|
|
|
|
mergedSquads, memberCount, maxed = mergeGroups(getSquadsOnChunk(map, chunk),
|
|
|
|
squad,
|
|
|
|
group,
|
|
|
|
status,
|
|
|
|
squadPosition,
|
|
|
|
memberCount)
|
|
|
|
|
|
|
|
if not maxed then
|
|
|
|
local neighbors = getNeighborChunks(map, chunk.x, chunk.y)
|
|
|
|
|
|
|
|
for x=1,#neighbors do
|
|
|
|
mergedSquads, memberCount, maxed = mergeGroups(getSquadsOnChunk(map, neighbors[x]),
|
|
|
|
squad,
|
|
|
|
group,
|
|
|
|
status,
|
|
|
|
squadPosition,
|
|
|
|
memberCount)
|
|
|
|
if maxed then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-04-22 01:14:04 +02:00
|
|
|
if mergedSquads and not squad.kamikaze then
|
2018-01-20 09:15:13 +02:00
|
|
|
local kamikazeThreshold = unitGroupUtils.calculateKamikazeThreshold(#squad.group.members, natives)
|
2017-07-01 06:36:23 +02:00
|
|
|
if (mRandom() < kamikazeThreshold) then
|
2017-05-24 08:46:23 +02:00
|
|
|
squad.kamikaze = true
|
|
|
|
end
|
2017-04-22 01:14:04 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-06-01 04:48:59 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
if (maxSquadIndex == squadCount) then
|
|
|
|
natives.regroupIndex = 1
|
|
|
|
else
|
2017-08-08 10:19:51 +02:00
|
|
|
natives.regroupIndex = maxSquadIndex + 1
|
2017-06-01 04:48:59 +02:00
|
|
|
end
|
2016-08-05 06:47:51 +02:00
|
|
|
end
|
|
|
|
|
2016-08-30 06:08:22 +02:00
|
|
|
return unitGroupUtils
|