-- imports local chunkPropertyUtils = require("libs/ChunkPropertyUtils") local unitUtils = require("libs/UnitUtils") local baseUtils = require("libs/BaseUtils") local mapUtils = require("libs/MapUtils") local movementUtils = require("libs/MovementUtils") local mathUtils = require("libs/MathUtils") 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 squadDefense = require("libs/SquadDefense") local squadAttack = require("libs/SquadAttack") local aiAttackWave = require("libs/AIAttackWave") local aiPlanning = require("libs/AIPlanning") local interop = require("libs/Interop") local tests = require("tests") local chunkUtils = require("libs/ChunkUtils") local upgrade = require("Upgrade") local config = require("config") -- constants local TRIPLE_CHUNK_SIZE = constants.TRIPLE_CHUNK_SIZE local INTERVAL_LOGIC = constants.INTERVAL_LOGIC local INTERVAL_PLAYER_PROCESS = constants.INTERVAL_PLAYER_PROCESS local INTERVAL_MAP_PROCESS = constants.INTERVAL_MAP_PROCESS local INTERVAL_SCAN = constants.INTERVAL_SCAN local INTERVAL_SQUAD = constants.INTERVAL_SQUAD local INTERVAL_RESQUAD = constants.INTERVAL_RESQUAD local INTERVAL_BUILDERS = constants.INTERVAL_BUILDERS local INTERVAL_TEMPERAMENT = constants.INTERVAL_TEMPERAMENT local HIVE_BUILDINGS = constants.HIVE_BUILDINGS local RECOVER_NEST_COST = constants.RECOVER_NEST_COST local RECOVER_WORM_COST = constants.RECOVER_WORM_COST local DOUBLE_CHUNK_SIZE = constants.DOUBLE_CHUNK_SIZE local PROCESS_QUEUE_SIZE = constants.PROCESS_QUEUE_SIZE local WATER_TILE_NAMES = constants.WATER_TILE_NAMES local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE local RETREAT_GRAB_RADIUS = constants.RETREAT_GRAB_RADIUS local RETREAT_SPAWNER_GRAB_RADIUS = constants.RETREAT_SPAWNER_GRAB_RADIUS local DEFINES_COMMAND_GROUP = defines.command.group local DEFINES_COMMAND_WANDER = defines.command.wander local DEFINES_COMMAND_BUILD_BASE = defines.command.build_base local DEFINES_COMMAND_ATTACK_AREA = defines.command.attack_area local DEFINES_COMMAND_GO_TO_LOCATION = defines.command.go_to_location local DEFINES_COMMMAD_COMPOUND = defines.command.compound local DEFINES_COMMAND_FLEE = defines.command.flee local DEFINES_COMMAND_STOP = defines.command.stop local DEFINES_COMPOUND_COMMAND_RETURN_LAST = defines.compound_command.return_last local DEFINES_COMPOUND_COMMAND_AND = defines.compound_command.logical_and local DEFINES_COMPOUND_COMMAND_OR = defines.compound_command.logical_or local CHUNK_SIZE = constants.CHUNK_SIZE local DEFINES_DISTRACTION_NONE = defines.distraction.none local DEFINES_DISTRACTION_BY_ENEMY = defines.distraction.by_enemy local DEFINES_DISTRACTION_BY_ANYTHING = defines.distraction.by_anything local DEFINES_WIRE_TYPE_RED = defines.wire_type.red local DEFINES_WIRE_TYPE_GREEN = defines.wire_type.green local ENERGY_THIEF_CONVERSION_TABLE = constants.ENERGY_THIEF_CONVERSION_TABLE local ENERGY_THIEF_LOOKUP = constants.ENERGY_THIEF_LOOKUP -- local POISON_LOOKUP = constants.POISON_LOOKUP -- imported functions local convertTypeToDrainCrystal = unitUtils.convertTypeToDrainCrystal local squadsDispatch = squadAttack.squadsDispatch local positionToChunkXY = mapUtils.positionToChunkXY local temperamentPlanner = aiPlanning.temperamentPlanner local getPlayerBaseGenerator = chunkPropertyUtils.getPlayerBaseGenerator local getChunkByPosition = mapUtils.getChunkByPosition local entityForPassScan = chunkUtils.entityForPassScan local processPendingChunks = chunkProcessor.processPendingChunks local processScanChunks = chunkProcessor.processScanChunks local processMap = mapProcessor.processMap local processPlayers = mapProcessor.processPlayers local scanMap = mapProcessor.scanMap local planning = aiPlanning.planning local rallyUnits = aiAttackWave.rallyUnits local recycleBases = baseUtils.recycleBases local deathScent = pheromoneUtils.deathScent local victoryScent = pheromoneUtils.victoryScent local cleanBuilders = unitGroupUtils.cleanBuilders local regroupSquads = unitGroupUtils.regroupSquads local convertUnitGroupToSquad = unitGroupUtils.convertUnitGroupToSquad local createBase = baseUtils.createBase local findNearbyBase = baseUtils.findNearbyBase local squadsBeginAttack = squadAttack.squadsBeginAttack local processActiveNests = mapProcessor.processActiveNests local retreatUnits = squadDefense.retreatUnits local accountPlayerEntity = chunkUtils.accountPlayerEntity local unregisterEnemyBaseStructure = chunkUtils.unregisterEnemyBaseStructure local registerEnemyBaseStructure = chunkUtils.registerEnemyBaseStructure local makeImmortalEntity = chunkUtils.makeImmortalEntity local registerResource = chunkUtils.registerResource local unregisterResource = chunkUtils.unregisterResource local upgradeEntity = baseUtils.upgradeEntity local rebuildNativeTables = baseUtils.rebuildNativeTables local mRandom = math.random local tRemove = table.remove -- local references to global local gameSurfaces -- used for manage which surfaces have been visited local map -- manages the chunks that make up the game world local natives -- manages the enemy units, structures, and ai local pendingChunks -- chunks that have yet to be processed by the mod -- hook functions local function onIonCannonFired(event) --[[ event.force, event.surface, event.player_index, event.position, event.radius --]] local surface = event.surface if (surface.name == natives.activeSurface) then natives.ionCannonBlasts = natives.ionCannonBlasts + 1 natives.points = natives.points + 3000 local chunk = getChunkByPosition(map, event.position) if (chunk ~= -1) then rallyUnits(chunk, map, surface, event.tick) end end end local function hookEvents() if config.ionCannonPresent then script.on_event(remote.call("orbital_ion_cannon", "on_ion_cannon_fired"), onIonCannonFired) end end local function onLoad() map = global.map natives = global.natives pendingChunks = global.pendingChunks gameSurfaces = global.gameSurfaces hookEvents() 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.name == natives.activeSurface) then pendingChunks[#pendingChunks+1] = event end end local function rebuildMap() game.get_surface(natives.activeSurface).print("Rampant - Reindexing chunks, please wait.") -- clear old map processing Queue -- prevents queue adding duplicate chunks -- chunks are by key, so should overwrite old global.map = {} map = global.map map.processQueue = {} map.processIndex = 1 map.scanIndex = 1 map.chunkToBase = {} map.chunkToNests = {} map.chunkToTurrets = {} map.chunkToTraps = {} map.chunkToUtilities = {} map.chunkToHives = {} map.chunkToPlayerBase = {} map.chunkToResource = {} map.chunkToPassScan = {} map.chunkToSquad = {} map.chunkToRetreats = {} map.chunkToRallys = {} map.chunkToSettler = {} map.chunkToPassable = {} map.chunkToPathRating = {} map.chunkToDeathGenerator = {} map.chunkToDrained = {} map.chunkToActiveNest = {} map.chunkToActiveRaidNest = {} map.nextChunkSort = 0 map.nextChunkSortTick = 0 -- preallocating memory to be used in code, making it fast by reducing garbage generated. map.neighbors = { -1, -1, -1, -1, -1, -1, -1, -1 } map.cardinalNeighbors = { -1, -1, -1, -1 } map.position = { x=0, y=0 } map.position2 = { x=0, y=0 } map.position3 = { x=0, y=0 } map.scentStaging = {} for x=1,PROCESS_QUEUE_SIZE+1 do map.scentStaging[x] = {0,0,0,0} end map.chunkScanCounts = {} map.chunkOverlapArray = { -1, -1, -1, -1 } -- map.mapOrdering = {} -- map.mapOrdering.len = 0 map.enemiesToSquad = {} map.enemiesToSquad.len = 0 map.chunkRemovals = {} map.processActiveNest = {} map.tickActiveNest = {} map.emptySquadsOnChunk = {} map.position2Top = {0, 0} map.position2Bottom = {0, 0} --this is shared between two different queries map.area = {{0, 0}, {0, 0}} map.testArea = {{0, 0}, {0, 0}} map.area2 = {map.position2Top, map.position2Bottom} map.buildPositionTop = {0, 0} map.buildPositionBottom = {0, 0} map.buildArea = {map.buildPositionTop, map.buildPositionBottom} map.countResourcesQuery = { area=map.area, type="resource" } map.filteredEntitiesUnitQuery = { area=map.area, force="enemy",type="unit" } map.filteredEntitiesClearBuildingQuery = { area=map.buildArea, force="neutral",collision_mask="player-layer" } map.filteredEntitiesEnemyUnitQuery = { area=map.area, force="enemy", type="unit", limit=301 } map.filteredEntitiesEnemyStructureQuery = { area=map.area, force="enemy", type={"turret","unit-spawner"} } map.filteredEntitiesPointQueryLimited = { position = map.position, radius = 10, limit = 1, force = "enemy", type = { "unit-spawner", "turret" } } map.activePlayerForces = {"player"} for _,force in pairs(game.forces) do local add = true if (force.name ~= "neutral") and (force.name ~= "enemy") then for i=1,#map.activePlayerForces do if (map.activePlayerForces[i] == force.name) then add = false break end end if add then map.activePlayerForces[#map.activePlayerForces+1] = force.name end end end map.filteredEntitiesPlayerQuery50 = { area=map.area, force=map.activePlayerForces, type={"wall", "transport-belt"}} map.filteredEntitiesPlayerQuery200 = { area=map.area, force=map.activePlayerForces, type={"splitter", "pump", "offshore-pump"}} map.filteredEntitiesPlayerQuery1000 = { area=map.area, force=map.activePlayerForces, type={"lamp", "solar-panel", "programmable-speaker", "accumulator", "assembling-machine", "turret", "ammo-turret"}} map.filteredEntitiesPlayerQuery2000 = { area=map.area, force=map.activePlayerForces, type={"furnace", "lab", "roboport", "beacon", "radar", "electric-turret"}} map.filteredEntitiesPlayerQuery3500 = { area=map.area, force=map.activePlayerForces, type={"boiler", "generator", "fluid-turret", "mining-drill"}} map.filteredEntitiesPlayerQuery12000 = { area=map.area, force=map.activePlayerForces, type={"artillery-turret", "reactor", "rocket-silo"}} local sharedArea = {{0,0},{0,0}} map.filteredEntitiesCliffQuery = { area=sharedArea, type="cliff", limit = 1 } map.filteredTilesPathQuery = { area=sharedArea, collision_mask="water-tile", limit = 1 } map.cliffQuery = { area=map.area2, type="cliff" } map.canPlaceQuery = { name="", position={0,0} } map.filteredTilesQuery = { collision_mask="water-tile", area=map.area } map.placeSpawnerProxyQuery = { name="spawner-proxy-rampant", position=map.position3, force="neutral" } map.upgradeEntityQuery = { name = "", position = nil } map.attackCommand = { type = DEFINES_COMMAND_ATTACK_AREA, destination = map.position, radius = CHUNK_SIZE * 1.5, distraction = DEFINES_DISTRACTION_BY_ANYTHING } map.moveCommand = { type = DEFINES_COMMAND_GO_TO_LOCATION, destination = map.position, radius = 2, pathfind_flags = { prefer_straight_paths = true, cache = true }, distraction = DEFINES_DISTRACTION_BY_ENEMY } map.settleCommand = { type = DEFINES_COMMAND_BUILD_BASE, destination = map.position, distraction = DEFINES_DISTRACTION_BY_ENEMY, ignore_planner = true } map.wonderCommand = { type = DEFINES_COMMAND_WANDER, wander_in_group = false, radius = TRIPLE_CHUNK_SIZE, ticks_to_wait = 3600 } map.wonder2Command = { type = DEFINES_COMMAND_WANDER, wander_in_group = false, radius = TRIPLE_CHUNK_SIZE, ticks_to_wait = 360 } map.stopCommand = { type = DEFINES_COMMAND_STOP } map.compoundSettleCommand = { type = DEFINES_COMMMAD_COMPOUND, structure_type = DEFINES_COMPOUND_COMMAND_RETURN_LAST, commands = { map.wonder2Command, map.settleCommand } } map.retreatCommand = { type = DEFINES_COMMAND_GROUP, group = nil, distraction = DEFINES_DISTRACTION_NONE, use_group_distraction = false } map.fleeCommand = { type = DEFINES_COMMAND_FLEE, from = nil, distraction = DEFINES_DISTRACTION_NONE } map.compoundRetreatCommand = { type = DEFINES_COMMMAD_COMPOUND, structure_type = DEFINES_COMPOUND_COMMAND_RETURN_LAST, commands = { map.fleeCommand, map.retreatCommand } } map.formGroupCommand = { type = DEFINES_COMMAND_GROUP, group = nil, distraction = DEFINES_DISTRACTION_ANYTHING, use_group_distraction = false } map.formCommand = { command = map.formGroupCommand, unit_count = 0, unit_search_distance = TRIPLE_CHUNK_SIZE } end local function onModSettingsChange(event) if event and ((string.sub(event.setting, 1, 7) ~= "rampant") or (string.sub(event.setting, 1, 15) == "rampant-arsenal") or (string.sub(event.setting, 1, 17) == "rampant-resources") or (string.sub(event.setting, 1, 17) == "rampant-evolution")) then return false end upgrade.compareTable(natives, "safeBuildings", settings.global["rampant-safeBuildings"].value) upgrade.compareTable(natives.safeEntities, "curved-rail", settings.global["rampant-safeBuildings-curvedRail"].value) upgrade.compareTable(natives.safeEntities, "straight-rail", settings.global["rampant-safeBuildings-straightRail"].value) upgrade.compareTable(natives.safeEntities, "rail-signal", settings.global["rampant-safeBuildings-railSignals"].value) upgrade.compareTable(natives.safeEntities, "rail-chain-signal", settings.global["rampant-safeBuildings-railChainSignals"].value) upgrade.compareTable(natives.safeEntities, "train-stop", settings.global["rampant-safeBuildings-trainStops"].value) upgrade.compareTable(natives.safeEntities, "lamp", settings.global["rampant-safeBuildings-lamps"].value) local changed, newValue = upgrade.compareTable(natives.safeEntities, "big-electric-pole", settings.global["rampant-safeBuildings-bigElectricPole"].value) if changed then natives.safeEntities["big-electric-pole"] = newValue natives.safeEntities["big-electric-pole-2"] = newValue natives.safeEntities["big-electric-pole-3"] = newValue natives.safeEntities["big-electric-pole-4"] = newValue natives.safeEntities["lighted-big-electric-pole-4"] = newValue natives.safeEntities["lighted-big-electric-pole-3"] = newValue natives.safeEntities["lighted-big-electric-pole-2"] = newValue natives.safeEntities["lighted-big-electric-pole"] = newValue end upgrade.compareTable(natives, "deadZoneFrequency", settings.global["rampant-deadZoneFrequency"].value) upgrade.compareTable(natives, "raidAIToggle", settings.global["rampant-raidAIToggle"].value) upgrade.compareTable(natives, "attackPlayerThreshold", settings.global["rampant-attackPlayerThreshold"].value) upgrade.compareTable(natives, "attackUsePlayer", settings.global["rampant-attackWaveGenerationUsePlayerProximity"].value) upgrade.compareTable(natives, "attackWaveMaxSize", settings.global["rampant-attackWaveMaxSize"].value) upgrade.compareTable(natives, "aiNocturnalMode", settings.global["rampant-permanentNocturnal"].value) upgrade.compareTable(natives, "aiPointsScaler", settings.global["rampant-aiPointsScaler"].value) upgrade.compareTable(natives, "newEnemies", settings.startup["rampant-newEnemies"].value) upgrade.compareTable(natives, "enemySeed", settings.startup["rampant-enemySeed"].value) upgrade.compareTable(natives, "disableVanillaAI", settings.global["rampant-disableVanillaAI"].value) natives.enabledMigration = natives.expansion and settings.global["rampant-enableMigration"].value upgrade.compareTable(natives, "ENEMY_VARIATIONS", settings.startup["rampant-newEnemyVariations"].value) game.forces.enemy.ai_controllable = not natives.disableVanillaAI return true end local function prepWorld(rebuild, surfaceName) local upgraded if surfaceName then natives.activeSurface = surfaceName if rebuild then global.version = nil end end upgraded, natives = upgrade.attempt(natives, surfaceName, gameSurfaces) gameSurfaces = global.gameSurfaces onModSettingsChange(nil) if natives.newEnemies then rebuildNativeTables(natives, game.get_surface(natives.activeSurface), game.create_random_generator(natives.enemySeed)) else natives.buildingHiveTypeLookup = {} natives.buildingHiveTypeLookup["biter-spawner"] = "biter-spawner" natives.buildingHiveTypeLookup["spitter-spawner"] = "spitter-spawner" natives.buildingHiveTypeLookup["small-worm-turret"] = "turret" natives.buildingHiveTypeLookup["medium-worm-turret"] = "turret" natives.buildingHiveTypeLookup["big-worm-turret"] = "turret" natives.buildingHiveTypeLookup["behemoth-worm-turret"] = "turret" end if upgraded then rebuildMap() map.natives = natives natives.map = map -- clear pending chunks, will be added when loop runs below global.pendingChunks = {} pendingChunks = global.pendingChunks -- queue all current chunks that wont be generated during play local surface = game.get_surface(natives.activeSurface) local tick = game.tick local position = {0,0} natives.nextChunkSort = 0 for chunk in surface.get_chunks() do local x = chunk.x local y = chunk.y position[1] = x position[2] = y if surface.is_chunk_generated(position) then onChunkGenerated({ surface = surface, area = { left_top = { x = x * 32, y = y * 32}}}) end end processPendingChunks(map, surface, pendingChunks, tick, rebuild) end end local function onConfigChanged() prepWorld(true, natives.activeSurface) end local function onBuild(event) local entity = event.created_entity or event.entity if (entity.surface.name == natives.activeSurface) then if (entity.type == "resource") and (entity.force.name == "neutral") then registerResource(entity, map) else accountPlayerEntity(entity, natives, true, false) if natives.safeBuildings then if natives.safeEntities[entity.type] or natives.safeEntities[entity.name] then entity.destructible = false end end end end end local function onMine(event) local entity = event.entity local surface = entity.surface if (surface.name == natives.activeSurface) then if (entity.type == "resource") and (entity.force.name == "neutral") then if (entity.amount == 0) then unregisterResource(entity, map) end else accountPlayerEntity(entity, natives, false, false) end end end local function onDeath(event) local entity = event.entity if entity.valid then local surface = entity.surface if (surface.name == natives.activeSurface) then local entityPosition = entity.position local chunk = getChunkByPosition(map, entityPosition) local cause = event.cause local tick = event.tick local entityType = entity.type if (entity.force.name == "enemy") then local artilleryBlast = (cause and ((cause.type == "artillery-wagon") or (cause.type == "artillery-turret"))) if artilleryBlast then natives.artilleryBlasts = natives.artilleryBlasts + 1 end if (entityType == "unit") then if (chunk ~= -1) then -- drop death pheromone where unit died deathScent(map, chunk) if event.force and (event.force.name ~= "enemy") and (chunk[MOVEMENT_PHEROMONE] < -natives.retreatThreshold) then natives.lostEnemyUnits = natives.lostEnemyUnits + 1 retreatUnits(chunk, entityPosition, convertUnitGroupToSquad(natives, entity.unit_group), map, surface, tick, (artilleryBlast and RETREAT_SPAWNER_GRAB_RADIUS) or RETREAT_GRAB_RADIUS, artilleryBlast) if (mRandom() < natives.rallyThreshold) and not surface.peaceful_mode then rallyUnits(chunk, map, surface, tick) end end end elseif event.force and (event.force.name ~= "enemy") and ((entityType == "unit-spawner") or (entityType == "turret")) then natives.points = natives.points + (((entityType == "unit-spawner") and RECOVER_NEST_COST) or RECOVER_WORM_COST) if (chunk ~= -1) then unregisterEnemyBaseStructure(map, entity) rallyUnits(chunk, map, surface, tick) retreatUnits(chunk, entityPosition, nil, map, surface, tick, RETREAT_SPAWNER_GRAB_RADIUS, (cause and ((cause.type == "artillery-wagon") or (cause.type == "artillery-turret")))) end end local pair = natives.drainPylons[entity.unit_number] if pair then local target = pair[1] local pole = pair[2] if target == entity then natives.drainPylons[entity.unit_number] = nil if pole.valid then natives.drainPylons[pole.unit_number] = nil pole.die() end elseif (pole == entity) then natives.drainPylons[entity.unit_number] = nil if target.valid then natives.drainPylons[target.unit_number] = nil target.destroy() end end end elseif (entity.force.name ~= "enemy") then local creditNatives = false if (event.force ~= nil) and (event.force.name == "enemy") then creditNatives = true if (chunk ~= -1) then victoryScent(map, chunk, entityType) end local drained = (entityType == "electric-turret") and map.chunkToDrained[chunk] if (cause ~= nil) or (drained and (drained - tick) > 0) then if ((cause and ENERGY_THIEF_LOOKUP[cause.name]) or (not cause)) then local conversion = ENERGY_THIEF_CONVERSION_TABLE[entityType] if conversion then local newEntity = surface.create_entity({position=entity.position, name=convertTypeToDrainCrystal(entity.force.evolution_factor, conversion), direction=entity.direction}) if (conversion == "pole") then local targetEntity = surface.create_entity({position=entity.position, name="pylon-target-rampant", direction=entity.direction}) targetEntity.backer_name = "" local pair = {targetEntity, newEntity} natives.drainPylons[targetEntity.unit_number] = pair natives.drainPylons[newEntity.unit_number] = pair local wires = entity.neighbours if wires then for _,v in pairs(wires.copper) do if (v.valid) then newEntity.connect_neighbour(v); end end for _,v in pairs(wires.red) do if (v.valid) then newEntity.connect_neighbour({wire = DEFINES_WIRE_TYPE_RED, target_entity = v}); end end for _,v in pairs(wires.green) do if (v.valid) then newEntity.connect_neighbour({wire = DEFINES_WIRE_TYPE_GREEN, target_entity = v}); end end end elseif newEntity.backer_name then newEntity.backer_name = "" end end end end elseif (entity.type == "resource") and (entity.force.name == "neutral") then if (entity.amount == 0) then unregisterResource(entity, map) end end if creditNatives and natives.safeBuildings and (natives.safeEntities[entityType] or natives.safeEntities[entity.name]) then makeImmortalEntity(surface, entity) else accountPlayerEntity(entity, natives, false, creditNatives) end end end end end local function onEnemyBaseBuild(event) local entity = event.entity if entity.valid then local surface = entity.surface if (surface.name == natives.activeSurface) then local chunk = getChunkByPosition(map, entity.position) if (chunk ~= -1) then local base if natives.newEnemies then base = findNearbyBase(map, chunk) if not base then base = createBase(natives, chunk, event.tick) end entity = upgradeEntity(entity, surface, base.alignment, natives, nil, true) end if entity and entity.valid then event.entity = registerEnemyBaseStructure(map, entity, base, surface) end end end end end local function onSurfaceTileChange(event) local surfaceIndex = event.surface_index or (event.robot and event.robot.surface and event.robot.surface.index) local surfaceName = game.surfaces[surfaceIndex].name if (surfaceName == natives.activeSurface) and ((event.item.name == "landfill") or (event.item.name == "waterfill")) and event.item then local surface = game.get_surface(natives.activeSurface) local chunks = {} local tiles = event.tiles for i=1,#tiles do local position = tiles[i].position local chunk = getChunkByPosition(map, position) if (chunk ~= -1) then map.chunkToPassScan[chunk] = true else local x,y = positionToChunkXY(position) local addMe = true for ci=1,#chunks do local c = chunks[ci] if (c.x == x) and (c.y == y) then addMe = false break end end if addMe then local chunkXY = {x=x,y=y} chunks[#chunks+1] = chunkXY onChunkGenerated({area = { left_top = chunkXY }, surface = surface}) end end end end end local function onResourceDepleted(event) local entity = event.entity if (entity.surface.name == natives.activeSurface) then unregisterResource(entity, map) end end local function onRobotCliff(event) local surface = event.robot.surface if (surface.name == natives.activeSurface) and (event.item.name == "cliff-explosives") then entityForPassScan(map, event.cliff) end end local function onUsedCapsule(event) local surface = game.players[event.player_index].surface if (surface.name == natives.activeSurface) and (event.item.name == "cliff-explosives") then map.position2Top.x = event.position.x-0.75 map.position2Top.y = event.position.y-0.75 map.position2Bottom.x = event.position.x+0.75 map.position2Bottom.y = event.position.y+0.75 local cliffs = surface.find_entities_filtered(map.cliffQuery) for i=1,#cliffs do entityForPassScan(map, cliffs[i]) end end end local function onRocketLaunch(event) local entity = event.rocket_silo or event.rocket if entity and entity.valid and (entity.surface.name == natives.activeSurface) then natives.rocketLaunched = natives.rocketLaunched + 1 natives.points = natives.points + 2000 end end local function onTriggerEntityCreated(event) local entity = event.entity if entity.valid and (entity.surface.name == natives.activeSurface) and (entity.name == "drain-trigger-rampant") then local chunk = getChunkByPosition(map, entity.position) if (chunk ~= -1) then map.chunkToDrained[chunk] = event.tick + 60 end entity.destroy() end end local function onInit() global.map = {} global.pendingChunks = {} global.natives = {} global.gameSurfaces = {} map = global.map natives = global.natives pendingChunks = global.pendingChunks gameSurfaces = global.gameSurfaces prepWorld(false, "nauvis") hookEvents() end local function onEntitySpawned(event) local entity = event.entity if natives.newEnemies and entity.valid then local surface = entity.surface if (surface.name == natives.activeSurface) and (entity.type ~= "unit") then if natives.buildingHiveTypeLookup[entity.name] then local spawner = event.spawner local disPos = mathUtils.distortPosition(entity.position, 8) local canPlaceQuery = map.canPlaceQuery local chunk = getChunkByPosition(map, disPos) if (chunk ~= -1) then local base = findNearbyBase(map, chunk) if not base then base = createBase(natives, chunk, event.tick) end entity = upgradeEntity(entity, surface, base.alignment, natives, disPos) if entity and entity.valid then event.entity = registerEnemyBaseStructure(map, entity, base, surface) end else entity.destroy() end else entity.destroy() end end end end local function onUnitGroupCreated(event) local group = event.group if (group.surface.name == natives.activeSurface) and (group.force.name == "enemy") then if not group.is_script_driven then natives.pendingStealGroups.len = natives.pendingStealGroups.len + 1 natives.pendingStealGroups[natives.pendingStealGroups.len] = group end end end local function onCommandDebugger(event) for i=1,natives.squads.len do if (natives.squads[i].group.valid) and (natives.squads[i].group.group_number == event.unit_number) then local msg if (event.result == defines.behavior_result.in_progress) then msg = "progress" elseif (event.result == defines.behavior_result.fail) then msg = "fail" elseif (event.result == defines.behavior_result.success) then msg = "success" elseif (event.result == defines.behavior_result.deleted) then msg = "deleted" end print(msg, event.unit_number) return end end end local function onForceCreated(event) map.activePlayerForces[#map.activePlayerForces+1] = event.force.name end local function onForceMerged(event) for i=#map.activePlayerForces,1,-1 do if (map.activePlayerForces[i] == event.source_name) then tRemove(map.activePlayerForces, i) break end end end local function onSurfaceRenamed(event) if event.old_name == natives.activeSurface then natives.activeSurface = event.new_name end if (gameSurfaces[event.old_name]) then gameSurfaces[event.new_name] = gameSurfaces[event.old_name] gameSurfaces[event.old_name] = nil end end local function onSurfaceCleared(event) local surface = game.get_surface(event.surface_index) if surface and surface.valid and (surface.name == natives.activeSurface) then prepWorld(true, natives.activeSurface) end end local function onPlayerChangedSurface(event) local player = game.players[event.player_index] local surface if player and player.valid and not settings.get_player_settings(player)["rampant-suppress-surface-change-warnings"].value then surface = player.surface if (natives.activeSurface ~= surface.name) then local playerName = player.name local surfaceName = surface.name local playerSurfaces = gameSurfaces[surfaceName] if not playerSurfaces then playerSurfaces = {} gameSurfaces[surfaceName] = playerSurfaces player.print({"description.rampant-change-surface", surfaceName, natives.activeSurface}) playerSurfaces[playerName] = true elseif not playerSurfaces[playerName] then player.print({"description.rampant-change-surface", surfaceName, natives.activeSurface}) playerSurfaces[playerName] = true end end end end local function onSurfaceDeleted(event) local surface = game.get_surface(event.surface_index) if surface and surface.valid then if (surface.name == natives.activeSurface) then prepWorld(true, "nauvis") end if (gameSurfaces[surface.name]) then gameSurfaces[surface.name] = nil end end end -- hooks script.on_nth_tick(INTERVAL_PLAYER_PROCESS, function (event) local gameRef = game processPlayers(gameRef.connected_players, map, gameRef.get_surface(natives.activeSurface), event.tick) end) script.on_nth_tick(INTERVAL_MAP_PROCESS, function (event) local gameRef = game processMap(map, gameRef.get_surface(natives.activeSurface), event.tick) end) script.on_nth_tick(INTERVAL_SCAN, function (event) local tick = event.tick local gameRef = game local surface = gameRef.get_surface(natives.activeSurface) processPendingChunks(map, surface, pendingChunks, tick) scanMap(map, surface, tick) processScanChunks(map, surface) end) script.on_nth_tick(INTERVAL_LOGIC, function (event) local tick = event.tick planning(natives, game.forces.enemy.evolution_factor, tick) squadsBeginAttack(natives, game.get_surface(natives.activeSurface)) if natives.newEnemies then recycleBases(natives, tick) end end) script.on_nth_tick(INTERVAL_TEMPERAMENT, function (event) temperamentPlanner(natives) end) script.on_nth_tick(INTERVAL_SQUAD, function () squadsDispatch(map, game.get_surface(natives.activeSurface)) end) script.on_nth_tick(INTERVAL_BUILDERS, function () cleanBuilders(natives, game.get_surface(natives.activeSurface)) end) script.on_nth_tick(INTERVAL_RESQUAD, function () regroupSquads(natives) end) script.on_event(defines.events.on_tick, function (event) processActiveNests(map, game.get_surface(natives.activeSurface), event.tick) end) script.on_event(defines.events.on_surface_cleared, onSurfaceCleared) script.on_event(defines.events.on_surface_renamed, onSurfaceRenamed) script.on_event(defines.events.on_player_changed_surface, onPlayerChangedSurface) 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_resource_depleted, onResourceDepleted) script.on_event({defines.events.on_player_built_tile, defines.events.on_robot_built_tile}, onSurfaceTileChange) script.on_event(defines.events.on_player_used_capsule, onUsedCapsule) script.on_event(defines.events.on_trigger_created_entity, onTriggerEntityCreated) script.on_event(defines.events.on_pre_robot_exploded_cliff, onRobotCliff) script.on_event(defines.events.on_biter_base_built, onEnemyBaseBuild) script.on_event({defines.events.on_player_mined_entity, defines.events.on_robot_mined_entity}, onMine) script.on_event({defines.events.on_built_entity, defines.events.on_robot_built_entity, defines.events.script_raised_built, defines.events.script_raised_revive}, onBuild) -- script.on_event(defines.events.on_ai_command_completed, onCommandDebugger) script.on_event(defines.events.on_entity_spawned, onEntitySpawned) script.on_event(defines.events.on_rocket_launched, onRocketLaunch) script.on_event({defines.events.on_entity_died, defines.events.script_raised_destroy}, onDeath) script.on_event(defines.events.on_chunk_generated, onChunkGenerated) script.on_event(defines.events.on_unit_group_created, onUnitGroupCreated) script.on_event(defines.events.on_force_created, onForceCreated) script.on_event(defines.events.on_forces_merged, onForceMerged) remote.add_interface("rampantTests", { pheromoneLevels = tests.pheromoneLevels, activeSquads = tests.activeSquads, entitiesOnPlayerChunk = tests.entitiesOnPlayerChunk, findNearestPlayerEnemy = tests.findNearestPlayerEnemy, morePoints = tests.morePoints, aiStats = tests.aiStats, dumpEnvironment = tests.dumpEnvironment, fillableDirtTest = tests.fillableDirtTest, tunnelTest = tests.tunnelTest, dumpNatives = tests.dumpatives, createEnemy = tests.createEnemy, attackOrigin = tests.attackOrigin, cheatMode = tests.cheatMode, gaussianRandomTest = tests.gaussianRandomTest, reveal = tests.reveal, showMovementGrid = tests.showMovementGrid, showBaseGrid = tests.showBaseGrid, baseStats = tests.baseStats, mergeBases = tests.mergeBases, clearBases = tests.clearBases, getOffsetChunk = tests.getOffsetChunk, registeredNest = tests.registeredNest, colorResourcePoints = tests.colorResourcePoints, entityStats = tests.entityStats, stepAdvanceTendrils = tests.stepAdvanceTendrils, unitGroupBuild = tests.unitGroupBuild, exportAiState = tests.exportAiState(nil), createEnergyTest = tests.createEnergyTest, killActiveSquads = tests.killActiveSquads, scanChunkPaths = tests.scanChunkPaths } ) interop.setActiveSurface = function (surfaceName) prepWorld(true, surfaceName) end commands.add_command("GetRampantAISurface", {"description.rampant-get-surface"}, function (event) for _,player in pairs(game.connected_players) do if (player.valid) then player.print({"description.rampant-get-surface", player.surface.name, natives.activeSurface}) end end end) commands.add_command("SetRampantAISurface", {"description.rampant-set-surface"}, function (event) local surfaceName = event.parameter if surfaceName then if (surfaceName ~= natives.activeSurface) then if not game.get_surface(surfaceName) then game.print({"description.rampant-invalid-set-surface", surfaceName}) else local surface = game.get_surface(natives.activeSurface) if surface and surface.valid then for _,entity in pairs(natives.drainPylons) do if entity and entity.valid then entity.destroy() end end end prepWorld(true, surfaceName) game.print({"description.rampant-set-surface", surfaceName}) end else game.print({"description.rampant-already-set-surface", surfaceName}) end else game.print({"description.rampant-error-set-surface"}) end end) remote.add_interface("rampant", interop)