607 lines
19 KiB
Lua

if (squadCompressionG) then
return squadCompressionG
end
local squadCompression = {}
local constants = require("Constants")
local mapUtils = require("MapUtils")
local mathUtils = require("MathUtils")
local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE
local BASE_PHEROMONE = constants.BASE_PHEROMONE
local BASE_DETECTION_PHEROMONE = constants.BASE_DETECTION_PHEROMONE
local PLAYER_PHEROMONE_GENERATOR_AMOUNT = constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT
local getChunkByXY = mapUtils.getChunkByXY
local getChunkByPosition = mapUtils.getChunkByPosition
local positionToChunkXY = mapUtils.positionToChunkXY
local euclideanDistancePoints = mathUtils.euclideanDistancePoints
local mCeil = math.ceil
local mMax = math.max
local compressedColor = {r = 0.3, g = 0.5, b = 0, a = 0.3}
local smthCompressedColor = {r = 0.3, g = 0.5, b = 0, a = 0.3} --{r = 0.7, g = 0.3, b = 0, a = 0.5}
function squadCompression.clearComresseData(universe, squad)
if not squad then
return
end
local group = squad.group
if group and group.valid then
local compresseData
for _, entity in pairs(group.members) do
compresseData = universe.compressedUnits[entity.unit_number]
if compresseData then
if compresseData.textId and rendering.is_valid(compresseData.textId) then
rendering.destroy(compresseData.textId)
end
universe.compressedUnits[entity.unit_number] = nil
end
end
end
squad.compressed = nil
squad.smoothCompressed = nil
end
function squadCompression.decompressUnit(universe, surface, entity)
compressIndex = entity.unit_number
compressedUnit = universe.compressedUnits[compressIndex]
if compressedUnit then
local decompressed = 1
if (compressedUnit.count > 1) then
for i = 2, compressedUnit.count do
local newEntity = surface.create_entity({
name = entity.name,
position = entity.position,
direction = entity.direction,
force = entity.force,
})
if newEntity and newEntity.valid then
newEntity.destructible = false
universe.oneTickImmunityUnits[#universe.oneTickImmunityUnits+1] = {entity = newEntity, tick = game.tick + 1}
end
decompressed = decompressed + 1
end
if compressedUnit.textId and rendering.is_valid(compressedUnit.textId) then
rendering.destroy(compressedUnit.textId)
end
end
universe.compressedUnits[compressIndex] = nil
return decompressed
else
return 0
end
end
function squadCompression.squadDecompress(universe, surface, squad, group, cause, forceDecompress)
if not surface then
return
end
if (not squad) then
if not group then
return
end
elseif not forceDecompress then
if not squad.compressed then
return
end
end
if not group then
group = squad.group
end
local decomressedTotal = 0
local queuedMembers = {}
local decomressLater = false
local compressedUnit
local compressIndex
if group and group.valid then
for _, entity in pairs(group.members) do
compressIndex = entity.unit_number
compressedUnit = universe.compressedUnits[compressIndex]
if compressedUnit then
if (compressedUnit.count > 1) then
local decomressed = 1
for i = 2, compressedUnit.count do
if decomressedTotal >= 200 then
break
end
local newEntity = surface.create_entity({
name = entity.name,
position = entity.position,
direction = entity.direction,
force = entity.force,
})
if newEntity.valid then
group.add_member(newEntity)
end
newEntity.destructible = false
universe.oneTickImmunityUnits[#universe.oneTickImmunityUnits+1] = {entity = newEntity, tick = game.tick + 1}
decomressedTotal = decomressedTotal + 1
decomressed = decomressed + 1
end
if decomressed < compressedUnit.count then
queuedMembers[compressIndex] = {
count = compressedUnit.count - decomressed,
name = entity.name,
position = {x = entity.position.x, y = entity.position.y},
direction = entity.direction,
force = entity.force
}
decomressLater = true
end
if compressedUnit.textId and rendering.is_valid(compressedUnit.textId) then
rendering.destroy(compressedUnit.textId)
end
end
universe.compressedUnits[compressIndex] = nil
end
end
if decomressLater then
universe.decomressQueue[group] = queuedMembers
end
--game.print(group.group_number..": decompressed: + "..decomressedTotal.." units [gps=" .. group.position.x .. "," .. group.position.y .."]") -- DEBUG
end
if squad then
squad.compressed = nil
squad.smoothCompressed = nil
squad.compressTick = game.tick
squad.decompressCause = cause
end
end
function squadCompression.processDecompressQueue(universe)
local decomressQueue = universe.decomressQueue
local group
local queuedMembers
group, queuedMembers = next(decomressQueue, nil)
if group then
local decomresseFinished = true
if group.valid then
local decomressedTotal = 0
for compressIndex, compressedData in pairs (queuedMembers) do
if decomressedTotal >= 50 then
decomresseFinished = false
break
end
local decomressed = 0
for i = 1, compressedData.count do
if decomressedTotal >= 50 then
decomresseFinished = false
break
end
local newEntity = group.surface.create_entity({
name = compressedData.name,
position = compressedData.position,
direction = compressedData.direction,
force = compressedData.force,
})
if newEntity.valid then
group.add_member(newEntity)
end
decomressedTotal = decomressedTotal + 1
decomressed = decomressed + 1
end
compressedData.count = compressedData.count - decomressed
if compressedData.count <= 0 then
queuedMembers[compressIndex] = nil
end
end
end
if decomresseFinished then
decomressQueue[group] = nil
end
end
end
local function draw_CompressedText(count, surface, entity, color, textId)
if textId and rendering.is_valid(textId) then
rendering.destroy(textId)
end
return rendering.draw_text{text = tostring(count),
surface = surface,
target = entity,
only_in_alt_mode = false,
color = (color or compressedColor),
scale = 2.5
}
end
local function destroy_CompressedText(textId)
if textId and rendering.is_valid(textId) then
rendering.destroy(textId)
end
end
local function squadCompress(map, squad)
if not squad then
return
end
if squad.compressed then
return
end
if squad.compressTick and ((game.tick - squad.compressTick) < 3600) then
return
end
local group = squad.group
if not group then
return
end
local surface = map.surface
local position
local groupPosition = group.position
local x, y = positionToChunkXY(groupPosition)
if squad.decompressCause and squad.decompressCause.valid then
local causeRange = mathUtils.euclideanDistancePoints(x, y, squad.decompressCause.position.x, squad.decompressCause.position.y)
if causeRange < 100 then
return
end
end
squad.decompressCause = nil
squad.compressTick = game.tick
local compressedUnits = map.universe.compressedUnits
local compressedUnit
local compressedMembers = {}
local unitsCounter = 0
if #group.members > 35 then
for _, entity in pairs(group.members) do
if entity.valid then
compressIndex = entity.name
if not compressedMembers[entity.name] then
compressedMembers[entity.name] = {count = 0}
end
compressedUnit = compressedUnits[entity.unit_number]
if compressedUnit then
compressedMembers[entity.name].count = compressedMembers[entity.name].count + compressedUnit.count
destroy_CompressedText(compressedUnit.textId)
compressedUnits[entity.unit_number] = nil
else
compressedMembers[entity.name].count = compressedMembers[entity.name].count + 1
end
if compressedMembers[entity.name].count > 1 then
unitsCounter = unitsCounter + compressedMembers[entity.name].count
entity.destroy()
end
end
end
for _, entity in pairs(group.members) do
if entity.valid then
local stackSize = compressedMembers[entity.name].count
if stackSize > 1 then
compressedUnits[entity.unit_number] = {count = stackSize, entity = entity}
if squad.nonRampantSquad then
compressedUnits[entity.unit_number].textId = draw_CompressedText(compressedUnits[entity.unit_number].count, surface, entity, {r = 0, g = 0.5, b = 0, a = 0.3})
else
compressedUnits[entity.unit_number].textId = draw_CompressedText(compressedUnits[entity.unit_number].count, surface, entity, compressedColor)
end
end
end
end
squad.compressed = true
-- game.print("compressed: + "..unitsCounter.." units [gps=" .. group.position.x .. "," .. group.position.y .."]") -- DEBUG
end
end
-----------------
-- compressDatas
-- name1 10 biters
-- name2 20
-- name3 15
-- name4 25
-- name5 30
-- membersToCompress = 100
-- compressedSize = 30
-- ==> averageStackSize = 4
-- result:
-- name1 3 4 3 3 10
-- name2 5 4 4 4 4 4 20
-- name3 4 4 4 4 3 15
-- name4 7 4 4 4 4 3 3 3 25
-- name5 8 4 4 4 4 4 4 3 3 30
-- 27 100
local function squadSmoothCompress(map, squad, compressedSize)
if not squad then
return
end
if not compressedSize then
return
end
if squad.smoothCompressed then
return
end
local group = squad.group
if not group then
return
end
if #group.members < (compressedSize + 5) then
return
end
if not squad.compressed and (squad.compressTick and ((game.tick - squad.compressTick) < 3600)) then
return
end
local compressedUnits = map.universe.compressedUnits
local compressedUnit
local surface = group.surface
------------------
local compressDatas = {}
local membersToCompress = 0
local unitsCounter = 0 -- debug
for _, entity in pairs(group.members) do
if entity.valid then
if not compressDatas[entity.name] then
compressDatas[entity.name] = {count = 0, entities = {}, unitSample = entity}
end
compressedUnit = compressedUnits[entity.unit_number]
if compressedUnit then
compressDatas[entity.name].count = compressDatas[entity.name].count + compressedUnit.count
membersToCompress = membersToCompress + compressedUnit.count
destroy_CompressedText(compressedUnit.textId)
compressedUnits[entity.unit_number] = nil
else
compressDatas[entity.name].count = compressDatas[entity.name].count + 1
membersToCompress = membersToCompress + 1
end
local entities = compressDatas[entity.name].entities
entities[#entities+1] = entity
end
end
local averageStackSize = mCeil(membersToCompress/compressedSize)
for entityName, compressData in pairs(compressDatas) do
compressData.maxStacks = mCeil(compressData.count / averageStackSize)
local entities = compressData.entities
local unitsToCreate = compressData.maxStacks - #entities
if unitsToCreate > 0 then
for i = 1, unitsToCreate do
local unitSample = compressData.unitSample
local entity = surface.create_entity({
name = unitSample.name,
position = unitSample.position,
direction = unitSample.direction,
force = unitSample.force,
})
entities[#entities+1] = entity
group.add_member(entity)
end
elseif unitsToCreate < 0 then
for i = #entities, (compressData.maxStacks + 1), -1 do
entities[i].destroy()
end
end
local unitsCounted = 0
local stacksCreated = 0
for i = compressData.maxStacks, 1, - 1 do
local entity = entities[i]
local stackSize = mCeil((compressData.count - unitsCounted) / i)
unitsCounted = unitsCounted + stackSize
if stackSize > 1 then
compressedUnits[entity.unit_number] = {count = stackSize, entity = entity}
if squad.nonRampantSquad then
compressedUnits[entity.unit_number].textId = draw_CompressedText(compressedUnits[entity.unit_number].count, surface, entity, {r = 0, g = 0.5, b = 0, a = 0.3})
else
compressedUnits[entity.unit_number].textId = draw_CompressedText(compressedUnits[entity.unit_number].count, surface, entity, smthCompressedColor)
end
unitsCounter = unitsCounter + stackSize -- debug
end
end
end
------------------
squad.compressed = true
squad.smoothCompressed = true
-- if unitsCounter > 0 then
-- game.print("squadSmoothCompress: compressed: + "..unitsCounter.." units [gps=" .. group.position.x .. "," .. group.position.y .."]") -- debug
-- end
end
function squadCompression.processCompression(map, squad, chunk, fullCompressAllowed, debugMessages)
if not map then
return
end
if chunk and (chunk ~= -1) then
if not squad.compressed then
if (chunk[PLAYER_PHEROMONE] < PLAYER_PHEROMONE_GENERATOR_AMOUNT * 0.008) then -- ~6 chunks
if fullCompressAllowed and (chunk[BASE_DETECTION_PHEROMONE] < 3138) then
squadCompress(map, squad)
elseif (chunk[BASE_DETECTION_PHEROMONE] < 5000) then
local squadSize = 100
if map.universe.squadCount < 5 then
squadSize = 100
else
squadSize = mMax(20, mCeil(500 / (map.universe.squadCount)))
end
squadSmoothCompress(map, squad, squadSize)
end
else
squad.compressTick = game.tick
end
else
if (chunk[BASE_DETECTION_PHEROMONE] > 7000) or (chunk[PLAYER_PHEROMONE] > PLAYER_PHEROMONE_GENERATOR_AMOUNT * 0.04) then -- ~ 4 chunks
if debugMessages then
game.print("processCompression: squad#"..squad.groupNumber.." squadDecompress [gps=" .. squad.group.position.x .. "," .. squad.group.position.y .."]") -- debug
end
squadCompression.squadDecompress(map.universe, map.surface, squad)
elseif (not squad.smoothCompressed) and (chunk[BASE_DETECTION_PHEROMONE] > 3874) then
local squadSize = 100
if map.universe.squadCount < 5 then
squadSize = 100
else
squadSize = mMax(20, mCeil(500 / (map.universe.squadCount)))
end
if debugMessages then
game.print("processCompression: squad#"..squad.groupNumber.." squadSmoothCompress [gps=" .. squad.group.position.x .. "," .. squad.group.position.y .."]") -- debug
end
squadSmoothCompress(map, squad, squadSize) -- squadFullToSmoothCompress
end
end
end
end
-- DEBUG
-- local renderColor = {0, 1, 0}
-- local renderColor2 = {1, 0, 0}
function squadCompression.processNonRampantSquads(universe)
local map
local u = 0
for i, squad in pairs(universe.nonRampantCompressedSquads) do
u = u + 1
map = squad.map
if map and squad.group.valid then
squadCompression.processCompression(map, squad, getChunkByPosition(map, squad.group.position), false, false) -- debug , true
-- debug
-- rendering.draw_circle{color = renderColor, filled = false, radius = 0.5, width = 2, target = squad.group.position, surface = squad.group.surface, time_to_live = 600}
-- rendering.draw_text{text = tostring(u), surface = squad.group.surface, target = squad.group.position, color = renderColor2, scale = 3, time_to_live = 600}
else
squad.compressed = nil
end
if not squad.compressed then
universe.nonRampantCompressedSquads[i] = nil
end
end
end
function squadCompression.onUnitKilled(universe, surface, entity, eventForce, cause)
if (not entity) or not (entity.valid) then
return
end
local compressedUnits = universe.compressedUnits
local compressedUnit = compressedUnits[entity.unit_number]
if not compressedUnit then
return
end
compressedUnit.count = compressedUnit.count - 1
if compressedUnit.count < 1 then
compressedUnits[entity.unit_number] = nil
else
newEntity = surface.create_entity({
name = entity.name,
position = entity.position,
direction = entity.direction,
force = entity.force,
})
if compressedUnit.count > 1 then
compressedUnits[newEntity.unit_number] = {count = compressedUnit.count, entity = newEntity}
compressedUnits[newEntity.unit_number].textId = draw_CompressedText(compressedUnits[newEntity.unit_number].count, surface, newEntity)
end
compressedUnits[entity.unit_number] = nil
local group = entity.unit_group
if group then
group.add_member(newEntity)
end
if eventForce and (eventForce.name ~= "enemy") and cause and cause.valid then
newEntity.destructible = false
universe.oneTickImmunityUnits[#universe.oneTickImmunityUnits+1] = {entity = newEntity, tick = game.tick + 5}
local incomingRange = mathUtils.euclideanDistancePoints(entity.position.x, entity.position.y, cause.position.x, cause.position.y)
if group then
local squad = universe.groupNumberToSquad[group.group_number] or universe.nonRampantCompressedSquads[group.group_number]
if incomingRange < 70 then
squadCompression.squadDecompress(universe, surface, squad, group, cause, true)
end
elseif incomingRange < 20 then
squadCompression.decompressUnit(universe, surface, newEntity)
end
end
end
end
function squadCompression.onUnitPreKilled(universe, surface, entity, eventForce, cause)
local compressedUnits = universe.compressedUnits
local compressedUnit = compressedUnits[entity.unit_number]
if not compressedUnit then
return
end
if compressedUnit.count < 2 then
compressedUnits[entity.unit_number] = nil
else
destroy_CompressedText(compressedUnit.textId)
entity.health = entity.prototype.max_health
compressedUnit.count = compressedUnit.count - 1
if compressedUnit.count > 1 then
compressedUnit.textId = draw_CompressedText(compressedUnit.count, surface, entity)
end
newEntity = surface.create_entity({
name = entity.name,
position = entity.position,
direction = entity.direction,
force = entity.force,
})
if newEntity and newEntity.valid then
newEntity.health = 0
end
if eventForce and (eventForce.name ~= "enemy") and cause and cause.valid then
local incomingRange = mathUtils.euclideanDistancePoints(entity.position.x, entity.position.y, cause.position.x, cause.position.y)
if group then
local squad = universe.groupNumberToSquad[group.group_number] or universe.nonRampantCompressedSquads[group.group_number]
if incomingRange < 70 then
squadCompression.squadDecompress(universe, surface, squad, group, cause, true)
end
elseif incomingRange < 20 then
squadCompression.decompressUnit(universe, surface, entity)
end
end
end
end
function squadCompression.removeOneTickImmunity(universe)
for i,entityData in pairs(universe.oneTickImmunityUnits) do
if game.tick >= entityData.tick then
local entity = entityData.entity
if entity.valid then
entity.destructible = true
end
universe.oneTickImmunityUnits[i] = nil
end
end
end
function squadCompression.checkCompressedUnitsList(universe)
if universe then
local compressedUnits = universe.compressedUnits
for compressedIndex, compressedUnit in pairs(compressedUnits) do
if (not compressedUnit.entity) or (not compressedUnit.entity.valid) then
compressedUnits[compressedIndex] = nil
end
end
end
end
squadCompressionG = squadCompression
return squadCompression