mirror of
https://github.com/veden/Rampant.git
synced 2025-03-17 20:58:35 +02:00
Merge branch 'bases' into baseMerger
This commit is contained in:
commit
2b3fa0ca0e
15
Upgrade.lua
15
Upgrade.lua
@ -132,6 +132,21 @@ function upgrade.attempt(natives, regionMap)
|
||||
game.surfaces[1].print("Rampant - Version 0.15.10")
|
||||
global.version = constants.VERSION_22
|
||||
end
|
||||
if (global.version < constants.VERSION_22) then
|
||||
|
||||
natives.bases = {}
|
||||
|
||||
natives.baseDistanceMin = 0
|
||||
|
||||
natives.baseIndex = 1
|
||||
|
||||
natives.randomGenerator = game.create_random_generator()
|
||||
|
||||
game.forces.enemy.ai_controllable = false
|
||||
|
||||
game.surfaces[1].print("Rampant - Version 0.15.10")
|
||||
global.version = constants.VERSION_22
|
||||
end
|
||||
return starting ~= global.version
|
||||
end
|
||||
|
||||
|
68
control.lua
68
control.lua
@ -1,10 +1,10 @@
|
||||
-- imports
|
||||
|
||||
local upgrade = require("Upgrade")
|
||||
local entityUtils = require("libs/EntityUtils")
|
||||
local mapUtils = require("libs/MapUtils")
|
||||
local unitGroupUtils = require("libs/UnitGroupUtils")
|
||||
local chunkProcessor = require("libs/ChunkProcessor")
|
||||
local baseProcessor = require("libs/BaseProcessor")
|
||||
local mapProcessor = require("libs/MapProcessor")
|
||||
local constants = require("libs/Constants")
|
||||
local pheromoneUtils = require("libs/PheromoneUtils")
|
||||
@ -14,6 +14,7 @@ local aiBuilding = require("libs/AIBuilding")
|
||||
local aiPlanning = require("libs/AIPlanning")
|
||||
local interop = require("libs/Interop")
|
||||
local tests = require("tests")
|
||||
local upgrade = require("Upgrade")
|
||||
|
||||
-- constants
|
||||
|
||||
@ -53,8 +54,11 @@ local squadBeginAttack = aiAttack.squadBeginAttack
|
||||
|
||||
local retreatUnits = aiDefense.retreatUnits
|
||||
|
||||
local addRemoveEntity = entityUtils.addRemoveEntity
|
||||
local makeImmortalEntity = entityUtils.makeImmortalEntity
|
||||
local addRemovePlayerEntity = entityUtils.addRemovePlayerEntity
|
||||
local removeEnemyBase = entityUtils.removeEnemyBase
|
||||
--local makeImmortalEntity = entityUtils.makeImmortalEntity
|
||||
|
||||
local processBases = baseProcessor.processBases
|
||||
|
||||
-- local references to global
|
||||
|
||||
@ -128,7 +132,8 @@ local function onConfigChanged()
|
||||
-- queue all current chunks that wont be generated during play
|
||||
local surface = game.surfaces[1]
|
||||
for chunk in surface.get_chunks() do
|
||||
onChunkGenerated({ surface = surface,
|
||||
onChunkGenerated({ tick = game.tick,
|
||||
surface = surface,
|
||||
area = { left_top = { x = chunk.x * 32,
|
||||
y = chunk.y * 32 }}})
|
||||
end
|
||||
@ -143,7 +148,7 @@ local function onTick(event)
|
||||
local evolutionFactor = game.forces.enemy.evolution_factor
|
||||
local players = game.players
|
||||
|
||||
processPendingChunks(natives, regionMap, surface, pendingChunks)
|
||||
processPendingChunks(natives, regionMap, surface, pendingChunks, tick)
|
||||
scanMap(regionMap, surface, natives, evolutionFactor)
|
||||
|
||||
if (tick == regionMap.logicTick) then
|
||||
@ -155,6 +160,9 @@ local function onTick(event)
|
||||
regroupSquads(natives, evolutionFactor)
|
||||
|
||||
processPlayers(players, regionMap, surface, natives, evolutionFactor, tick)
|
||||
|
||||
processBases(regionMap, surface, natives, tick)
|
||||
|
||||
squadBeginAttack(natives, players, evolutionFactor)
|
||||
squadAttack(regionMap, surface, natives)
|
||||
end
|
||||
@ -174,7 +182,7 @@ local function onBuild(event)
|
||||
end
|
||||
|
||||
local function onPickUp(event)
|
||||
addRemoveEntity(regionMap, event.entity, natives, false, false)
|
||||
addRemovePlayerEntity(regionMap, event.entity, natives, false, false)
|
||||
end
|
||||
|
||||
local function onDeath(event)
|
||||
@ -183,7 +191,7 @@ local function onDeath(event)
|
||||
if (surface.index == 1) then
|
||||
if (entity.force.name == "enemy") then
|
||||
if (entity.type == "unit") then
|
||||
local entityPosition = entity.position
|
||||
local entityPosition = entity.position
|
||||
local deathChunk = getChunkByPosition(regionMap, entityPosition.x, entityPosition.y)
|
||||
|
||||
if deathChunk then
|
||||
@ -216,7 +224,7 @@ local function onDeath(event)
|
||||
end
|
||||
|
||||
elseif (entity.type == "unit-spawner") or (entity.type == "turret") then
|
||||
addRemoveEntity(regionMap, entity, natives, false, false)
|
||||
removeEnemyBase(regionMap, entity)
|
||||
end
|
||||
elseif (entity.force.name == "player") then
|
||||
local creditNatives = false
|
||||
@ -231,17 +239,17 @@ local function onDeath(event)
|
||||
if creditNatives and natives.safeBuildings and (natives.safeEntities[entity.type] or natives.safeEntityName[entity.name]) then
|
||||
makeImmortalEntity(surface, entity)
|
||||
else
|
||||
addRemoveEntity(regionMap, entity, natives, false, creditNatives)
|
||||
addRemovePlayerEntity(regionMap, entity, natives, false, creditNatives)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onSurfaceTileChange(event)
|
||||
-- local player = game.players[event.player_index]
|
||||
-- if (player.surface.index==1) then
|
||||
-- aiBuilding.fillTunnel(global.regionMap, player.surface, global.natives, event.positions)
|
||||
-- end
|
||||
local player = game.players[event.player_index]
|
||||
if (player.surface.index==1) then
|
||||
aiBuilding.fillTunnel(regionMap, player.surface, natives, event.positions)
|
||||
end
|
||||
end
|
||||
|
||||
local function onInit()
|
||||
@ -277,18 +285,26 @@ script.on_event(defines.events.on_entity_died, onDeath)
|
||||
script.on_event(defines.events.on_tick, onTick)
|
||||
script.on_event(defines.events.on_chunk_generated, onChunkGenerated)
|
||||
|
||||
remote.add_interface("rampantTests", {
|
||||
test1 = tests.test1,
|
||||
test2 = tests.test2,
|
||||
test3 = tests.test3,
|
||||
test4 = tests.test4,
|
||||
test5 = tests.test5,
|
||||
test6 = tests.test6,
|
||||
test7 = tests.test7,
|
||||
test8 = tests.test8,
|
||||
test9 = tests.test9,
|
||||
test10 = tests.test10,
|
||||
test11 = tests.test11
|
||||
})
|
||||
remote.add_interface("rampantTests",
|
||||
{
|
||||
pheromoneLevels = tests.pheromoneLevels,
|
||||
activeSquads = tests.activeSquads,
|
||||
entitiesOnPlayerChunk = tests.entitiesOnPlayerChunk,
|
||||
findNearestPlayerEnemy = tests.findNearestPlayerEnemy,
|
||||
aiStats = tests.aiStats,
|
||||
fillableDirtTest = tests.fillableDirtTest,
|
||||
tunnelTest = tests.tunnelTest,
|
||||
createEnemy = tests.createEnemy,
|
||||
attackOrigin = tests.attackOrigin,
|
||||
cheatMode = tests.cheatMode,
|
||||
gaussianRandomTest = tests.gaussianRandomTest,
|
||||
reveal = tests.reveal,
|
||||
showMovementGrid = tests.showMovementGrid,
|
||||
baseStats = tests.baseStats,
|
||||
baseTiles = tests.baseTiles,
|
||||
mergeBases = tests.mergeBases,
|
||||
clearBases = tests.clearBases
|
||||
}
|
||||
)
|
||||
|
||||
remote.add_interface("rampant", interop)
|
||||
|
1
data.lua
1
data.lua
@ -2,6 +2,7 @@ require("prototypes/enemies/AttackAcidBall")
|
||||
require("prototypes/enemies/AttackAcidFlame")
|
||||
|
||||
require("prototypes/buildings/tunnel")
|
||||
require("prototypes/buildings/UnitSpawners")
|
||||
|
||||
require("prototypes/tile/fillableDirt")
|
||||
|
||||
|
@ -18,7 +18,6 @@ local SQUAD_RAIDING = constants.SQUAD_RAIDING
|
||||
local SQUAD_GUARDING = constants.SQUAD_GUARDING
|
||||
|
||||
local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR
|
||||
local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR
|
||||
|
||||
local DEFINES_GROUP_FINISHED = defines.group_state.finished
|
||||
local DEFINES_GROUP_GATHERING = defines.group_state.gathering
|
||||
@ -52,7 +51,7 @@ end
|
||||
|
||||
local function scoreAttackLocation(position, squad, neighborChunk, surface)
|
||||
local squadMovementPenalty = lookupSquadMovementPenalty(squad, neighborChunk.cX, neighborChunk.cY)
|
||||
local r = surface.get_pollution(position) + neighborChunk[MOVEMENT_PHEROMONE] + neighborChunk[BASE_PHEROMONE] + (neighborChunk[PLAYER_PHEROMONE] * 25) - neighborChunk[ENEMY_BASE_GENERATOR]
|
||||
local r = surface.get_pollution(position) + neighborChunk[MOVEMENT_PHEROMONE] + neighborChunk[BASE_PHEROMONE] + (neighborChunk[PLAYER_PHEROMONE] * 25) --- neighborChunk[ENEMY_BASE_GENERATOR]
|
||||
return r - squadMovementPenalty
|
||||
end
|
||||
|
||||
@ -118,9 +117,8 @@ function aiAttack.squadAttack(regionMap, surface, natives)
|
||||
squad.cycles = 4
|
||||
end
|
||||
|
||||
if not squad.rabid and squad.frenzy and (euclideanDistanceNamed(groupPosition, squad.frenzyPosition) > 100) then
|
||||
squad.frenzy = false
|
||||
end
|
||||
local outsideFrenzyRadius = not squad.rabid and squad.frenzy and (euclideanDistanceNamed(groupPosition, squad.frenzyPosition) > 100)
|
||||
squad.frenzy = not outsideFrenzyRadius
|
||||
|
||||
if squad.rabid or squad.frenzy then
|
||||
attackCmd.distraction = DEFINES_DISTRACTION_BY_ANYTHING
|
||||
|
@ -15,8 +15,6 @@ local BASE_PHEROMONE = constants.BASE_PHEROMONE
|
||||
local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE
|
||||
local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
|
||||
|
||||
local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR
|
||||
|
||||
local AI_MAX_SQUAD_COUNT = constants.AI_MAX_SQUAD_COUNT
|
||||
|
||||
local AI_SQUAD_COST = constants.AI_SQUAD_COST
|
||||
@ -35,6 +33,8 @@ local RALLY_CRY_DISTANCE = constants.RALLY_CRY_DISTANCE
|
||||
local DEFINES_COMMAND_GROUP = defines.command.group
|
||||
local DEFINES_DISTRACTION_NONE = defines.distraction.none
|
||||
|
||||
local CHUNK_BASE = constants.CHUNK_BASE
|
||||
|
||||
-- imported functions
|
||||
|
||||
local getNeighborChunks = mapUtils.getNeighborChunks
|
||||
@ -63,11 +63,7 @@ local function attackWaveValidCandidate(chunk, natives, surface, evolutionFactor
|
||||
local threshold = natives.attackThresholdRange
|
||||
local delta = threshold * evolutionFactor
|
||||
|
||||
if (total > ((threshold - delta) + natives.attackThresholdMin)) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
return total > ((threshold - delta) + natives.attackThresholdMin)
|
||||
end
|
||||
|
||||
local function scoreUnitGroupLocation(position, squad, neighborChunk, surface)
|
||||
@ -75,7 +71,7 @@ local function scoreUnitGroupLocation(position, squad, neighborChunk, surface)
|
||||
end
|
||||
|
||||
local function validUnitGroupLocation(x, chunk, neighborChunk)
|
||||
return neighborChunk[NORTH_SOUTH_PASSABLE] and neighborChunk[EAST_WEST_PASSABLE] and neighborChunk[ENEMY_BASE_GENERATOR] == 0
|
||||
return neighborChunk[NORTH_SOUTH_PASSABLE] and neighborChunk[EAST_WEST_PASSABLE] and neighborChunk[CHUNK_BASE] ~= nil
|
||||
end
|
||||
|
||||
function aiBuilding.rallyUnits(chunk, regionMap, surface, natives, evolutionFactor, tick)
|
||||
@ -95,15 +91,11 @@ function aiBuilding.rallyUnits(chunk, regionMap, surface, natives, evolutionFact
|
||||
end
|
||||
|
||||
function aiBuilding.formSquads(regionMap, surface, natives, chunk, evolution_factor, cost)
|
||||
if (natives.points > cost) and (chunk[ENEMY_BASE_GENERATOR] ~= 0) and (#natives.squads < (AI_MAX_SQUAD_COUNT * evolution_factor)) then
|
||||
local valid = false
|
||||
if not surface.peaceful_mode then
|
||||
if (cost == AI_VENGENCE_SQUAD_COST) then
|
||||
valid = true
|
||||
elseif (cost == AI_SQUAD_COST) then
|
||||
valid = attackWaveValidCandidate(chunk, natives, surface, evolution_factor)
|
||||
end
|
||||
end
|
||||
if (natives.points > cost) and (chunk[CHUNK_BASE] ~= nil) and (#natives.squads < (AI_MAX_SQUAD_COUNT * evolution_factor)) then
|
||||
local valid = not surface.peaceful_mode and
|
||||
((cost == AI_VENGENCE_SQUAD_COST) or
|
||||
((cost == AI_SQUAD_COST) and attackWaveValidCandidate(chunk, natives, surface, evolution_factor)))
|
||||
|
||||
if valid and (math.random() < mMax((0.25 * evolution_factor), 0.10)) then
|
||||
local squadPosition = {x=0, y=0}
|
||||
local squadPath, _ = scoreNeighbors(chunk,
|
||||
@ -120,9 +112,7 @@ function aiBuilding.formSquads(regionMap, surface, natives, chunk, evolution_fac
|
||||
|
||||
local squad = createSquad(squadPosition, surface, natives)
|
||||
|
||||
if (math.random() < 0.03) then
|
||||
squad.rabid = true
|
||||
end
|
||||
squad.rabid = math.random() < 0.03
|
||||
|
||||
local scaledWaveSize = attackWaveScaling(evolution_factor, natives)
|
||||
local foundUnits = surface.set_multi_command({ command = { type = DEFINES_COMMAND_GROUP,
|
||||
|
@ -15,8 +15,6 @@ local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
|
||||
local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE
|
||||
local BASE_PHEROMONE = constants.BASE_PHEROMONE
|
||||
|
||||
local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR
|
||||
|
||||
local HALF_CHUNK_SIZE = constants.HALF_CHUNK_SIZE
|
||||
|
||||
local SQUAD_RETREATING = constants.SQUAD_RETREATING
|
||||
@ -27,6 +25,8 @@ local RETREAT_TRIGGERED = constants.RETREAT_TRIGGERED
|
||||
|
||||
local INTERVAL_LOGIC = constants.INTERVAL_LOGIC
|
||||
|
||||
local NEST_BASE = constants.NEST_BASE
|
||||
|
||||
-- imported functions
|
||||
|
||||
local getNeighborChunksWithDirection = mapUtils.getNeighborChunksWithDirection
|
||||
@ -45,12 +45,12 @@ end
|
||||
|
||||
local function scoreRetreatLocation(position, squad, neighborChunk, surface)
|
||||
local safeScore = -neighborChunk[BASE_PHEROMONE] + neighborChunk[MOVEMENT_PHEROMONE]
|
||||
local dangerScore = surface.get_pollution(position) + (neighborChunk[PLAYER_PHEROMONE] * 100) + (neighborChunk[ENEMY_BASE_GENERATOR] * 50)
|
||||
local dangerScore = surface.get_pollution(position) + (neighborChunk[PLAYER_PHEROMONE] * 100) --+ (neighborChunk[ENEMY_BASE_GENERATOR] * 50)
|
||||
return safeScore - dangerScore
|
||||
end
|
||||
|
||||
function aiDefense.retreatUnits(chunk, squad, regionMap, surface, natives, tick)
|
||||
if (tick - chunk[RETREAT_TRIGGERED] > INTERVAL_LOGIC) and (chunk[ENEMY_BASE_GENERATOR] == 0) then
|
||||
if (tick - chunk[RETREAT_TRIGGERED] > INTERVAL_LOGIC) and (#chunk[NEST_BASE] == 0) then
|
||||
local performRetreat = false
|
||||
local enemiesToSquad
|
||||
|
||||
|
22
libs/AIPredicates.lua
Normal file
22
libs/AIPredicates.lua
Normal file
@ -0,0 +1,22 @@
|
||||
local aiPredicates = {}
|
||||
|
||||
-- imports
|
||||
|
||||
local constants = require("Constants")
|
||||
local nocturnalUtils = require("NocturnalUtils")
|
||||
|
||||
-- constants
|
||||
|
||||
local AI_STATE_AGGRESSIVE = constants.AI_STATE_AGGRESSIVE
|
||||
|
||||
-- imported functions
|
||||
|
||||
local canAttackNocturnal = nocturnalUtils.canAttack
|
||||
|
||||
-- module code
|
||||
|
||||
function aiPredicates.canAttack(natives, surface)
|
||||
return (natives.state == AI_STATE_AGGRESSIVE) or canAttackNocturnal(natives, surface)
|
||||
end
|
||||
|
||||
return aiPredicates
|
39
libs/BaseProcessor.lua
Normal file
39
libs/BaseProcessor.lua
Normal file
@ -0,0 +1,39 @@
|
||||
local baseProcessor = {}
|
||||
|
||||
-- imports
|
||||
|
||||
local baseUtils = require("BaseUtils")
|
||||
local constants = require("Constants")
|
||||
|
||||
-- constants
|
||||
|
||||
local BASE_QUEUE_SIZE = constants.BASE_QUEUE_SIZE
|
||||
|
||||
-- imported functions
|
||||
|
||||
local mMin = math.min
|
||||
|
||||
local buildOrder = baseUtils.buildOrder
|
||||
|
||||
-- module code
|
||||
|
||||
function baseProcessor.processBases(regionMap, surface, natives, tick)
|
||||
local baseIndex = natives.baseIndex
|
||||
local bases = natives.bases
|
||||
|
||||
local endIndex = mMin(baseIndex+BASE_QUEUE_SIZE, #bases)
|
||||
for index = baseIndex, endIndex do
|
||||
local base = bases[index]
|
||||
|
||||
buildOrder(regionMap, natives, base, surface, tick)
|
||||
end
|
||||
|
||||
if (endIndex == #bases) then
|
||||
natives.baseIndex = 1
|
||||
else
|
||||
natives.baseIndex = endIndex + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return baseProcessor
|
135
libs/BaseUtils.lua
Normal file
135
libs/BaseUtils.lua
Normal file
@ -0,0 +1,135 @@
|
||||
local baseUtils = {}
|
||||
|
||||
-- imports
|
||||
|
||||
local mapUtils = require("MapUtils")
|
||||
local constants = require("Constants")
|
||||
local mathUtils = require("MathUtils")
|
||||
local entityUtils = require("EntityUtils")
|
||||
|
||||
-- constants
|
||||
|
||||
local BASE_DISTANCE_THRESHOLD = constants.BASE_DISTANCE_THRESHOLD
|
||||
|
||||
local BASE_ALIGNMENT_NEUTRAL = constants.BASE_ALIGNMENT_NEUTRAL
|
||||
|
||||
local AI_NEST_COST = constants.AI_NEST_COST
|
||||
local AI_WORM_COST = constants.AI_WORM_COST
|
||||
|
||||
local CHUNK_SIZE = constants.CHUNK_SIZE
|
||||
|
||||
local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER
|
||||
local MAGIC_MAXIMUM_BASE_NUMBER = constants.MAGIC_MAXIMUM_BASE_NUMBER
|
||||
|
||||
-- imported functions
|
||||
|
||||
local euclideanDistancePoints = mapUtils.euclideanDistancePoints
|
||||
|
||||
local gaussianRandomRange = mathUtils.gaussianRandomRange
|
||||
|
||||
local addEnemyBase = entityUtils.addEnemyBase
|
||||
|
||||
-- module code
|
||||
|
||||
function baseUtils.annexNest(natives, position)
|
||||
local bases = natives.bases
|
||||
local annex = nil
|
||||
local closest = MAGIC_MAXIMUM_NUMBER
|
||||
for i=1,#bases do
|
||||
local base = bases[i]
|
||||
local distance = euclideanDistancePoints(base.x, base.y, position.x, position.y)
|
||||
if (distance <= BASE_DISTANCE_THRESHOLD) and (distance < closest) then
|
||||
closest = distance
|
||||
annex = base
|
||||
end
|
||||
end
|
||||
return annex
|
||||
end
|
||||
|
||||
function baseUtils.buildHive(regionMap, base, surface)
|
||||
local valid = false
|
||||
local position = surface.find_non_colliding_position("biter-spawner", {x=base.x, y=base.y}, 2*CHUNK_SIZE, 10)
|
||||
if position then
|
||||
local biterSpawner = {name="biter-spawner", position=position}
|
||||
base.hive = surface.create_entity(biterSpawner)
|
||||
addEnemyBase(regionMap, base.hive, base)
|
||||
valid = true
|
||||
end
|
||||
return valid
|
||||
end
|
||||
|
||||
function baseUtils.buildOutpost(natives, base, surface, tick, position)
|
||||
|
||||
end
|
||||
|
||||
function baseUtils.buildTendril(natives, base, surface, tick, startPosition, endPosition)
|
||||
|
||||
end
|
||||
|
||||
function baseUtils.buildOrder(regionMap, natives, base, surface, tick)
|
||||
if not base.hive or (base.upgradePoints < 10) then
|
||||
return
|
||||
end
|
||||
|
||||
local generator = natives.randomGenerator
|
||||
generator.re_seed(base.pattern)
|
||||
|
||||
for level=0,base.level do
|
||||
local slices = (level * 3)
|
||||
local slice = (2 * math.pi) / slices
|
||||
local pos = 0
|
||||
local thing
|
||||
local cost
|
||||
local radiusAdjustment
|
||||
if (generator() < 0.3) then
|
||||
thing = "small-worm-turret"
|
||||
cost = AI_WORM_COST
|
||||
radiusAdjustment = -4
|
||||
else
|
||||
thing = "biter-spawner"
|
||||
cost = AI_NEST_COST
|
||||
radiusAdjustment = 0
|
||||
end
|
||||
for _ = 1, slices do
|
||||
if (base.upgradePoints < 10) then
|
||||
return
|
||||
end
|
||||
local radius = 10 * level
|
||||
local distortion = gaussianRandomRange(radius, 10, radius - 7.5 + radiusAdjustment, radius + 7.5 + radiusAdjustment, generator)
|
||||
local nestPosition = {x = base.x + (distortion * math.cos(pos)),
|
||||
y = base.y + (distortion * math.sin(pos))}
|
||||
local biterSpawner = {name=thing, position=nestPosition}
|
||||
if surface.can_place_entity(biterSpawner) then
|
||||
addEnemyBase(regionMap, surface.create_entity(biterSpawner), base)
|
||||
base.upgradePoints = base.upgradePoints - cost
|
||||
end
|
||||
pos = pos + slice
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function baseUtils.createBase(regionMap, natives, position, surface, tick)
|
||||
local bases = natives.bases
|
||||
local base = {
|
||||
x = position.x,
|
||||
y = position.y,
|
||||
created = tick,
|
||||
alignment = { BASE_ALIGNMENT_NEUTRAL },
|
||||
hive = nil,
|
||||
nests = {},
|
||||
worms = {},
|
||||
eggs = {},
|
||||
upgradePoints = 0,
|
||||
growth = tick,
|
||||
pattern = math.random(MAGIC_MAXIMUM_BASE_NUMBER),
|
||||
level = 3
|
||||
}
|
||||
if not baseUtils.buildHive(regionMap, base, surface) then
|
||||
return nil
|
||||
end
|
||||
bases[#bases+1] = base
|
||||
return base
|
||||
end
|
||||
|
||||
return baseUtils
|
||||
|
@ -12,7 +12,7 @@ local scoreChunk = chunkUtils.scoreChunk
|
||||
|
||||
-- module code
|
||||
|
||||
function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack)
|
||||
function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack, tick)
|
||||
local processQueue = regionMap.processQueue
|
||||
|
||||
for _=#pendingStack, 1, -1 do
|
||||
@ -29,7 +29,7 @@ function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendin
|
||||
regionMap[chunkX][chunk.cY] = chunk
|
||||
|
||||
checkChunkPassability(chunk, surface)
|
||||
scoreChunk(natives, chunk, surface)
|
||||
scoreChunk(regionMap, chunk, surface, natives, tick)
|
||||
processQueue[#processQueue+1] = chunk
|
||||
end
|
||||
end
|
||||
|
@ -4,15 +4,18 @@ local chunkUtils = {}
|
||||
|
||||
local constants = require("Constants")
|
||||
|
||||
local baseUtils = require("BaseUtils")
|
||||
|
||||
-- constants
|
||||
|
||||
local BASE_PHEROMONE = constants.BASE_PHEROMONE
|
||||
local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE
|
||||
local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
|
||||
local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES
|
||||
local NEST_BASE = constants.NEST_BASE
|
||||
local WORM_BASE = constants.WORM_BASE
|
||||
|
||||
local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR
|
||||
local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR
|
||||
|
||||
local NORTH_SOUTH = constants.NORTH_SOUTH
|
||||
local EAST_WEST = constants.EAST_WEST
|
||||
@ -20,16 +23,19 @@ local EAST_WEST = constants.EAST_WEST
|
||||
local EAST_WEST_PASSABLE = constants.EAST_WEST_PASSABLE
|
||||
local NORTH_SOUTH_PASSABLE = constants.NORTH_SOUTH_PASSABLE
|
||||
|
||||
local ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT = constants.ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT
|
||||
|
||||
local CHUNK_TICK = constants.CHUNK_TICK
|
||||
|
||||
local RETREAT_TRIGGERED = constants.RETREAT_TRIGGERED
|
||||
local RALLY_TRIGGERED = constants.RALLY_TRIGGERED
|
||||
|
||||
-- imported functions
|
||||
|
||||
local annexNest = baseUtils.annexNest
|
||||
local createBase = baseUtils.createBase
|
||||
|
||||
-- module code
|
||||
|
||||
function chunkUtils.checkForDeadendTiles(constantCoordinate, iteratingCoordinate, direction, surface)
|
||||
local function checkForDeadendTiles(constantCoordinate, iteratingCoordinate, direction, surface)
|
||||
local get_tile = surface.get_tile
|
||||
|
||||
for x=iteratingCoordinate, iteratingCoordinate + 31 do
|
||||
@ -53,13 +59,13 @@ function chunkUtils.checkChunkPassability(chunk, surface)
|
||||
local passableNorthSouth = false
|
||||
local passableEastWest = false
|
||||
for xi=x, x + 31 do
|
||||
if (not chunkUtils.checkForDeadendTiles(xi, y, NORTH_SOUTH, surface)) then
|
||||
if (not checkForDeadendTiles(xi, y, NORTH_SOUTH, surface)) then
|
||||
passableNorthSouth = true
|
||||
break
|
||||
end
|
||||
end
|
||||
for yi=y, y + 31 do
|
||||
if (not chunkUtils.checkForDeadendTiles(yi, x, EAST_WEST, surface)) then
|
||||
if (not checkForDeadendTiles(yi, x, EAST_WEST, surface)) then
|
||||
passableEastWest = true
|
||||
break
|
||||
end
|
||||
@ -69,28 +75,36 @@ function chunkUtils.checkChunkPassability(chunk, surface)
|
||||
chunk[NORTH_SOUTH_PASSABLE] = passableNorthSouth
|
||||
end
|
||||
|
||||
function chunkUtils.scoreChunk(natives, chunk, surface)
|
||||
function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, tick)
|
||||
local x = chunk.pX
|
||||
local y = chunk.pY
|
||||
|
||||
|
||||
local chunkPosition = {x=x, y=y}
|
||||
local areaBoundingBox = {
|
||||
{x, y},
|
||||
chunkPosition,
|
||||
{x + 32, y + 32}
|
||||
}
|
||||
local enemyChunkQuery = {area=areaBoundingBox,
|
||||
type="unit-spawner",
|
||||
force="enemy"}
|
||||
local enemyWormChunkQuery = {area=areaBoundingBox,
|
||||
type="turret",
|
||||
force="enemy"}
|
||||
local playerChunkQuery = {area=areaBoundingBox,
|
||||
force="player"}
|
||||
|
||||
local entities = surface.count_entities_filtered(enemyChunkQuery)
|
||||
local worms = surface.count_entities_filtered(enemyWormChunkQuery)
|
||||
local playerObjects = 0
|
||||
local enemies = surface.find_entities_filtered(enemyChunkQuery)
|
||||
|
||||
local nestsRemoved = 0
|
||||
local wormsRemoved = 0
|
||||
local bitersRemoved = 0
|
||||
|
||||
chunk[ENEMY_BASE_GENERATOR] = (entities * ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT) + worms
|
||||
for i=1, #enemies do
|
||||
local entityType = enemies[i].type
|
||||
if (entityType == "unit-spawner") then
|
||||
nestsRemoved = nestsRemoved + 3
|
||||
elseif (entityType == "turret") then
|
||||
wormsRemoved = wormsRemoved + 2
|
||||
elseif (entityType == "unit") then
|
||||
bitersRemoved = bitersRemoved + 1
|
||||
end
|
||||
end
|
||||
|
||||
entities = surface.find_entities_filtered(playerChunkQuery)
|
||||
|
||||
@ -105,10 +119,25 @@ function chunkUtils.scoreChunk(natives, chunk, surface)
|
||||
end
|
||||
end
|
||||
|
||||
local entityScore = BUILDING_PHEROMONES[entityType]
|
||||
if (entityScore ~= nil) then
|
||||
playerObjects = playerObjects + entityScore
|
||||
end
|
||||
if (nestsRemoved > 0) or (wormsRemoved > 0) or (bitersRemoved > 0) then
|
||||
for i=1, #enemies do
|
||||
enemies[i].destroy()
|
||||
end
|
||||
local foundBase = annexNest(natives, chunkPosition) or createBase(regionMap, natives, chunkPosition, surface, tick)
|
||||
if foundBase then
|
||||
foundBase.upgradePoints = foundBase.upgradePoints + nestsRemoved + wormsRemoved + bitersRemoved
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local playerObjects = 0
|
||||
local entities = surface.find_entities_filtered(playerChunkQuery)
|
||||
|
||||
for i=1, #entities do
|
||||
local entityScore = BUILDING_PHEROMONES[entities[i].type]
|
||||
if (entityScore ~= nil) then
|
||||
playerObjects = playerObjects + entityScore
|
||||
end
|
||||
end
|
||||
|
||||
chunk[PLAYER_BASE_GENERATOR] = playerObjects
|
||||
@ -124,13 +153,14 @@ function chunkUtils.createChunk(topX, topY)
|
||||
chunk[MOVEMENT_PHEROMONE] = 0
|
||||
chunk[BASE_PHEROMONE] = 0
|
||||
chunk[PLAYER_PHEROMONE] = 0
|
||||
chunk[ENEMY_BASE_GENERATOR] = 0
|
||||
chunk[PLAYER_BASE_GENERATOR] = 0
|
||||
chunk[NORTH_SOUTH_PASSABLE] = false
|
||||
chunk[EAST_WEST_PASSABLE] = false
|
||||
chunk[CHUNK_TICK] = 0
|
||||
chunk[RETREAT_TRIGGERED] = 0
|
||||
chunk[RALLY_TRIGGERED] = 0
|
||||
chunk[NEST_BASE] = {}
|
||||
chunk[WORM_BASE] = {}
|
||||
return chunk
|
||||
end
|
||||
|
||||
|
@ -21,10 +21,12 @@ constants.VERSION_22 = 22
|
||||
-- misc
|
||||
|
||||
constants.MAGIC_MAXIMUM_NUMBER = 1e99 -- used in loops trying to find the lowest/highest score
|
||||
constants.MAGIC_MAXIMUM_BASE_NUMBER = 100000000
|
||||
constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL = 10000
|
||||
|
||||
constants.PROCESS_QUEUE_SIZE = 500
|
||||
constants.SCAN_QUEUE_SIZE = 6
|
||||
constants.BASE_QUEUE_SIZE = 10
|
||||
constants.PROCESS_PLAYER_BOUND = 4
|
||||
|
||||
constants.TICKS_A_SECOND = 60
|
||||
@ -33,11 +35,21 @@ constants.TICKS_A_MINUTE = constants.TICKS_A_SECOND * 60
|
||||
constants.INTERVAL_PROCESS = 19
|
||||
constants.INTERVAL_LOGIC = 38
|
||||
|
||||
-- chunk properties
|
||||
|
||||
constants.CHUNK_SIZE = 32
|
||||
constants.HALF_CHUNK_SIZE = constants.CHUNK_SIZE / 2
|
||||
constants.QUARTER_CHUNK_SIZE = constants.HALF_CHUNK_SIZE / 2
|
||||
constants.NORTH_SOUTH = 1
|
||||
constants.EAST_WEST = 2
|
||||
|
||||
-- ai
|
||||
|
||||
constants.AI_POINT_GENERATOR_AMOUNT = 6
|
||||
constants.AI_SCOUT_COST = 45
|
||||
constants.AI_SQUAD_COST = 175
|
||||
constants.AI_NEST_COST = 10
|
||||
constants.AI_WORM_COST = 2
|
||||
constants.AI_VENGENCE_SQUAD_COST = 45
|
||||
constants.AI_SETTLER_COST = 75
|
||||
constants.AI_BASE_BUILDING_COST = 500
|
||||
@ -58,19 +70,23 @@ constants.AI_MAX_STATE_DURATION = 4
|
||||
constants.AI_MIN_TEMPERAMENT_DURATION = 5
|
||||
constants.AI_MAX_TEMPERAMENT_DURATION = 15
|
||||
|
||||
-- ai base
|
||||
|
||||
constants.BASE_DISTANCE_THRESHOLD = 15 * constants.CHUNK_SIZE
|
||||
|
||||
constants.BASE_ALIGNMENT_NEUTRAL = 1
|
||||
constants.BASE_ALIGNMENT_FIRE = 2
|
||||
constants.BASE_ALIGNMENT_BURROW = 3
|
||||
constants.BASE_ALIGNMENT_SUICIDE = 4
|
||||
constants.BASE_ALIGNMENT_INFEST = 5
|
||||
|
||||
|
||||
-- ai retreat
|
||||
|
||||
constants.NO_RETREAT_BASE_PERCENT = 0.10
|
||||
constants.NO_RETREAT_EVOLUTION_BONUS_MAX = 0.25
|
||||
constants.NO_RETREAT_SQUAD_SIZE_BONUS_MAX = 0.40
|
||||
|
||||
-- chunk properties
|
||||
|
||||
constants.CHUNK_SIZE = 32
|
||||
constants.HALF_CHUNK_SIZE = constants.CHUNK_SIZE / 2
|
||||
constants.QUARTER_CHUNK_SIZE = constants.HALF_CHUNK_SIZE / 2
|
||||
constants.NORTH_SOUTH = 1
|
||||
constants.EAST_WEST = 2
|
||||
|
||||
-- pheromone amounts
|
||||
|
||||
@ -92,8 +108,8 @@ constants.PLAYER_PHEROMONE_PERSISTANCE = 0.98
|
||||
constants.MOVEMENT_PHEROMONE = 1
|
||||
constants.BASE_PHEROMONE = 2
|
||||
constants.PLAYER_PHEROMONE = 3
|
||||
constants.RESOURCE_PHEROMONE = 4
|
||||
|
||||
constants.ENEMY_BASE_GENERATOR = 4
|
||||
constants.PLAYER_BASE_GENERATOR = 5
|
||||
|
||||
constants.NORTH_SOUTH_PASSABLE = 6
|
||||
@ -102,6 +118,8 @@ constants.EAST_WEST_PASSABLE = 7
|
||||
constants.CHUNK_TICK = 8
|
||||
constants.RETREAT_TRIGGERED = 9
|
||||
constants.RALLY_TRIGGERED = 10
|
||||
constants.NEST_BASE = 11
|
||||
constants.WORM_BASE = 12
|
||||
|
||||
-- Squad status
|
||||
|
||||
|
@ -9,10 +9,10 @@ local constants = require("Constants")
|
||||
|
||||
local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES
|
||||
|
||||
local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR
|
||||
local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR
|
||||
|
||||
local ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT = constants.ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT
|
||||
local NEST_BASE = constants.NEST_BASE
|
||||
local WORM_BASE = constants.WORM_BASE
|
||||
|
||||
local DEFINES_DIRECTION_EAST = defines.direction.east
|
||||
local DEFINES_WIRE_TYPE_RED = defines.wire_type.red
|
||||
@ -82,39 +82,104 @@ local function getEntityOverlapChunks(regionMap, entity)
|
||||
return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk
|
||||
end
|
||||
|
||||
function entityUtils.addRemoveEntity(regionMap, entity, natives, addObject, creditNatives)
|
||||
function entityUtils.addRemovePlayerEntity(regionMap, entity, natives, addObject, creditNatives)
|
||||
local leftTop, rightTop, leftBottom, rightBottom
|
||||
local entityValue
|
||||
local pheromoneType
|
||||
if (BUILDING_PHEROMONES[entity.type] ~= nil) and (entity.force.name == "player") then
|
||||
entityValue = BUILDING_PHEROMONES[entity.type]
|
||||
pheromoneType = PLAYER_BASE_GENERATOR
|
||||
elseif (entity.type == "unit-spawner") and (entity.force.name == "enemy") then
|
||||
entityValue = ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT
|
||||
pheromoneType = ENEMY_BASE_GENERATOR
|
||||
elseif (entity.type == "turret") and (entity.force.name == "enemy") then
|
||||
entityValue = 1
|
||||
pheromoneType = ENEMY_BASE_GENERATOR
|
||||
end
|
||||
if (entityValue ~= nil) then
|
||||
|
||||
leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity)
|
||||
if not addObject then
|
||||
if creditNatives and (pheromoneType ~= ENEMY_BASE_GENERATOR) then
|
||||
natives.points = natives.points + entityValue
|
||||
end
|
||||
entityValue = -entityValue
|
||||
if creditNatives then
|
||||
natives.points = natives.points + entityValue
|
||||
end
|
||||
entityValue = -entityValue
|
||||
end
|
||||
if (leftTop ~= nil) then
|
||||
leftTop[PLAYER_BASE_GENERATOR] = leftTop[PLAYER_BASE_GENERATOR] + entityValue
|
||||
end
|
||||
if (rightTop ~= nil) then
|
||||
rightTop[PLAYER_BASE_GENERATOR] = rightTop[PLAYER_BASE_GENERATOR] + entityValue
|
||||
end
|
||||
if (leftBottom ~= nil) then
|
||||
leftBottom[PLAYER_BASE_GENERATOR] = leftBottom[PLAYER_BASE_GENERATOR] + entityValue
|
||||
end
|
||||
if (rightBottom ~= nil) then
|
||||
rightBottom[PLAYER_BASE_GENERATOR] = rightBottom[PLAYER_BASE_GENERATOR] + entityValue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function addBaseToChunk(chunk, entity, base)
|
||||
local indexChunk
|
||||
local indexBase
|
||||
if (entity.type == "unit-spawner") then
|
||||
indexChunk = chunk[NEST_BASE]
|
||||
indexBase = base.nests
|
||||
elseif (entity.type == "turret") then
|
||||
indexChunk = chunk[WORM_BASE]
|
||||
indexBase = base.worms
|
||||
end
|
||||
indexChunk[entity.unit_number] = base
|
||||
indexBase[entity.unit_number] = entity
|
||||
end
|
||||
|
||||
local function removeBaseFromChunk(chunk, entity)
|
||||
local indexChunk
|
||||
if (entity.type == "unit-spawner") then
|
||||
indexChunk = chunk[NEST_BASE]
|
||||
elseif (entity.type == "turret") then
|
||||
indexChunk = chunk[WORM_BASE]
|
||||
end
|
||||
local base = indexChunk[entity.unit_number]
|
||||
local indexBase
|
||||
if base then
|
||||
if (entity.type == "unit-spawner") then
|
||||
indexBase = base.nests
|
||||
elseif (entity.type == "turret") then
|
||||
indexBase = base.worms
|
||||
end
|
||||
indexBase[entity.unit_number] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function entityUtils.addEnemyBase(regionMap, entity, base)
|
||||
local entityType = entity.type
|
||||
if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then
|
||||
local leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity)
|
||||
|
||||
if (leftTop ~= nil) then
|
||||
leftTop[pheromoneType] = leftTop[pheromoneType] + entityValue
|
||||
addBaseToChunk(leftTop, entity, base)
|
||||
end
|
||||
if (rightTop ~= nil) then
|
||||
rightTop[pheromoneType] = rightTop[pheromoneType] + entityValue
|
||||
addBaseToChunk(rightTop, entity, base)
|
||||
end
|
||||
if (leftBottom ~= nil) then
|
||||
leftBottom[pheromoneType] = leftBottom[pheromoneType] + entityValue
|
||||
addBaseToChunk(leftBottom, entity, base)
|
||||
end
|
||||
if (rightBottom ~= nil) then
|
||||
rightBottom[pheromoneType] = rightBottom[pheromoneType] + entityValue
|
||||
addBaseToChunk(rightBottom, entity, base)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function entityUtils.removeEnemyBase(regionMap, entity)
|
||||
local entityType = entity.type
|
||||
if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then
|
||||
local leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity)
|
||||
|
||||
if (leftTop ~= nil) then
|
||||
removeBaseFromChunk(leftTop, entity)
|
||||
end
|
||||
if (rightTop ~= nil) then
|
||||
removeBaseFromChunk(rightTop, entity)
|
||||
end
|
||||
if (leftBottom ~= nil) then
|
||||
removeBaseFromChunk(leftBottom, entity)
|
||||
end
|
||||
if (rightBottom ~= nil) then
|
||||
removeBaseFromChunk(rightBottom, entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,14 +4,14 @@ local mapProcessor = {}
|
||||
|
||||
local pheromoneUtils = require("PheromoneUtils")
|
||||
local aiBuilding = require("AIBuilding")
|
||||
local aiPredicates = require("AIPredicates")
|
||||
local constants = require("Constants")
|
||||
local mapUtils = require("MapUtils")
|
||||
local nocturnalUtils = require("NocturnalUtils")
|
||||
local playerUtils = require("PlayerUtils")
|
||||
|
||||
-- constants
|
||||
|
||||
local PROCESS_QUEUE_SIZE = constants.PROCESS_QUEUE_SIZE
|
||||
local ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT = constants.ENEMY_BASE_PHEROMONE_GENERATOR_AMOUNT
|
||||
|
||||
local RETREAT_MOVEMENT_PHEROMONE_LEVEL = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL
|
||||
|
||||
@ -19,12 +19,12 @@ local SCAN_QUEUE_SIZE = constants.SCAN_QUEUE_SIZE
|
||||
|
||||
local AI_UNIT_REFUND = constants.AI_UNIT_REFUND
|
||||
local CHUNK_SIZE = constants.CHUNK_SIZE
|
||||
local ENEMY_BASE_GENERATOR = constants.ENEMY_BASE_GENERATOR
|
||||
local AI_STATE_AGGRESSIVE = constants.AI_STATE_AGGRESSIVE
|
||||
|
||||
local PROCESS_PLAYER_BOUND = constants.PROCESS_PLAYER_BOUND
|
||||
local CHUNK_TICK = constants.CHUNK_TICK
|
||||
|
||||
local NEST_BASE = constants.NEST_BASE
|
||||
|
||||
local AI_MAX_POINTS = constants.AI_MAX_POINTS
|
||||
local AI_SQUAD_COST = constants.AI_SQUAD_COST
|
||||
local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST
|
||||
@ -45,12 +45,12 @@ local getChunkByPosition = mapUtils.getChunkByPosition
|
||||
|
||||
local playerScent = pheromoneUtils.playerScent
|
||||
|
||||
local euclideanDistanceNamed = mapUtils.euclideanDistanceNamed
|
||||
|
||||
local canAttackNocturnal = nocturnalUtils.canAttack
|
||||
local canAttack = aiPredicates.canAttack
|
||||
|
||||
local mMin = math.min
|
||||
|
||||
local validPlayer = playerUtils.validPlayer
|
||||
|
||||
-- module code
|
||||
|
||||
local function nonRepeatingRandom(players)
|
||||
@ -83,7 +83,7 @@ function mapProcessor.processMap(regionMap, surface, natives, evolution_factor)
|
||||
regionMap.processRoll = roll
|
||||
end
|
||||
|
||||
local squads = ((natives.state == AI_STATE_AGGRESSIVE) or canAttackNocturnal(natives, surface)) and (0.11 <= roll) and (roll <= 0.35)
|
||||
local squads = canAttack(natives, surface) and (0.11 <= roll) and (roll <= 0.35)
|
||||
|
||||
local processQueue = regionMap.processQueue
|
||||
local endIndex = mMin(index + PROCESS_QUEUE_SIZE, #processQueue)
|
||||
@ -120,11 +120,11 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, evolu
|
||||
local vengenceThreshold = -(evolution_factor * RETREAT_MOVEMENT_PHEROMONE_LEVEL)
|
||||
local roll = math.random()
|
||||
|
||||
local squads = ((natives.state == AI_STATE_AGGRESSIVE) or canAttackNocturnal(natives, surface)) and (0.11 <= roll) and (roll <= 0.20)
|
||||
local squads = canAttack(natives, surface) and (0.11 <= roll) and (roll <= 0.20)
|
||||
|
||||
for i=1,#playerOrdering do
|
||||
local player = players[playerOrdering[i]]
|
||||
if (player ~= nil) and player.connected and (player.character ~= nil) and player.character.valid and (player.character.surface.index == 1) then
|
||||
if validPlayer(player) then
|
||||
local playerPosition = player.character.position
|
||||
local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y)
|
||||
|
||||
@ -135,13 +135,13 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, evolu
|
||||
end
|
||||
for i=1,#playerOrdering do
|
||||
local player = players[playerOrdering[i]]
|
||||
if (player ~= nil) and player.connected and (player.character ~= nil) and player.character.valid and (player.character.surface.index == 1) then
|
||||
if validPlayer(player) then
|
||||
local playerPosition = player.character.position
|
||||
local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y)
|
||||
|
||||
if playerChunk then
|
||||
local vengence = ((playerChunk[ENEMY_BASE_GENERATOR] ~= 0) or (playerChunk[MOVEMENT_PHEROMONE] < vengenceThreshold)) and
|
||||
(natives.state == AI_STATE_AGGRESSIVE or canAttackNocturnal(natives, surface))
|
||||
local vengence = canAttack(natives, surface) and ((#playerChunk[NEST_BASE] ~= 0) or (playerChunk[MOVEMENT_PHEROMONE] < vengenceThreshold))
|
||||
|
||||
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)
|
||||
@ -172,7 +172,7 @@ end
|
||||
Passive scan to find entities that have been generated outside the factorio event system
|
||||
--]]
|
||||
function mapProcessor.scanMap(regionMap, surface, natives, evolution_factor)
|
||||
local index = regionMap.scanPointer
|
||||
-- local index = regionMap.scanPointer
|
||||
|
||||
local chunkPosition = {x=0,y=0}
|
||||
local chunkBox = {chunkPosition,
|
||||
|
@ -163,6 +163,12 @@ function mapUtils.euclideanDistanceNamed(p1, p2)
|
||||
return ((xs * xs) + (ys * ys)) ^ 0.5
|
||||
end
|
||||
|
||||
function mapUtils.euclideanDistancePoints(x1, y1, x2, y2)
|
||||
local xs = x1 - x2
|
||||
local ys = y1 - y2
|
||||
return ((xs * xs) + (ys * ys)) ^ 0.5
|
||||
end
|
||||
|
||||
function mapUtils.euclideanDistanceArray(p1, p2)
|
||||
local xs = p1[1] - p2[1]
|
||||
local ys = p1[2] - p2[2]
|
||||
|
@ -30,27 +30,32 @@ end
|
||||
--[[
|
||||
Used for gaussian random numbers
|
||||
--]]
|
||||
local function marsagliaPolarMethod()
|
||||
local function marsagliaPolarMethod(rg)
|
||||
local iid1
|
||||
local iid2
|
||||
local q
|
||||
repeat
|
||||
iid1 = 2 * math.random() + -1
|
||||
iid2 = 2 * math.random() + -1
|
||||
if rg then
|
||||
iid1 = 2 * rg() + -1
|
||||
iid2 = 2 * rg() + -1
|
||||
else
|
||||
iid1 = 2 * math.random() + -1
|
||||
iid2 = 2 * math.random() + -1
|
||||
end
|
||||
q = (iid1 * iid1) + (iid2 * iid2)
|
||||
until (q ~= 0) and (q < 1)
|
||||
local s = mSqrt((-2 * mLog10(q)) / q)
|
||||
return iid1 * s
|
||||
end
|
||||
|
||||
function mathUtils.gaussianRandom(mean, std_dev)
|
||||
return mean + (marsagliaPolarMethod() * std_dev)
|
||||
function mathUtils.gaussianRandom(mean, std_dev, rg)
|
||||
return mean + (marsagliaPolarMethod(rg) * std_dev)
|
||||
end
|
||||
|
||||
function mathUtils.gaussianRandomRange(mean, std_dev, min, max)
|
||||
function mathUtils.gaussianRandomRange(mean, std_dev, min, max, rg)
|
||||
local q
|
||||
repeat
|
||||
q = mathUtils.gaussianRandom(mean, std_dev)
|
||||
q = mathUtils.gaussianRandom(mean, std_dev, rg)
|
||||
until (q >= min) and (q <= max)
|
||||
return q
|
||||
end
|
||||
|
@ -10,6 +10,10 @@ local euclideanDistanceNamed = mapUtils.euclideanDistanceNamed
|
||||
|
||||
-- module code
|
||||
|
||||
function playerUtils.validPlayer(player)
|
||||
return (player ~= nil) and player.connected and (player.character ~= nil) and player.character.valid and (player.character.surface.index == 1)
|
||||
end
|
||||
|
||||
function playerUtils.playersWithinProximityToPosition(players, position, distance)
|
||||
for _,player in pairs(players) do
|
||||
if (player ~= nil) and player.connected and (player.character ~= nil) and player.character.valid and (player.character.surface.index == 1) then
|
||||
|
190
prototypes/buildings/UnitSpawners.lua
Normal file
190
prototypes/buildings/UnitSpawners.lua
Normal file
@ -0,0 +1,190 @@
|
||||
|
||||
function spawner_idle_animation(variation, tint)
|
||||
return
|
||||
{
|
||||
layers =
|
||||
{
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-idle.png",
|
||||
line_length = 8,
|
||||
width = 243,
|
||||
height = 181,
|
||||
frame_count = 8,
|
||||
animation_speed = 0.18,
|
||||
direction_count = 1,
|
||||
run_mode = "forward-then-backward",
|
||||
shift = {0.140625 - 0.65, -0.234375},
|
||||
y = variation * 181
|
||||
},
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-idle-mask.png",
|
||||
flags = { "mask" },
|
||||
width = 166,
|
||||
height = 148,
|
||||
frame_count = 8,
|
||||
animation_speed = 0.18,
|
||||
run_mode = "forward-then-backward",
|
||||
shift = {-0.34375 - 0.65, -0.375},
|
||||
line_length = 8,
|
||||
tint = tint,
|
||||
y = variation * 148
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function spawner_die_animation(variation, tint)
|
||||
return
|
||||
{
|
||||
layers =
|
||||
{
|
||||
{
|
||||
width = 255,
|
||||
height = 184,
|
||||
frame_count = 20,
|
||||
direction_count = 1,
|
||||
shift = {-0.015625 - 0.65, -0.28125},
|
||||
stripes =
|
||||
{
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-die-01.png",
|
||||
width_in_frames = 7,
|
||||
height_in_frames = 4,
|
||||
y = variation * 184
|
||||
},
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-die-02.png",
|
||||
width_in_frames = 7,
|
||||
height_in_frames = 4,
|
||||
y = variation * 184
|
||||
},
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-die-03.png",
|
||||
width_in_frames = 6,
|
||||
height_in_frames = 4,
|
||||
y = variation * 184
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
flags = { "mask" },
|
||||
width = 166,
|
||||
height = 148,
|
||||
frame_count = 20,
|
||||
direction_count = 1,
|
||||
shift = {-0.34375 - 0.65, -0.375},
|
||||
tint = tint,
|
||||
stripes =
|
||||
{
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-die-mask-01.png",
|
||||
width_in_frames = 10,
|
||||
height_in_frames = 4,
|
||||
y = variation * 148
|
||||
},
|
||||
{
|
||||
filename = "__base__/graphics/entity/spawner/spawner-die-mask-02.png",
|
||||
width_in_frames = 10,
|
||||
height_in_frames = 4,
|
||||
y = variation * 148
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local biter_spawner_powered_tint = {r=1.0, g=1.0, b=1.0, a=1.0}
|
||||
|
||||
data:extend({
|
||||
|
||||
|
||||
{
|
||||
type = "unit-spawner",
|
||||
name = "biter-spawner-powered",
|
||||
icon = "__base__/graphics/icons/biter-spawner.png",
|
||||
flags = {"placeable-player", "placeable-enemy", "not-repairable"},
|
||||
max_health = 350,
|
||||
order="b-b-g",
|
||||
subgroup="enemies",
|
||||
resistances =
|
||||
{
|
||||
{
|
||||
type = "physical",
|
||||
decrease = 2,
|
||||
percent = 15
|
||||
},
|
||||
{
|
||||
type = "explosion",
|
||||
decrease = 5,
|
||||
percent = 15,
|
||||
},
|
||||
{
|
||||
type = "fire",
|
||||
decrease = 3,
|
||||
percent = 60,
|
||||
}
|
||||
},
|
||||
working_sound = {
|
||||
sound =
|
||||
{
|
||||
{
|
||||
filename = "__base__/sound/creatures/spawner.ogg",
|
||||
volume = 1.0
|
||||
}
|
||||
},
|
||||
apparent_volume = 2
|
||||
},
|
||||
dying_sound =
|
||||
{
|
||||
{
|
||||
filename = "__base__/sound/creatures/spawner-death-1.ogg",
|
||||
volume = 1.0
|
||||
},
|
||||
{
|
||||
filename = "__base__/sound/creatures/spawner-death-2.ogg",
|
||||
volume = 1.0
|
||||
}
|
||||
},
|
||||
healing_per_tick = 0.02,
|
||||
collision_box = {{-3.2, -2.2}, {2.2, 2.2}},
|
||||
selection_box = {{-3.5, -2.5}, {2.5, 2.5}},
|
||||
-- in ticks per 1 pu
|
||||
pollution_absorbtion_absolute = 20,
|
||||
pollution_absorbtion_proportional = 0.01,
|
||||
corpse = "biter-spawner-corpse",
|
||||
dying_explosion = "blood-explosion-huge",
|
||||
max_count_of_owned_units = 7,
|
||||
max_friends_around_to_spawn = 5,
|
||||
animations =
|
||||
{
|
||||
spawner_idle_animation(0, biter_spawner_powered_tint),
|
||||
spawner_idle_animation(1, biter_spawner_powered_tint),
|
||||
spawner_idle_animation(2, biter_spawner_powered_tint),
|
||||
spawner_idle_animation(3, biter_spawner_powered_tint)
|
||||
},
|
||||
result_units = (function()
|
||||
local res = {}
|
||||
res[1] = {"small-biter", {{0.0, 0.3}, {0.6, 0.0}}}
|
||||
if not data.is_demo then
|
||||
-- from evolution_factor 0.3 the weight for medium-biter is linearly rising from 0 to 0.3
|
||||
-- this means for example that when the evolution_factor is 0.45 the probability of spawning
|
||||
-- a small biter is 66% while probability for medium biter is 33%.
|
||||
res[2] = {"medium-biter", {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}
|
||||
-- for evolution factor of 1 the spawning probabilities are: small-biter 0%, medium-biter 1/8, big-biter 4/8, behemoth biter 3/8
|
||||
res[3] = {"big-biter", {{0.5, 0.0}, {1.0, 0.4}}}
|
||||
res[4] = {"behemoth-biter", {{0.9, 0.0}, {1.0, 0.3}}}
|
||||
end
|
||||
return res
|
||||
end)(),
|
||||
-- With zero evolution the spawn rate is 6 seconds, with max evolution it is 2.5 seconds
|
||||
spawning_cooldown = {360, 150},
|
||||
spawning_radius = 10,
|
||||
spawning_spacing = 3,
|
||||
max_spawn_shift = 0,
|
||||
max_richness_for_spawn_shift = 100,
|
||||
call_for_help_radius = 50
|
||||
}
|
||||
|
||||
})
|
@ -173,4 +173,5 @@ data:extend({
|
||||
order = "g[modifier]-a[damage]",
|
||||
per_user = false
|
||||
}
|
||||
|
||||
})
|
||||
|
130
tests.lua
130
tests.lua
@ -2,8 +2,10 @@ local tests = {}
|
||||
|
||||
local constants = require("libs/Constants")
|
||||
local mathUtils = require("libs/MathUtils")
|
||||
local chunkUtils = require("libs/ChunkUtils")
|
||||
local baseUtils = require("libs/BaseUtils")
|
||||
|
||||
function tests.test1()
|
||||
function tests.pheromoneLevels()
|
||||
local player = game.player.character
|
||||
local playerChunkX = math.floor(player.position.x / 32)
|
||||
local playerChunkY = math.floor(player.position.y / 32)
|
||||
@ -34,18 +36,18 @@ function tests.test1()
|
||||
end
|
||||
end
|
||||
|
||||
function tests.test2()
|
||||
function tests.activeSquads()
|
||||
print("--")
|
||||
for i=1, #global.natives.squads do
|
||||
local squad = global.natives.squads[i]
|
||||
if squad.group.valid then
|
||||
print(math.floor(squad.group.position.x * 0.03125), math.floor(squad.group.position.y * 0.03125), squad.status, squad.group.state, #squad.group.members)
|
||||
print(math.floor(squad.group.position.x * 0.03125), math.floor(squad.group.position.y * 0.03125), squad.status, squad.group.state)
|
||||
print(serpent.dump(squad))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tests.test3()
|
||||
function tests.entitiesOnPlayerChunk()
|
||||
local playerPosition = game.players[1].position
|
||||
local chunkX = math.floor(playerPosition.x * 0.03125) * 32
|
||||
local chunkY = math.floor(playerPosition.y * 0.03125) * 32
|
||||
@ -58,7 +60,7 @@ function tests.test3()
|
||||
print("--")
|
||||
end
|
||||
|
||||
function tests.test4()
|
||||
function tests.findNearestPlayerEnemy()
|
||||
local playerPosition = game.players[1].position
|
||||
local chunkX = math.floor(playerPosition.x * 0.03125) * 32
|
||||
local chunkY = math.floor(playerPosition.y * 0.03125) * 32
|
||||
@ -71,11 +73,11 @@ function tests.test4()
|
||||
print("--")
|
||||
end
|
||||
|
||||
function tests.test5()
|
||||
function tests.aiStats()
|
||||
print(global.natives.points, game.tick, global.natives.state, global.natives.temperament, global.natives.stateTick, global.natives.temperamentTick)
|
||||
end
|
||||
|
||||
function tests.test6()
|
||||
function tests.fillableDirtTest()
|
||||
local playerPosition = game.players[1].position
|
||||
local chunkX = math.floor(playerPosition.x * 0.03125) * 32
|
||||
local chunkY = math.floor(playerPosition.y * 0.03125) * 32
|
||||
@ -86,22 +88,21 @@ function tests.test6()
|
||||
false)
|
||||
end
|
||||
|
||||
function tests.test7()
|
||||
function tests.tunnelTest()
|
||||
local playerPosition = game.players[1].position
|
||||
local chunkX = math.floor(playerPosition.x * 0.03125) * 32
|
||||
local chunkY = math.floor(playerPosition.y * 0.03125) * 32
|
||||
game.surfaces[1].create_entity({name="tunnel-entrance", position={chunkX, chunkY}})
|
||||
end
|
||||
|
||||
function tests.test8(x)
|
||||
function tests.createEnemy(x)
|
||||
local playerPosition = game.players[1].position
|
||||
local chunkX = math.floor(playerPosition.x * 0.03125) * 32
|
||||
local chunkY = math.floor(playerPosition.y * 0.03125) * 32
|
||||
local entity = game.surfaces[1].create_entity({name=x, force="enemy", position={chunkX, chunkY}})
|
||||
--entity.insert({ name = "flame-thrower-ammo", count = 1})
|
||||
game.surfaces[1].create_entity({name=x, position={chunkX, chunkY}})
|
||||
end
|
||||
|
||||
function tests.test9()
|
||||
function tests.attackOrigin()
|
||||
local enemy = game.surfaces[1].find_nearest_enemy({position={0,0},
|
||||
max_distance = 1000})
|
||||
if (enemy ~= nil) and enemy.valid then
|
||||
@ -112,12 +113,12 @@ function tests.test9()
|
||||
end
|
||||
end
|
||||
|
||||
function tests.test10()
|
||||
function tests.cheatMode()
|
||||
game.players[1].cheat_mode = true
|
||||
game.forces.player.research_all_technologies()
|
||||
end
|
||||
|
||||
function tests.test11()
|
||||
function tests.gaussianRandomTest()
|
||||
local result = {}
|
||||
for x=0,100,1 do
|
||||
result[x] = 0
|
||||
@ -131,4 +132,105 @@ function tests.test11()
|
||||
end
|
||||
end
|
||||
|
||||
function tests.reveal (size)
|
||||
game.player.force.chart(game.player.surface,
|
||||
{{x=-size, y=-size}, {x=size, y=size}})
|
||||
end
|
||||
|
||||
function tests.baseStats()
|
||||
local natives = global.natives
|
||||
print ("cX", "cY", "pX", "pY", "created", "align", "str", "upgradePoints", "#nest", "#worms", "#eggs", "hive")
|
||||
for i=1, #natives.bases do
|
||||
local base = natives.bases[i]
|
||||
local nestCount = 0
|
||||
local wormCount = 0
|
||||
local eggCount = 0
|
||||
for _,_ in pairs(base.nests) do
|
||||
nestCount = nestCount + 1
|
||||
end
|
||||
for _,_ in pairs(base.worms) do
|
||||
wormCount = wormCount + 1
|
||||
end
|
||||
for _,_ in pairs(base.eggs) do
|
||||
eggCount = eggCount + 1
|
||||
end
|
||||
print(base.x, base.y, base.x * 32, base.y * 32, base.created, base.alignment, base.strength, base.upgradePoints, nestCount, wormCount, eggCount, base.hive)
|
||||
end
|
||||
end
|
||||
|
||||
function tests.baseTiles()
|
||||
local natives = global.natives
|
||||
for i=1, #natives.bases do
|
||||
local base = natives.bases[i]
|
||||
-- local color = "concrete"
|
||||
-- if (i % 3 == 0) then
|
||||
-- color = "deepwater"
|
||||
-- elseif (i % 2 == 0) then
|
||||
-- color = "water"
|
||||
-- end
|
||||
-- for x=1,#base.chunks do
|
||||
-- local chunk = base.chunks[x]
|
||||
-- chunkUtils.colorChunk(chunk.pX, chunk.pY, color, game.surfaces[1])
|
||||
-- end
|
||||
chunkUtils.colorChunk(base.x, base.y, "deepwater-green", game.surfaces[1])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function tests.clearBases()
|
||||
|
||||
local surface = game.surfaces[1]
|
||||
for x=#global.natives.bases,1,-1 do
|
||||
local base = global.natives.bases[x]
|
||||
for c=1,#base.chunks do
|
||||
local chunk = base.chunks[c]
|
||||
chunkUtils.clearChunkNests(chunk, surface)
|
||||
end
|
||||
|
||||
base.chunks = {}
|
||||
|
||||
if (surface.can_place_entity({name="biter-spawner-powered", position={base.cX * 32, base.cY * 32}})) then
|
||||
surface.create_entity({name="biter-spawner-powered", position={base.cX * 32, base.cY * 32}})
|
||||
local slice = math.pi / 12
|
||||
local pos = 0
|
||||
for i=1,24 do
|
||||
if (math.random() < 0.8) then
|
||||
local distance = mathUtils.roundToNearest(mathUtils.gaussianRandomRange(45, 5, 37, 60), 1)
|
||||
if (surface.can_place_entity({name="biter-spawner", position={base.cX * 32 + (distance*math.sin(pos)), base.cY * 32 + (distance*math.cos(pos))}})) then
|
||||
if (math.random() < 0.3) then
|
||||
surface.create_entity({name="small-worm-turret", position={base.cX * 32 + (distance*math.sin(pos)), base.cY * 32 + (distance*math.cos(pos))}})
|
||||
else
|
||||
surface.create_entity({name="biter-spawner", position={base.cX * 32 + (distance*math.sin(pos)), base.cY * 32 + (distance*math.cos(pos))}})
|
||||
end
|
||||
end
|
||||
end
|
||||
pos = pos + slice
|
||||
end
|
||||
else
|
||||
table.remove(global.natives.bases, x)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tests.mergeBases()
|
||||
local natives = global.natives
|
||||
baseUtils.mergeBases(natives)
|
||||
end
|
||||
|
||||
function tests.showMovementGrid()
|
||||
local chunks = global.regionMap.processQueue
|
||||
for i=1,#chunks do
|
||||
local chunk = chunks[i]
|
||||
local color = "deepwater-green"
|
||||
if (chunk[constants.NORTH_SOUTH_PASSABLE] and chunk[constants.EAST_WEST_PASSABLE]) then
|
||||
color = "water"
|
||||
elseif chunk[constants.NORTH_SOUTH_PASSABLE] then
|
||||
color = "deepwater"
|
||||
elseif chunk[constants.EAST_WEST_PASSABLE] then
|
||||
color = "water-green"
|
||||
end
|
||||
chunkUtils.colorChunk(chunk.pX, chunk.pY, color, game.surfaces[1])
|
||||
end
|
||||
end
|
||||
|
||||
return tests
|
||||
|
Loading…
x
Reference in New Issue
Block a user