1
0
mirror of https://github.com/veden/Rampant.git synced 2025-01-07 23:01:39 +02:00
Rampant/control.lua

401 lines
14 KiB
Lua

-- imports
local entityUtils = require("libs/EntityUtils")
local mapUtils = require("libs/MapUtils")
local unitGroupUtils = require("libs/UnitGroupUtils")
local chunkProcessor = require("libs/ChunkProcessor")
local mapProcessor = require("libs/MapProcessor")
local constants = require("libs/Constants")
local pheromoneUtils = require("libs/PheromoneUtils")
local aiDefense = require("libs/AIDefense")
local aiAttack = require("libs/AIAttack")
local aiBuilding = require("libs/AIBuilding")
local aiPlanning = require("libs/AIPlanning")
local mathUtils = require("libs/MathUtils")
local interop = require("libs/Interop")
local tests = require("tests")
-- constants
local INTERVAL_LOGIC = constants.INTERVAL_LOGIC
local INTERVAL_PROCESS = constants.INTERVAL_PROCESS
local MAX_RALLY_CRIES = constants.MAX_RALLY_CRIES
local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE
local BASE_RALLY_CHANCE = constants.BASE_RALLY_CHANCE
local BONUS_RALLY_CHANCE = constants.BONUS_RALLY_CHANCE
local RETREAT_MOVEMENT_PHEROMONE_LEVEL = constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL
local CHUNK_SIZE = constants.CHUNK_SIZE
-- imported functions
local getChunkByPosition = mapUtils.getChunkByPosition
local processPendingChunks = chunkProcessor.processPendingChunks
local processMap = mapProcessor.processMap
local processPlayers = mapProcessor.processPlayers
local scanMap = mapProcessor.scanMap
local planning = aiPlanning.planning
local rallyUnits = aiBuilding.rallyUnits
local deathScent = pheromoneUtils.deathScent
local victoryScent = pheromoneUtils.victoryScent
local regroupSquads = unitGroupUtils.regroupSquads
local convertUnitGroupToSquad = unitGroupUtils.convertUnitGroupToSquad
local squadAttack = aiAttack.squadAttack
local squadBeginAttack = aiAttack.squadBeginAttack
local retreatUnits = aiDefense.retreatUnits
local addRemoveEntity = entityUtils.addRemoveEntity
--local makeImmortalEntity = entityUtils.makeImmortalEntity
local roundToNearest = mathUtils.roundToNearest
-- local references to global
local regionMap
local natives
local pendingChunks
-- hook functions
local function onLoad()
regionMap = global.regionMap
natives = global.natives
pendingChunks = global.pendingChunks
end
local function onChunkGenerated(event)
-- queue generated chunk for delayed processing, queuing is required because some mods (RSO) mess with chunk as they
-- are generated, which messes up the scoring.
if (event.surface.index == 1) then
pendingChunks[#pendingChunks+1] = event
end
end
local function onModSettingsChange(event)
if event and (string.sub(event.setting, 1, 7) ~= "rampant") then
return
end
natives.safeBuildings = settings.global["rampant-safeBuildings"].value
natives.safeEntities["curved-rail"] = settings.global["rampant-safeBuildings-curvedRail"].value
natives.safeEntities["straight-rail"] = settings.global["rampant-safeBuildings-straightRail"].value
natives.safeEntities["rail-signal"] = settings.global["rampant-safeBuildings-railSignals"].value
natives.safeEntities["rail-chain-signal"] = settings.global["rampant-safeBuildings-railChainSignals"].value
natives.safeEntities["train-stop"] = settings.global["rampant-safeBuildings-trainStops"].value
natives.safeEntityName["big-electric-pole"] = settings.global["rampant-safeBuildings-bigElectricPole"].value
natives.attackUsePlayer = settings.global["rampant-attackWaveGenerationUsePlayerProximity"].value
natives.attackUsePollution = settings.global["rampant-attackWaveGenerationUsePollution"].value
natives.attackThresholdMin = settings.global["rampant-attackWaveGenerationThresholdMin"].value
natives.attackThresholdMax = settings.global["rampant-attackWaveGenerationThresholdMax"].value
natives.attackThresholdRange = natives.attackThresholdMax - natives.attackThresholdMin
natives.attackWaveMaxSize = settings.global["rampant-attackWaveMaxSize"].value
natives.attackPlayerThreshold = settings.global["rampant-attackPlayerThreshold"].value
natives.aiNocturnalMode = settings.global["rampant-permanentNocturnal"].value
natives.aiPointsScaler = settings.global["rampant-aiPointsScaler"].value
end
local function onConfigChanged()
if (global.version == nil) then
-- removed in version 9
-- regionMap.pQ = {{}} -- processing queue
-- regionMap.pI = 1 -- insertion location for chunk processing
-- regionMap.pP = 1 -- index for the chunk set to process
-- regionMap.pR = -1 -- current processing roll
natives.squads = {}
natives.scouts = {}
natives.tunnels = {}
natives.points = 0
pendingChunks = {}
global.version = constants.VERSION_5
end
if (global.version < constants.VERSION_9) then
-- remove version 5 references
regionMap.pQ = nil
regionMap.pI = nil
regionMap.pP = nil
regionMap.pR = nil
global.version = constants.VERSION_9
end
if (global.version < constants.VERSION_10) then
for _,squad in pairs(natives.squads) do
squad.frenzy = false
squad.frenzyPosition = {x=0,y=0}
squad.rabid = false
end
global.version = constants.VERSION_10
end
if (global.version < constants.VERSION_11) then
natives.state = constants.AI_STATE_AGGRESSIVE
natives.temperament = 0
-- needs to be on inner logic tick loop interval
natives.stateTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
natives.temperamentTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
global.version = constants.VERSION_11
end
if (global.version < constants.VERSION_12) then
for _,squad in pairs(natives.squads) do
squad.status = constants.SQUAD_GUARDING
squad.kamikaze = false
end
-- reset ai build points due to error in earning points
natives.points = 0
global.version = constants.VERSION_12
end
if (global.version < constants.VERSION_13) then
-- switched over to tick event
regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
regionMap.processTick = roundToNearest(game.tick + INTERVAL_PROCESS, INTERVAL_PROCESS)
-- used to rate limit the number of rally cries during a period of time
natives.rallyCries = MAX_RALLY_CRIES
global.version = constants.VERSION_13
end
if (global.version < constants.VERSION_14) then
game.map_settings.unit_group.member_disown_distance = 5
game.map_settings.unit_group.max_member_speedup_when_behind = 1.1
game.map_settings.unit_group.max_member_slowdown_when_ahead = 1.0
game.map_settings.unit_group.max_group_slowdown_factor = 0.9
game.surfaces[1].print("Rampant - Version 0.14.11")
global.version = constants.VERSION_14
end
if (global.version < constants.VERSION_16) then
natives.lastShakeMessage = 0
--remove version 14 retreat limit, it has been made redundant
natives.retreats = nil
game.map_settings.unit_group.max_group_radius = 20
game.surfaces[1].print("Rampant - Version 0.14.13")
global.version = constants.VERSION_16
end
if (global.version < constants.VERSION_18) then
natives.safeEntities = {}
natives.safeEntityName = {}
game.surfaces[1].print("Rampant - Version 0.15.5")
global.version = constants.VERSION_18
end
if (global.version < constants.VERSION_19) then
onModSettingsChange(nil)
-- clear old regionMap processing Queue
-- prevents queue adding duplicate chunks
-- chunks are by key, so should overwrite old
regionMap.processQueue = {}
regionMap.processPointer = 1
regionMap.scanPointer = 1
-- clear pending chunks, will be added when loop runs below
pendingChunks = {}
-- 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,
area = { left_top = { x = chunk.x * 32,
y = chunk.y * 32 }}})
end
game.surfaces[1].print("Rampant - Version 0.15.6")
global.version = constants.VERSION_19
end
end
local function onTick(event)
local tick = event.tick
if (tick == regionMap.processTick) then
regionMap.processTick = regionMap.processTick + INTERVAL_PROCESS
local surface = game.surfaces[1]
local evolutionFactor = game.forces.enemy.evolution_factor
local players = game.players
processPendingChunks(regionMap, surface, pendingChunks)
scanMap(regionMap, surface, natives, evolutionFactor)
if (tick == regionMap.logicTick) then
regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
natives.rallyCries = MAX_RALLY_CRIES
planning(natives, evolutionFactor, tick, surface)
regroupSquads(natives, evolutionFactor)
processPlayers(players, regionMap, surface, natives, evolutionFactor, tick)
squadBeginAttack(natives, players, evolutionFactor)
squadAttack(regionMap, surface, natives)
end
processMap(regionMap, surface, natives, evolutionFactor)
end
end
local function onBuild(event)
addRemoveEntity(regionMap, event.created_entity, natives, true, false)
end
local function onPickUp(event)
addRemoveEntity(regionMap, event.entity, natives, false, false)
end
local function onDeath(event)
local entity = event.entity
local surface = entity.surface
if (surface.index == 1) then
if (entity.force.name == "enemy") then
if (entity.type == "unit") then
local entityPosition = entity.position
local deathChunk = getChunkByPosition(regionMap, entityPosition.x, entityPosition.y)
if (deathChunk ~= nil) then
-- drop death pheromone where unit died
deathScent(deathChunk)
if ((event.force ~= nil) and (event.force.name == "player")) then
local evolutionFactor = game.forces.enemy.evolution_factor
if (deathChunk[MOVEMENT_PHEROMONE] < -(evolutionFactor * RETREAT_MOVEMENT_PHEROMONE_LEVEL)) then
retreatUnits(deathChunk,
convertUnitGroupToSquad(natives,
entity.unit_group),
regionMap,
surface,
natives,
event.tick)
local rallyThreshold = BASE_RALLY_CHANCE + (evolutionFactor * BONUS_RALLY_CHANCE)
if (natives.rallyCries >= 0) and (math.random() < rallyThreshold) then
natives.rallyCries = natives.rallyCries - 1
rallyUnits(deathChunk,
regionMap,
surface,
natives,
evolutionFactor)
end
end
end
end
-- removeScout(entity, natives)
elseif (entity.type == "unit-spawner") or (entity.type == "turret") then
addRemoveEntity(regionMap, entity, natives, false, false)
end
elseif (entity.force.name == "player") then
local creditNatives = false
local entityPosition = entity.position
if (event.force ~= nil) and (event.force.name == "enemy") then
creditNatives = true
local victoryChunk = getChunkByPosition(regionMap, entityPosition.x, entityPosition.y)
victoryScent(victoryChunk, entity.type)
end
if creditNatives and natives.safeBuildings and (natives.safeEntities[entity.type] or natives.safeEntityName[entity.name]) then
-- makeImmortalEntity(surface, entity)
-- patch (Needs to be removed)
local repairPosition = entityPosition
local repairName = entity.name
local repairForce = entity.force
local repairDirection = entity.direction
entity.destroy()
surface.create_entity({position=repairPosition,
name=repairName,
direction=repairDirection,
force=repairForce})
-- forces enemy to disperse
local enemies = surface.find_enemy_units({repairPosition.x, repairPosition.y}, CHUNK_SIZE)
for i=1, #enemies do
enemies[i].set_command({type=defines.command.wander,
distraction=defines.distraction.by_enemy})
end
else
addRemoveEntity(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
end
local function onInit()
global.regionMap = {}
global.pendingChunks = {}
global.natives = {}
regionMap = global.regionMap
natives = global.natives
pendingChunks = global.pendingChunks
onConfigChanged()
end
-- hooks
script.on_init(onInit)
script.on_load(onLoad)
script.on_event(defines.events.on_runtime_mod_setting_changed,
onModSettingsChange)
script.on_configuration_changed(onConfigChanged)
script.on_event(defines.events.on_player_built_tile, onSurfaceTileChange)
script.on_event({defines.events.on_preplayer_mined_item,
defines.events.on_robot_pre_mined},
onPickUp)
script.on_event({defines.events.on_built_entity,
defines.events.on_robot_built_entity},
onBuild)
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("rampant", interop)