2016-08-05 06:47:51 +02:00
|
|
|
local mapProcessor = {}
|
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
-- imports
|
|
|
|
|
2016-08-21 23:48:55 +02:00
|
|
|
local pheromoneUtils = require("PheromoneUtils")
|
|
|
|
local aiBuilding = require("AIBuilding")
|
2017-05-19 09:47:24 +02:00
|
|
|
local aiPredicates = require("AIPredicates")
|
2016-08-29 02:05:28 +02:00
|
|
|
local constants = require("Constants")
|
2016-10-15 02:00:18 +02:00
|
|
|
local mapUtils = require("MapUtils")
|
2017-05-19 09:47:24 +02:00
|
|
|
local playerUtils = require("PlayerUtils")
|
2016-08-18 07:55:08 +02:00
|
|
|
|
2016-09-14 13:16:33 +02:00
|
|
|
-- constants
|
|
|
|
|
|
|
|
local PROCESS_QUEUE_SIZE = constants.PROCESS_QUEUE_SIZE
|
2016-11-04 09:26:19 +02:00
|
|
|
|
2016-09-14 13:16:33 +02:00
|
|
|
local SCAN_QUEUE_SIZE = constants.SCAN_QUEUE_SIZE
|
|
|
|
|
|
|
|
local CHUNK_SIZE = constants.CHUNK_SIZE
|
2017-06-01 03:46:53 +02:00
|
|
|
local DOUBLE_CHUNK_SIZE = constants.DOUBLE_CHUNK_SIZE
|
|
|
|
local TRIPLE_CHUNK_SIZE = constants.TRIPLE_CHUNK_SIZE
|
2016-10-15 02:00:18 +02:00
|
|
|
|
|
|
|
local PROCESS_PLAYER_BOUND = constants.PROCESS_PLAYER_BOUND
|
|
|
|
local CHUNK_TICK = constants.CHUNK_TICK
|
|
|
|
|
2017-05-28 06:50:37 +02:00
|
|
|
local NEST_COUNT = constants.NEST_COUNT
|
|
|
|
local WORM_COUNT = constants.WORM_COUNT
|
2017-05-12 06:50:06 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local AI_MAX_OVERFLOW_POINTS = constants.AI_MAX_OVERFLOW_POINTS
|
2016-11-04 09:26:19 +02:00
|
|
|
local AI_SQUAD_COST = constants.AI_SQUAD_COST
|
|
|
|
local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST
|
|
|
|
|
|
|
|
local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
|
2017-04-17 04:46:36 +02:00
|
|
|
local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR
|
2017-06-08 02:57:24 +02:00
|
|
|
local RESOURCE_GENERATOR = constants.RESOURCE_GENERATOR
|
2017-04-17 04:46:36 +02:00
|
|
|
local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES
|
2016-11-04 09:26:19 +02:00
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
-- imported functions
|
|
|
|
|
2016-08-21 23:48:55 +02:00
|
|
|
local scents = pheromoneUtils.scents
|
|
|
|
local processPheromone = pheromoneUtils.processPheromone
|
2016-08-20 04:52:27 +02:00
|
|
|
|
2016-08-21 23:48:55 +02:00
|
|
|
local formSquads = aiBuilding.formSquads
|
2016-08-20 04:52:27 +02:00
|
|
|
|
2016-10-15 02:00:18 +02:00
|
|
|
local getChunkByIndex = mapUtils.getChunkByIndex
|
|
|
|
local getChunkByPosition = mapUtils.getChunkByPosition
|
|
|
|
|
|
|
|
local playerScent = pheromoneUtils.playerScent
|
2016-08-20 04:52:27 +02:00
|
|
|
|
2017-05-19 09:47:24 +02:00
|
|
|
local canAttack = aiPredicates.canAttack
|
2017-05-14 00:32:16 +02:00
|
|
|
|
2017-05-28 06:50:37 +02:00
|
|
|
local euclideanDistanceNamed = mapUtils.euclideanDistanceNamed
|
|
|
|
|
2016-08-29 02:05:28 +02:00
|
|
|
local mMin = math.min
|
|
|
|
|
2017-05-19 09:47:24 +02:00
|
|
|
local validPlayer = playerUtils.validPlayer
|
|
|
|
|
2016-08-20 04:52:27 +02:00
|
|
|
-- module code
|
|
|
|
|
2016-10-15 02:00:18 +02:00
|
|
|
local function nonRepeatingRandom(players)
|
|
|
|
local ordering = {}
|
|
|
|
for _,player in pairs(players) do
|
|
|
|
ordering[#ordering+1] = player.index
|
|
|
|
end
|
|
|
|
for i=#ordering,1,-1 do
|
|
|
|
local s = math.random(i)
|
|
|
|
local t = ordering[i]
|
|
|
|
ordering[i] = ordering[s]
|
|
|
|
ordering[s] = t
|
|
|
|
end
|
|
|
|
return ordering
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
processing is not consistant as it depends on the number of chunks that have been generated
|
|
|
|
so if we process 400 chunks an iteration and 200 chunks have been generated than these are
|
|
|
|
processed 3 times a second and 1200 generated chunks would be processed once a second
|
|
|
|
In theory, this might be fine as smaller bases have less surface to attack and need to have
|
|
|
|
pheromone dissipate at a faster rate.
|
|
|
|
--]]
|
2017-06-01 03:46:53 +02:00
|
|
|
function mapProcessor.processMap(regionMap, surface, natives, tick)
|
2016-08-29 02:05:28 +02:00
|
|
|
local roll = regionMap.processRoll
|
|
|
|
local index = regionMap.processPointer
|
2016-08-25 01:30:45 +02:00
|
|
|
|
2016-08-29 02:05:28 +02:00
|
|
|
if (index == 1) then
|
2016-08-26 00:20:06 +02:00
|
|
|
roll = math.random()
|
2016-08-29 02:05:28 +02:00
|
|
|
regionMap.processRoll = roll
|
2016-08-26 00:20:06 +02:00
|
|
|
end
|
2017-06-01 03:46:53 +02:00
|
|
|
|
2017-06-08 02:57:24 +02:00
|
|
|
local tempNeighbors = {nil, nil, nil, nil}
|
|
|
|
local tempNeighborsAll = {nil, nil, nil, nil, nil, nil, nil, nil}
|
2017-05-14 00:32:16 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local squads = canAttack(natives, surface) and (0.11 <= roll) and (roll <= 0.35) and (natives.points >= AI_SQUAD_COST)
|
2016-08-20 04:52:27 +02:00
|
|
|
|
2016-08-29 02:05:28 +02:00
|
|
|
local processQueue = regionMap.processQueue
|
2016-09-14 13:16:33 +02:00
|
|
|
local endIndex = mMin(index + PROCESS_QUEUE_SIZE, #processQueue)
|
2016-08-29 02:05:28 +02:00
|
|
|
for x=index,endIndex do
|
|
|
|
local chunk = processQueue[x]
|
2017-04-22 01:14:04 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
if (chunk[CHUNK_TICK] ~= tick) then
|
|
|
|
chunk[CHUNK_TICK] = tick
|
|
|
|
|
|
|
|
processPheromone(regionMap, chunk, tempNeighbors)
|
2017-04-22 01:14:04 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
if squads and (chunk[NEST_COUNT] ~= 0) then
|
|
|
|
formSquads(regionMap, surface, natives, chunk, AI_SQUAD_COST, tempNeighborsAll)
|
|
|
|
squads = natives.points >= AI_SQUAD_COST
|
|
|
|
end
|
|
|
|
|
|
|
|
scents(chunk)
|
|
|
|
end
|
2016-08-29 02:05:28 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
if (endIndex == #processQueue) then
|
|
|
|
regionMap.processPointer = 1
|
|
|
|
else
|
|
|
|
regionMap.processPointer = endIndex + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-15 02:00:18 +02:00
|
|
|
--[[
|
|
|
|
Localized player radius were processing takes place in realtime, doesn't store state
|
|
|
|
between calls.
|
|
|
|
vs
|
|
|
|
the slower passive version processing the entire map in multiple passes.
|
|
|
|
--]]
|
2017-06-01 03:46:53 +02:00
|
|
|
function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
|
2016-10-15 02:00:18 +02:00
|
|
|
-- put down player pheromone for player hunters
|
|
|
|
-- randomize player order to ensure a single player isn't singled out
|
|
|
|
local playerOrdering = nonRepeatingRandom(players)
|
2017-03-25 23:46:30 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local roll = math.random()
|
|
|
|
|
|
|
|
local allowingAttacks = canAttack(natives, surface)
|
2016-10-15 02:00:18 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local squads = allowingAttacks and (0.11 <= roll) and (roll <= 0.20) and (natives.points >= AI_SQUAD_COST)
|
|
|
|
|
2017-06-08 02:57:24 +02:00
|
|
|
local tempNeighbors = {nil, nil, nil, nil}
|
|
|
|
local tempNeighborsAll = {nil, nil, nil, nil, nil, nil, nil, nil}
|
2016-10-15 02:00:18 +02:00
|
|
|
|
|
|
|
for i=1,#playerOrdering do
|
|
|
|
local player = players[playerOrdering[i]]
|
2017-05-19 09:47:24 +02:00
|
|
|
if validPlayer(player) then
|
2016-10-15 02:00:18 +02:00
|
|
|
local playerPosition = player.character.position
|
|
|
|
local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y)
|
|
|
|
|
2017-05-27 02:58:33 +02:00
|
|
|
if playerChunk then
|
2016-10-15 02:00:18 +02:00
|
|
|
playerScent(playerChunk)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for i=1,#playerOrdering do
|
|
|
|
local player = players[playerOrdering[i]]
|
2017-05-19 09:47:24 +02:00
|
|
|
if validPlayer(player) then
|
2016-10-15 02:00:18 +02:00
|
|
|
local playerPosition = player.character.position
|
|
|
|
local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y)
|
|
|
|
|
2017-05-27 02:58:33 +02:00
|
|
|
if playerChunk then
|
2017-06-01 03:46:53 +02:00
|
|
|
local vengence = allowingAttacks and ((playerChunk[NEST_COUNT] ~= 0) or (playerChunk[WORM_COUNT] ~= 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold)) and (natives.points >= AI_VENGENCE_SQUAD_COST)
|
|
|
|
|
2016-10-15 02:00:18 +02:00
|
|
|
for x=playerChunk.cX - PROCESS_PLAYER_BOUND, playerChunk.cX + PROCESS_PLAYER_BOUND do
|
|
|
|
for y=playerChunk.cY - PROCESS_PLAYER_BOUND, playerChunk.cY + PROCESS_PLAYER_BOUND do
|
|
|
|
local chunk = getChunkByIndex(regionMap, x, y)
|
2017-05-27 02:58:33 +02:00
|
|
|
if chunk and (chunk[CHUNK_TICK] ~= tick) then
|
2016-10-15 02:00:18 +02:00
|
|
|
chunk[CHUNK_TICK] = tick
|
2017-04-22 01:14:04 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
processPheromone(regionMap, chunk, tempNeighbors)
|
2016-11-04 09:26:19 +02:00
|
|
|
|
2016-10-15 02:00:18 +02:00
|
|
|
if squads then
|
2017-06-01 03:46:53 +02:00
|
|
|
formSquads(regionMap, surface, natives, chunk, AI_SQUAD_COST, tempNeighborsAll)
|
|
|
|
squads = natives.points >= AI_SQUAD_COST
|
2016-11-04 09:26:19 +02:00
|
|
|
end
|
|
|
|
if vengence then
|
2017-06-01 03:46:53 +02:00
|
|
|
formSquads(regionMap, surface, natives, chunk, AI_VENGENCE_SQUAD_COST, tempNeighborsAll)
|
|
|
|
vengence = natives.points >= AI_VENGENCE_SQUAD_COST
|
2016-10-15 02:00:18 +02:00
|
|
|
end
|
2017-04-22 01:14:04 +02:00
|
|
|
|
|
|
|
scents(chunk)
|
|
|
|
|
2016-10-15 02:00:18 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-04 01:51:35 +02:00
|
|
|
--[[
|
2017-04-17 04:46:36 +02:00
|
|
|
Passive scan to find entities that have been generated outside the factorio event system
|
2016-11-04 01:51:35 +02:00
|
|
|
--]]
|
2017-06-01 03:46:53 +02:00
|
|
|
function mapProcessor.scanMap(regionMap, surface, natives)
|
2017-05-28 06:50:37 +02:00
|
|
|
local index = regionMap.scanPointer
|
2017-06-01 03:46:53 +02:00
|
|
|
|
|
|
|
local offset = {0, 0}
|
|
|
|
local chunkBox = {false, offset}
|
2017-05-24 08:46:23 +02:00
|
|
|
local playerQuery = {area = chunkBox,
|
|
|
|
force = "player"}
|
2017-06-08 02:57:24 +02:00
|
|
|
local resourceQuery = {area = chunkBox,
|
|
|
|
type = "resource"}
|
2017-06-01 03:46:53 +02:00
|
|
|
local unitCountQuery = { area = chunkBox,
|
|
|
|
type = "unit",
|
|
|
|
force = "enemy",
|
|
|
|
limit = 301 }
|
2017-05-24 08:46:23 +02:00
|
|
|
|
2016-08-29 02:05:28 +02:00
|
|
|
local processQueue = regionMap.processQueue
|
2016-09-14 13:16:33 +02:00
|
|
|
local endIndex = mMin(index + SCAN_QUEUE_SIZE, #processQueue)
|
2017-05-24 08:46:23 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local unitRefundAmount = natives.unitRefundAmount
|
|
|
|
|
2016-08-29 02:05:28 +02:00
|
|
|
for x=index,endIndex do
|
2016-10-15 02:00:18 +02:00
|
|
|
local chunk = processQueue[x]
|
2017-04-17 04:46:36 +02:00
|
|
|
|
2017-05-28 06:50:37 +02:00
|
|
|
chunkBox[1] = chunk
|
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
offset[1] = chunk.x + CHUNK_SIZE
|
|
|
|
offset[2] = chunk.y + CHUNK_SIZE
|
2017-05-24 08:46:23 +02:00
|
|
|
|
|
|
|
local unitCount = surface.count_entities_filtered(unitCountQuery)
|
|
|
|
|
|
|
|
if (unitCount > 300) then
|
2017-06-01 03:46:53 +02:00
|
|
|
local closeBy = false
|
|
|
|
local squads = natives.squads
|
|
|
|
for i=1, #squads do
|
|
|
|
local squadGroup = squads[i].group
|
|
|
|
if squadGroup.valid and (euclideanDistanceNamed(squadGroup.position, chunk) < DOUBLE_CHUNK_SIZE) then
|
2017-05-24 08:46:23 +02:00
|
|
|
closeBy = true
|
2017-06-01 03:46:53 +02:00
|
|
|
break
|
2017-05-24 08:46:23 +02:00
|
|
|
end
|
|
|
|
end
|
2017-06-01 03:46:53 +02:00
|
|
|
|
|
|
|
if not closeBy then
|
|
|
|
local units = surface.find_enemy_units(chunk, TRIPLE_CHUNK_SIZE)
|
2017-04-22 01:14:04 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
unitCount = #units
|
|
|
|
for i=1,unitCount do
|
|
|
|
units[i].destroy()
|
|
|
|
end
|
|
|
|
natives.points = natives.points + (unitCount * unitRefundAmount)
|
2017-04-22 01:14:04 +02:00
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
if (natives.points > AI_MAX_OVERFLOW_POINTS) then
|
|
|
|
natives.points = AI_MAX_OVERFLOW_POINTS
|
|
|
|
end
|
2017-04-22 01:14:04 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-08 02:57:24 +02:00
|
|
|
chunk[RESOURCE_GENERATOR] = surface.count_entities_filtered(resourceQuery)
|
|
|
|
|
2017-06-01 03:46:53 +02:00
|
|
|
local entities = surface.find_entities_filtered(playerQuery)
|
2017-04-17 04:46:36 +02:00
|
|
|
local playerBaseGenerator = 0
|
2017-05-24 08:46:23 +02:00
|
|
|
local safeBuildings = natives.safeBuildings
|
2017-04-17 04:46:36 +02:00
|
|
|
for i=1,#entities do
|
|
|
|
local entity = entities[i]
|
|
|
|
local value = BUILDING_PHEROMONES[entity.type]
|
2017-05-24 08:46:23 +02:00
|
|
|
if safeBuildings then
|
|
|
|
if natives.safeEntities[entity.type] or natives.safeEntityName[entity.name] then
|
|
|
|
entity.destructible = false
|
|
|
|
end
|
|
|
|
end
|
2017-04-17 04:46:36 +02:00
|
|
|
if (value ~= nil) then
|
|
|
|
playerBaseGenerator = playerBaseGenerator + value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
chunk[PLAYER_BASE_GENERATOR] = playerBaseGenerator
|
2016-08-05 06:47:51 +02:00
|
|
|
end
|
2017-04-22 01:14:04 +02:00
|
|
|
|
2016-08-29 02:05:28 +02:00
|
|
|
if (endIndex == #processQueue) then
|
2016-10-15 02:00:18 +02:00
|
|
|
regionMap.scanPointer = 1
|
2016-08-29 02:05:28 +02:00
|
|
|
else
|
2016-10-15 02:00:18 +02:00
|
|
|
regionMap.scanPointer = endIndex + 1
|
2016-08-20 04:52:27 +02:00
|
|
|
end
|
2016-08-05 06:47:51 +02:00
|
|
|
end
|
|
|
|
|
2016-09-08 22:02:23 +02:00
|
|
|
return mapProcessor
|