1903 lines
68 KiB
Lua

-- Copyright (C) 2022 Dimm2101
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
-- imports
local chunkPropertyUtils = require("libs/ChunkPropertyUtils")
local unitUtils = require("libs/UnitUtils")
local baseUtils = require("libs/BaseUtils")
local bitersEnrage = require("libs/BitersEnrage")
local mapUtils = require("libs/MapUtils")
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 squadCompression = require("libs/SquadCompression")
local tests = require("libs/Tests")
local undergroundAttack = require("libs/UndergroundAttack")
local aiAttackWave = require("libs/AIAttackWave")
local aiPlanning = require("libs/AIPlanning")
local chunkUtils = require("libs/ChunkUtils")
local upgrade = require("Upgrade")
local config = require("config")
local aiPredicates = require("libs/AIPredicates")
local stringUtils = require("libs/StringUtils")
require("remote_interface")
-- constants
local BASE_CHANGING_CHANCE = constants.BASE_CHANGING_CHANCE
local AI_SETTLER_COST = constants.AI_SETTLER_COST
local RECOVER_NEST_COST = constants.RECOVER_NEST_COST
local RECOVER_WORM_COST = constants.RECOVER_WORM_COST
local RETREAT_GRAB_RADIUS = constants.RETREAT_GRAB_RADIUS
local RETREAT_SPAWNER_GRAB_RADIUS = constants.RETREAT_SPAWNER_GRAB_RADIUS
local PROCESS_QUEUE_SIZE = constants.PROCESS_QUEUE_SIZE
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 SURFACE_IGNORED = constants.SURFACE_IGNORED
local OVERDAMAGEPROTECTION_THRESHOLD = constants.OVERDAMAGEPROTECTION_THRESHOLD
local VANILLA_ENTITIES = constants.VANILLA_ENTITIES
-- imported functions
local isRampantSetting = stringUtils.isRampantSetting
local canMigrate = aiPredicates.canMigrate
local convertTypeToDrainCrystal = unitUtils.convertTypeToDrainCrystal
local squadDispatch = squadAttack.squadDispatch
local processDecompressQueue = squadCompression.processDecompressQueue
local processNonRampantSquads = squadCompression.processNonRampantSquads
local removeOneTickImmunity = squadCompression.removeOneTickImmunity
local onUnitPreKilled = squadCompression.onUnitPreKilled
local createUndergroudAttack = undergroundAttack.createUndergroudAttack
local enrageBitersInRange = bitersEnrage.enrageBitersInRange
local addDebugButton = tests.addDebugButton
local onDebugElementClick = tests.onDebugElementClick
local debug_onUnitDamaged = tests.debug_onUnitDamaged
local in_debug_list = tests.in_debug_list
local cleanUpMapTables = mapProcessor.cleanUpMapTables
local positionToChunkXY = mapUtils.positionToChunkXY
local processMapAIs = aiPlanning.processMapAIs
local processVengence = mapProcessor.processVengence
local processSpawners = mapProcessor.processSpawners
local processStaticMap = mapProcessor.processStaticMap
local disperseVictoryScent = pheromoneUtils.disperseVictoryScent
local getChunkByPosition = mapUtils.getChunkByPosition
local entityForPassScan = chunkUtils.entityForPassScan
local processPendingChunks = chunkProcessor.processPendingChunks
local processScanChunks = chunkProcessor.processScanChunks
local processPendingMutations = chunkProcessor.processPendingMutations
local processMap = mapProcessor.processMap
local processPlayers = mapProcessor.processPlayers
local scanEnemyMap = mapProcessor.scanEnemyMap
local scanPlayerMap = mapProcessor.scanPlayerMap
local scanResourceMap = mapProcessor.scanResourceMap
local suspendClearedMaps = mapProcessor.suspendClearedMaps
local processNests = mapProcessor.processNests
local processGrowingBases = mapProcessor.processGrowingBases
local rallyUnits = aiAttackWave.rallyUnits
local recycleBases = baseUtils.recycleBases
local deathScent = pheromoneUtils.deathScent
local victoryScent = pheromoneUtils.victoryScent
local createSquad = unitGroupUtils.createSquad
local createBase = baseUtils.createBase
local findNearbyBase = baseUtils.findNearbyBase
local processActiveNests = mapProcessor.processActiveNests
local getDeathGenerator = chunkPropertyUtils.getDeathGenerator
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 cleanSquads = squadAttack.cleanSquads
local processCompression = squadCompression.processCompression
local upgradeEntity = baseUtils.upgradeEntity
local rebuildNativeTables = baseUtils.rebuildNativeTables
local mRandom = math.random
local tRemove = table.remove
local sFind = string.find
local sSub = string.sub
local ShowNewBaseAligments = baseUtils.ShowNewBaseAligments
local thisIsNewEnemyPosition = chunkPropertyUtils.thisIsNewEnemyPosition
local mMax = math.max
local mMin = math.min
-- local references to global
local universe -- manages the chunks that make up the game universe
-- hook functions
local function onIonCannonFired(event)
--[[
event.force, event.surface, event.player_index, event.position, event.radius
--]]
local map = universe.maps[event.surface.index]
if not map then
return
end
universe.retribution = universe.retribution + 1
map.vengenceLimiter = 0
map.ionCannonBlasts = map.ionCannonBlasts + 1
map.points = map.points + 4000
if universe.aiPointsPrintGainsToChat then
game.print(map.surface.name .. ": Points: +" .. 4000 .. ". [Ion Cannon] Total: " .. string.format("%.2f", map.points))
end
local chunk = getChunkByPosition(map, event.position)
if (chunk ~= -1) then
rallyUnits(chunk, map, event.tick)
end
end
local function hookEvents()
if settings.startup["ion-cannon-radius"] ~= nil then
script.on_event(remote.call("orbital_ion_cannon", "on_ion_cannon_fired"),
onIonCannonFired)
end
end
local function onLoad()
universe = global.universe
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.
universe.pendingChunks[event] = true
end
local function onChunkDeleted(event)
local surfaceIndex = event.surface_index
local map = universe.maps[surfaceIndex]
if map then
local positions = event.positions
for i=1,#positions do
local position = positions[i]
local x = position.x * 32
local y = position.y * 32
local chunk = mapUtils.getChunkByXY(map, x, y)
if chunk ~= -1 then
mapUtils.removeChunkFromMap(map, chunk)
end
end
end
end
local function prepMap(surface)
local surfaceIndex = surface.index
if not universe.maps then
universe.maps = {}
end
if SURFACE_IGNORED(surface, universe) then
return
end
surface.print("Rampant, fixed - Indexing surface:" .. tostring(surface.index) .. ", please wait.")
local map = universe.maps[surfaceIndex]
if not map then
map = {}
universe.maps[surfaceIndex] = map
end
map.processedChunks = 0
map.processQueue = {}
map.processIndex = 1
map.cleanupIndex = 1
map.scanPlayerIndex = 1
map.scanResourceIndex = 1
map.scanEnemyIndex = 1
map.processStaticIndex = 1
map.outgoingScanWave = true
map.outgoingStaticScanWave = true
map.chunkToPlayerTurrets = {}
map.chunkToBase = {}
map.chunkToNests = {}
map.chunkToTurrets = {}
map.chunkToTraps = {}
map.chunkToUtilities = {}
map.chunkToHives = {}
map.chunkToPlayerBase = {}
map.chunkToPlayerBaseDetection = {}
map.chunkToResource = {}
map.chunkToSquad = {}
map.chunkToRetreats = {}
map.chunkToRallys = {}
map.chunkToPassable = {}
map.chunkToPathRating = {}
map.chunkToDeathGenerator = {}
map.chunkToDrained = {}
map.chunkToVictory = {}
map.chunkToActiveNest = {}
map.chunkToActiveRaidNest = {}
map.nextChunkSort = 0
map.nextChunkSortTick = 0
map.deployVengenceIterator = nil
map.recycleBaseIterator = nil
map.processActiveSpawnerIterator = nil
map.processActiveRaidSpawnerIterator = nil
map.processMigrationIterator = nil
map.processNestIterator = nil
map.victoryScentIterator = nil
map.chunkScanCounts = {}
map.chunkFactionCounts = {}
map.enemiesToSquad = {}
map.enemiesToSquad.len = 0
map.chunkRemovals = {}
map.processActiveNest = {}
map.tickActiveNest = {}
map.emptySquadsOnChunk = {}
map.surface = surface
map.universe = universe
map.vengenceQueue = {}
map.points = 0
map.state = constants.AI_STATE_AGGRESSIVE
map.baseId = 0
map.squads = nil
map.pendingAttack = nil
map.building = nil
map.evolutionLevel = game.forces.enemy.evolution_factor
map.canAttackTick = 0
map.drainPylons = {}
map.groupNumberToSquad = {}
map.activeRaidNests = 0
map.activeNests = 0
map.destroyPlayerBuildings = 0
map.lostEnemyUnits = 0
map.lostEnemyBuilding = 0
map.rocketLaunched = 0
map.builtEnemyBuilding = 0
map.ionCannonBlasts = 0
map.artilleryBlasts = 0
map.vengenceLimiter = 0
map.squadsGenerated = 0 -- (every 20 active Nests chunks = 1 max squad in agressive mode), aiAttackWave.formSquads
map.temperament = 0.5
map.temperamentScore = 0
map.stateTick = 0
map.nextPlayerScan = 0
map.basesToGrow = {}
-- queue all current chunks that wont be generated during play
local tick = game.tick
local position = {0,0}
map.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(universe, tick, true)
end
local function setNewEnemySide()
universe["ALLOW_OTHER_ENEMIES"] = settings.startup["rampantFixed--allowOtherEnemies"].value
universe["NEW_ENEMIES_SIDE"] = settings.startup["rampantFixed--newEnemiesSide"].value
end
local function onBuild(event)
local entity = event.created_entity or event.entity
if entity.valid then
-- game.print("onBuild.."..entity.name)
local map = universe.maps[entity.surface.index]
if not map then
return
end
if (entity.type == "resource") and (entity.force.name == "neutral") then
registerResource(entity, map)
else
accountPlayerEntity(entity, map, true, false)
if universe.safeBuildings then
if universe.safeEntities[entity.type] or universe.safeEntities[entity.name] then
entity.destructible = false
end
end
end
end
end
local function onMine(event)
local entity = event.entity
if entity.valid then
local map = universe.maps[entity.surface.index]
if not map then
return
end
if (entity.type == "resource") and (entity.force.name == "neutral") then
if (entity.amount == 0) then
unregisterResource(entity, map)
end
else
accountPlayerEntity(entity, map, false, false)
end
end
end
-----------------
local landfillVectors = {{0,0}, {1,0}, {0,1}, {-1,0}, {0,-1}}
local function biters_landfill(entity)
if (not entity) or (not entity.valid) then return end
if entity.prototype.max_health < 300 then return end
local position = entity.position
local surface = entity.surface
for _, vector in pairs(landfillVectors) do
local tile = surface.get_tile({position.x + vector[1], position.y + vector[2]})
if tile.collides_with("resource-layer") then
surface.set_tiles({{name = "landfill", position = tile.position}},true,true,true,true)
local particle_pos = {tile.position.x + 0.5, tile.position.y + 0.5}
for _ = 1, 50, 1 do
surface.create_particle({
name = "stone-particle",
position = particle_pos,
frame_speed = 0.1,
vertical_speed = 0.12,
height = 0.01,
movement = {-0.05 + mRandom(0, 100) * 0.001, -0.05 + mRandom(0, 100) * 0.001}
})
end
end
end
end
-----------------
local function onDeath(event)
local entity = event.entity
if entity.valid then
local surface = entity.surface
local map = universe.maps[surface.index]
if not map then
return
end
if (entity.force.name == "neutral") then
if (entity.name == "cliff") then
entityForPassScan(map, entity)
end
return
end
if entity.prototype.has_flag("not-in-kill-statistics") then
return
end
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 (not artilleryBlast) and (entity.force.name == "enemy") then
local incomingRange = 0
if event.cause and event.cause.valid then
incomingRange = mathUtils.euclideanDistancePoints(entity.position.x, entity.position.y, event.cause.position.x, event.cause.position.y)
if incomingRange >= 90 then
artilleryBlast = true
end
end
end
if artilleryBlast then
map.artilleryBlasts = map.artilleryBlasts + 1
map.vengenceLimiter = 0
universe.retribution = universe.retribution + 0.002
if cause and cause.valid and (cause.type == "electric-turret") then
if mRandom() < 0.005 then
entity.surface.create_entity({
name = "targetDummyPlasma-rampant",
position = entityPosition,
force = "enemy",
})
end
end
end
if (entityType == "unit") then
if (chunk ~= -1) then
if event.force and (event.force.name ~= "enemy") then
biters_landfill(entity)
-- drop death pheromone where unit died
deathScent(map, chunk)
-- if (not artilleryBlast) and (-getDeathGenerator(map, chunk) < -universe.retreatThreshold) and cause and cause.valid then
-- retreatUnits(chunk,
-- cause,
-- map,
-- tick,
-- (artilleryBlast and RETREAT_SPAWNER_GRAB_RADIUS) or RETREAT_GRAB_RADIUS)
-- end
map.lostEnemyUnits = map.lostEnemyUnits + 1
local chainVengenceCoefficient = settings.global["rampantFixed--chainVengenceCoefficient"].value -- 0.6 default
local vengenceOffset = chainVengenceCoefficient ^ map.vengenceLimiter
if (not surface.peaceful_mode) and (mRandom() < (universe.rallyThreshold * vengenceOffset)) then
rallyUnits(chunk, map, tick)
end
end
if artilleryBlast and universe.undergroundAttack then
undergroundAttack.onUnitKilled_DigIn(map, entity, cause)
else
squadCompression.onUnitKilled(universe, surface, entity, event.force, cause)
end
end
elseif event.force and (event.force.name ~= "enemy") and
((entityType == "unit-spawner") or (entityType == "turret"))
then
map.vengenceLimiter = 0
local pointsGain = 0
if (entityType == "unit-spawner") then
if artilleryBlast then
universe.retribution = universe.retribution + 0.018 -- additional retribution points
end
if universe.aiDifficulty ~= "Hard" then
pointsGain = RECOVER_NEST_COST * 0.2
else
pointsGain = RECOVER_NEST_COST
end
else
if universe.aiDifficulty ~= "Hard" then
pointsGain = RECOVER_WORM_COST * 0.01
else
pointsGain = RECOVER_WORM_COST
end
end
if artilleryBlast then
pointsGain = pointsGain * 1.2
end
map.points = map.points + pointsGain
if universe.aiPointsPrintGainsToChat and (pointsGain > 0) then
game.print(map.surface.name .. ": Points: +" .. pointsGain .. ". [Worm or Nest Lost] Total: " .. string.format("%.2f", map.points))
end
unregisterEnemyBaseStructure(map, entity, event.damage_type)
if (chunk ~= -1) then
rallyUnits(chunk, map, tick)
if cause and cause.valid and (cause.type == "character") then
enrageBitersInRange(map, cause.position, getChunkByPosition(map, cause.position), tick)
end
-- if artilleryBlast and cause and cause.valid then
-- retreatUnits(chunk,
-- cause,
-- map,
-- tick,
-- RETREAT_SPAWNER_GRAB_RADIUS)
-- end
end
else
local entityUnitNumber = entity.unit_number
local pair = map.drainPylons[entityUnitNumber]
if pair then
local target = pair[1]
local pole = pair[2]
if target == entity then
map.drainPylons[entityUnitNumber] = nil
if pole.valid then
map.drainPylons[pole.unit_number] = nil
pole.die()
end
elseif (pole == entity) then
map.drainPylons[entityUnitNumber] = nil
if target.valid then
map.drainPylons[target.unit_number] = nil
target.destroy()
end
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 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}
map.drainPylons[targetEntity.unit_number] = pair
map.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 universe.safeBuildings and
(universe.safeEntities[entityType] or universe.safeEntities[entity.name])
then
makeImmortalEntity(surface, entity)
else
accountPlayerEntity(entity, map, false, creditNatives)
end
end
end
end
local function onEnemyBaseBuild(event)
local entity = event.entity
if entity.valid then
local map = universe.maps[entity.surface.index]
if not map then
return
end
if map.suspended then
map.suspended = false
map.suspendCheckTick = event.tick
end
local chunk = getChunkByPosition(map, entity.position)
if (chunk ~= -1) then
local base
if universe.NEW_ENEMIES then
local thisIsRampantEnemy = false
base = findNearbyBase(map, chunk, MAXIMUM_BASE_RADIUS, BASE_CHANGING_CHANCE)
if not base then
thisIsRampantEnemy = thisIsNewEnemyPosition(universe, chunk.x, chunk.y)
base = createBase(map,
chunk,
event.tick,
thisIsRampantEnemy)
end
if base and base.thisIsRampantEnemy then
if VANILLA_ENTITIES[entity.name] or ((not universe.ALLOW_OTHER_ENEMIES) and (mRandom()<0.8)) then -- if change this, look also chunkUtils.initialScan
entity = upgradeEntity(entity,
base.alignment,
map ,nil, true)
end
end
end
if entity and entity.valid then
event.entity = registerEnemyBaseStructure(map, entity, base)
end
else
local x,y = positionToChunkXY(entity.position)
onChunkGenerated({
surface = entity.surface,
area = {
left_top = {
x = x,
y = y
}
}
})
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 map = universe.maps[surfaceIndex]
if not map then
return
end
local surface = map.surface
local chunks = {}
local tiles = event.tiles
if event.tile then
if ((event.tile.name == "landfill") or sFind(event.tile.name, "water")) then
for i=1,#tiles do
local position = tiles[i].position
local chunk = getChunkByPosition(map, position)
if (chunk ~= -1) then
local chunkData = {chunk.x, chunk.y, map.surface.index}
local chunkIndex = "x"..chunkData[1].."y"..chunkData[2].."m"..chunkData[3]
universe.chunkToPassScan[chunkIndex] = chunkData
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
else
for i=1,#tiles do
local tile = tiles[i]
if (tile.name == "landfill") or sFind(tile.name, "water") then
local position = tile.position
local chunk = getChunkByPosition(map, position)
if (chunk ~= -1) then
local chunkData = {chunk.x, chunk.y, map.surface.index}
local chunkIndex = "x"..chunkData[1].."y"..chunkData[2].."m"..chunkData[3]
universe.chunkToPassScan[chunkIndex] = chunkData
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
end
local function onResourceDepleted(event)
local entity = event.entity
if entity.valid then
local map = universe.maps[entity.surface.index]
if not map then
return
end
unregisterResource(entity, map)
end
end
local function onRobotCliff(event)
local entity = event.robot
if entity.valid then
local map = universe.maps[entity.surface.index]
if not map then
return
end
if (event.item.name == "cliff-explosives") then
entityForPassScan(map, event.cliff)
end
end
end
local function onUsedCapsule(event)
local surface = game.players[event.player_index].surface
local map = universe.maps[surface.index]
if not map then
return
end
if (event.item.name == "cliff-explosives") then
local position2Top = universe.position2Top
local position2Bottom = universe.position2Bottom
position2Top.x = event.position.x-0.75
position2Top.y = event.position.y-0.75
position2Bottom.x = event.position.x+0.75
position2Bottom.y = event.position.y+0.75
local cliffs = surface.find_entities_filtered(universe.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.valid then
local map = universe.maps[entity.surface.index]
local points
if game.active_mods["space-exploration"] then
universe.retribution = universe.retribution + 0.1
points = 500
else
universe.retribution = universe.retribution + 10
points = 5000
end
if not map then
return
end
map.vengenceLimiter = 0
map.rocketLaunched = map.rocketLaunched + 1
map.points = map.points + points
if universe.aiPointsPrintGainsToChat then
game.print(map.surface.name .. ": Points: +" .. points .. ". [Rocket Launch] Total: " .. string.format("%.2f", map.points))
end
end
end
local function onTriggerEntityCreated(event)
local entity = event.entity
if entity.valid and (entity.name == "drain-trigger-rampant") then
local map = universe.maps[entity.surface.index]
if not map then
return
end
local chunk = getChunkByPosition(map, entity.position)
if (chunk ~= -1) then
map.chunkToDrained[chunk] = event.tick + 60
end
entity.destroy()
end
end
local function onGroupFinishedGathering(event)
local group = event.group
if not group.valid or (group.force.name ~= "enemy") then
return
end
local map = universe.maps[group.surface.index]
if not map then
--group.destroy()
return
end
local squad = universe.groupNumberToSquad[group.group_number]
if (not group.is_script_driven) or (squad and not squad.vengence) then
if (not group.is_script_driven) and (not settings.global["rampantFixed--allowDaytimeNonRampantActions"].value) then
if (map.state == constants.AI_STATE_PEACEFUL) or (universe.aiNocturnalMode and (group.surface.darkness <= 0.65)) then
group.destroy()
return
end
end
end
if squad then
if not squad.undergoundAttack then
processCompression(map, squad, getChunkByPosition(map, group.position), true)
else
createUndergroudAttack(map, squad)
end
elseif (#group.members > 70) and group.position and not group.is_script_driven then
local nonRampantSquad = createSquad(group.position, map, group, false)
nonRampantSquad.nonRampantSquad = true
processCompression(map, nonRampantSquad, getChunkByPosition(map, group.position), false)
if nonRampantSquad.compressed then
universe.nonRampantCompressedSquads[group.group_number] = nonRampantSquad
--game.print("non-rampant squad#"..group.group_number..", "..#group.members.." units [gps=" .. group.position.x .. "," .. group.position.y .."]") -- debug
end
end
-- group can be destroyed after createUndergroudAttack()
if squad and group.valid and group.is_script_driven then
squadDispatch(map, squad)
end
end
local function onForceCreated(event)
upgrade.rebuildActivePlayerForces(universe)
-- if event.force then
-- if game.forces["enemy"].is_friend(event.force) then
-- return
-- end
-- if game.forces["enemy"].get_cease_fire(event.force) then
-- return
-- end
-- end
-- universe.activePlayerForces[#universe.activePlayerForces+1] = event.force.name
end
local function onForceDiplomacyChanged(event)
upgrade.rebuildActivePlayerForces(universe)
-- local otherForce = event.force
-- if event.force.name == "enemy" then
-- otherForce = event.other_force
-- end
-- local thisIsEnemyFriend = game.forces["enemy"].is_friend(otherForce)
-- thisIsEnemyFriend = thisIsEnemyFriend or game.forces["enemy"].get_cease_fire(otherForce)
-- if thisIsEnemyFriend then
-- for i=#universe.activePlayerForces,1,-1 do
-- if (universe.activePlayerForces[i] == otherForce.name) then
-- tRemove(universe.activePlayerForces, i)
-- break
-- end
-- end
-- else
-- local forceFound = false
-- for i=#universe.activePlayerForces,1,-1 do
-- if (universe.activePlayerForces[i] == otherForce.name) then
-- forceFound = true
-- break
-- end
-- end
-- if not forceFound then
-- universe.activePlayerForces[#universe.activePlayerForces+1] = otherForce.name
-- end
-- end
end
local function onForceMerged(event)
upgrade.rebuildActivePlayerForces(universe)
-- for i=#universe.activePlayerForces,1,-1 do
-- if (universe.activePlayerForces[i] == event.source_name) then
-- tRemove(universe.activePlayerForces, i)
-- break
-- end
-- end
end
local function onSurfaceCreated(event)
local surface = game.surfaces[event.surface_index]
if not SURFACE_IGNORED(surface, universe) then
prepMap(surface)
end
end
local function onSurfaceDeleted(event)
local surfaceIndex = event.surface_index
if (universe.mapIterator == surfaceIndex) then
universe.mapIterator, universe.activeMap = next(universe.maps, universe.mapIterator)
end
if (universe.suspendMapsIterator == surfaceIndex) then
universe.suspendMapsIterator = next(universe.maps, universe.suspendMapsIterator)
end
universe.maps[surfaceIndex] = nil
--game.print("onSurfaceDeleted: surfaceIndex =".. tostring(surfaceIndex)) -- debug
-- bases and squads will be deleted by baseUtils.recycleBases and squadAttack.cleanSquads
end
local function onBuilderArrived(event)
local builder = event.group
if not (builder and builder.valid) then
builder = event.unit
if not (builder and builder.valid and builder.force.name == "enemy") then
return
end
elseif (builder.force.name ~= "enemy") then
return
end
local targetPosition = universe.position
targetPosition.x = builder.position.x
targetPosition.y = builder.position.y
if universe.aiPointsPrintSpendingToChat then
game.print("Settled: [gps=" .. targetPosition.x .. "," .. targetPosition.y .."]")
end
builder.surface.create_entity(universe.createBuildCloudQuery)
end
local targetDummyArray= {}
targetDummyArray["targetDummyPlasma-rampant"] = true
targetDummyArray["targetDummyFire-rampant"] = true
targetDummyArray["targetDummyPhysical-rampant"] = true
targetDummyArray["targetDummyLaser-rampant"] = true
local protectedAreaUnitsQuery = {
position = nil,
radius = 6,
force = "enemy",
type={"unit"}
}
local function onSectorScanned(event)
local entity = event.radar
if entity.valid then
if targetDummyArray[entity.name] then
-- canceled since 1.5.0
-- if entity.name == "targetDummyPlasma-rampant" then
-- protectedAreaUnitsQuery.position = entity.position
-- local protectedEntities = entity.surface.find_entities_filtered(protectedAreaUnitsQuery)
-- for i = 1, #protectedEntities do
-- local protectedEntity = protectedEntities[i]
-- protectedEntity.destructible = false
-- universe.protectedUnits[protectedEntity.unit_number] = {entity = protectedEntity, tick = game.tick + 120}
-- --game.print("tick:"..game.tick.. " add protection to "..universe.protectedUnits[protectedEntity.unit_number].tick)
-- end
-- end
entity.damage(30, "neutral")
elseif entity.name == "test-rampant" then
onEntitySpawned(event)
entity.damage(1, "neutral")
end
end
return
end
local function processProtectedUnits()
-- if not universe then
-- return
-- end
for unitNumber, protectionData in pairs(universe.protectedUnits) do
if not protectionData.entity.valid then
universe.protectedUnits[unitNumber] = nil
elseif protectionData.tick <= game.tick then
protectionData.entity.destructible = true
universe.protectedUnits[unitNumber] = nil
--game.print("tick:"..game.tick.." remove protection")
end
end
end
-- this variables reset after reloading (player can change/disable protecion at startup settings)
-- as practice has shown, local variables do not lead to desynchronization, unless it leads to different results
local unitsProtection = {}
local OP_EfficiencyPercent = settings.startup["rampantFixed--oneshotProtection_efficiency"].value
local LR_EfficiencyPercent = settings.startup["rampantFixed--longRangeImmunity_efficiency"].value
local OP_efficienty = OP_EfficiencyPercent * 0.01
local LR_Efficiency = LR_EfficiencyPercent * 0.01
local LR_DamageKf = 1 - LR_Efficiency
local allowOneshotProtection = settings.startup["rampantFixed--allowOneshotProtection"]
local allowLongRangeImmunity = settings.startup["rampantFixed--allowLongRangeImmunity"]
local LR_exceptions = {}
LR_exceptions["fluid-turret"] = true
LR_exceptions["artillery-turret"] = true
LR_exceptions["artillery-wagon"] = true
local LR_exceptions_DamageKf = 0.5
local function fillAndReturnUnitProtections(entity)
if unitsProtection[entity.name] then
return unitsProtection[entity.name]
else
local longRangeImmunity
local overdamageProtection
if entity.prototype.resistances then
for resistance, values in pairs(entity.prototype.resistances) do
-- some checks, becouse mods can assign there resistances to their own units. And make them almost immortal
if resistance == "rampant-longRangeImmunity" then
if values.percent and (math.floor(100*values.percent+0.1) == LR_EfficiencyPercent) and (values.decrease > 10) then -- -- bug: sometimes percent can differ (ex: 0.949000049)
longRangeImmunity = values.decrease
end
elseif resistance == "rampant-overdamageProtection" then
if values.percent and (math.floor(100*values.percent+0.1) == OP_EfficiencyPercent) and (values.decrease > 5) then
overdamageProtection = values.decrease
end
end
end
end
unitsProtection[entity.name] = {longRangeImmunity = longRangeImmunity, overdamageProtection = overdamageProtection}
-- game.print("LRI = "..tostring(longRangeImmunity).." , OP = "..tostring(overdamageProtection)) -- debug
return unitsProtection[entity.name]
end
end
local function onEntityDamaged(event)
if not event.entity then
return
end
if event.entity.valid and event.entity.unit_number then
local entity = event.entity
local unitProtection = fillAndReturnUnitProtections(event.entity)
if (event.final_damage_amount > 0) and event.cause and (event.cause ~= event.entity) then
if (event.cause.type == "character") and debug_onUnitDamaged(event, universe.debugSettings) then
return
end
-----------------
if allowLongRangeImmunity and unitProtection.longRangeImmunity then
local incomingRange = 0
if event.cause and event.cause.valid then
if LR_exceptions[event.cause.type] then
incomingRange = -1
else
incomingRange = mathUtils.euclideanDistancePoints(entity.position.x, entity.position.y, event.cause.position.x, event.cause.position.y)
end
if incomingRange == - 1 then
local startHP = universe.unitProtectionData.unitCurrentHP[entity.unit_number] or (entity.prototype.max_health)
event.final_damage_amount = event.final_damage_amount * LR_exceptions_DamageKf
entity.health = startHP - event.final_damage_amount
event.final_health = entity.health
elseif incomingRange>unitProtection.longRangeImmunity then
local startHP = universe.unitProtectionData.unitCurrentHP[entity.unit_number] or (entity.prototype.max_health)
if LR_DamageKf > 0 then
event.final_damage_amount = event.final_damage_amount * LR_DamageKf
else
event.final_damage_amount = 0
end
entity.health = startHP - event.final_damage_amount
event.final_health = entity.health
-- game.print("LRI:"..startHP.."-("..event.original_damage_amount.."->"..event.final_damage_amount..")="..entity.health) -- DEBUG
end
end
end
-----------------
if allowOneshotProtection and unitProtection.overdamageProtection and (event.original_damage_amount > OVERDAMAGEPROTECTION_THRESHOLD) and (event.original_damage_amount >= event.final_damage_amount) then
local maxDamage = unitProtection.overdamageProtection
local startHP = universe.unitProtectionData.unitCurrentHP[entity.unit_number] or (entity.prototype.max_health)
if maxDamage<event.original_damage_amount then
local final_damage_amount = maxDamage*(event.final_damage_amount/event.original_damage_amount)
if OP_efficienty < 1 then
final_damage_amount = event.final_damage_amount - (event.final_damage_amount - final_damage_amount) * OP_efficienty
end
event.final_damage_amount = final_damage_amount
entity.health = startHP - final_damage_amount
end
event.final_health = entity.health
--game.print("OP:"..startHP.."-("..event.original_damage_amount.."->"..event.final_damage_amount..")="..entity.health..", source = ".. tostring(event.cause.name)) -- DEBUG
end
end
-- canceled since 1.5.0
-- if entity.health<=0 then
-- local compressedUnit = universe.compressedUnits[entity.unit_number]
-- if compressedUnit and (compressedUnit.count > 1) then
-- onUnitPreKilled(universe, entity.surface, entity, event.force, event.cause)
-- end
-- end
if unitProtection.longRangeImmunity or unitProtection.overdamageProtection then
if entity.health<=0 then
universe.unitProtectionData.unitCurrentHP[entity.unit_number] = nil
else
universe.unitProtectionData.unitCurrentHP[entity.unit_number] = entity.health
end
end
end
end
local function showNewgameMessages()
-- if universe.NEW_ENEMIES then
-- game.print({"description.rampantFixed--EnemySettings1_1_10"})
-- end
-- if game.active_mods["space-exploration"] and game.active_mods["combat-mechanics-overhaul"] and game.active_mods["Krastorio2"] then
-- if not settings.startup["rampantFixed--useBlockableSteamAttacks"].value then
-- game.print({"description.rampantFixed--K2_SE_CMO_incompatibilityWarning"})
-- end
-- end
end
-- hooks
script.on_event(defines.events.on_tick,
function ()
local gameRef = game
local tick = gameRef.tick
local pick = tick % 8
local pick60 = tick % 60
---------
if tick == 1 then
showNewgameMessages()
end
---------
removeOneTickImmunity(universe)
processProtectedUnits()
local map = universe.activeMap
local mapCounter = 0 -- it looks very much like some mods are able to remove the main world. If this happens and there are no worlds left with biters, it will turn out to be an endless cycle.
if (not map) or map.suspended or (universe.processedChunks > #map.processQueue) then
repeat
universe.mapIterator, map = next(universe.maps, universe.mapIterator)
if not map then
universe.mapIterator, map = next(universe.maps, nil)
break
end
mapCounter = mapCounter + 1
until map and ((not map.suspended) or (map.surface.name == "nauvis") or (mapCounter > 1000)) -- nauvis cant be suspended, but just in case...
universe.processedChunks = 0
universe.activeMap = map
end
if not map then
if universe.mapIterator then
universe.maps[universe.mapIterator] = nil
universe.mapIterator = nil
end
processPendingChunks(universe, tick)
processScanChunks(universe)
universe.processedChunks = universe.processedChunks + PROCESS_QUEUE_SIZE
return
end
if (pick == 0) then
processPendingChunks(universe, tick)
processScanChunks(universe)
universe.processedChunks = universe.processedChunks + PROCESS_QUEUE_SIZE
if universe.NEW_ENEMIES then
recycleBases(universe, tick)
end
cleanUpMapTables(map, tick)
suspendClearedMaps(universe, tick)
elseif (pick == 1) then
processPlayers(gameRef.connected_players, universe, tick)
elseif (pick == 2) then
processMap(map, tick)
elseif (pick == 3) then
processStaticMap(map)
disperseVictoryScent(map)
processVengence(map)
elseif (pick == 4) then
scanResourceMap(map, tick)
elseif (pick == 5) then
scanEnemyMap(map, tick)
processSpawners(map, tick)
elseif (pick == 6) then
scanPlayerMap(map, tick)
processNests(map, tick)
elseif (pick == 7) then
processGrowingBases(universe, tick)
end
if pick60 == 0 then
processMapAIs(universe, gameRef.forces.enemy.evolution_factor, tick)
end
if pick~=5 then
processPendingMutations(universe)
end
processActiveNests(universe, tick)
processDecompressQueue(universe)
cleanSquads(universe)
end
)
script.on_nth_tick(30000,
function()
if universe then
local evo = game.forces.enemy.evolution_factor
if settings.startup["rampantFixed--JuggernautEnemy"].value and (not universe.JuggernautAlertShown) and (evo > 0.75) and (evo < 0.82) then
universe.JuggernautAlertShown = true
game.print({"description.rampantFixed--JuggernautAlert"})
end
baseUtils.setRandomBaseToMutate(universe)
end
end
)
--- copressed and underground squads
script.on_nth_tick(60,
function()
if universe then
if universe.nonRampantCompressedSquads then
processNonRampantSquads(universe)
end
end
end
)
script.on_nth_tick(30,
function()
if universe then
undergroundAttack.processUndergroundSquads(universe)
end
end
)
script.on_nth_tick(3600,
function()
if universe then
undergroundAttack.updateUndergroundAttackProbability(universe)
end
end
)
script.on_nth_tick(36000,
function()
squadCompression.checkCompressedUnitsList(universe)
end
)
---
---- GUI + ----------
local function surfaceStatusCaption(surfaceIgnored)
if surfaceIgnored then
return {"description.rampantFixed--surfaceIgnored_True"}
else
return {"description.rampantFixed--surfaceIgnored_False"}
end
end
function create_surfaceIteraction_frame(player)
local gui = player.gui.screen
for i, children in pairs(gui.children) do
if children.name == "rampantFixed--surfaceIteraction_frame" then
children.destroy()
break
end
end
local root = gui.add{name = "rampantFixed--surfaceIteraction_frame", type = "frame", direction = "vertical"} -- , style = "non_draggable_frame", caption={"description.rampantFixed--surfaceIteraction_frame"}
root.force_auto_center()
player.opened = root
if not (root and root.valid) then return end -- setting player.opened can cause other scripts to delete UIs
-- Titlebar
local titlebar = root.add {
type = "flow",
name = "rampantFixed_closeSurfaceTitle",
direction = "horizontal"
}
titlebar.drag_target = root
titlebar.add { -- Title
type = "label",
caption = {"description.rampantFixed--surfaceIteraction_frame"},
ignored_by_interaction = true,
style = "frame_title"
}
titlebar.add {
type = "empty-widget",
ignored_by_interaction = true,
}
titlebar.add { -- Close button
type = "sprite-button",
name="rampantFixed_closeSurfaceStatus",
sprite = "utility/close_white",
hovered_sprite = "utility/close_black",
clicked_sprite = "utility/close_black",
style = "close_button"
}
---------------
local title_table = root.add{type="table", name="rampantFixed--surfaceIteraction_table", column_count=2, draw_horizontal_lines=false}
title_table.style.horizontally_stretchable = true
title_table.style.column_alignments[1] = "left"
title_table.style.column_alignments[2] = "right"
title_table.drag_target = root
title_table.add{type="label", name="rampantFixed_surfaceName_Title", caption={"description.rampantFixed--surfaceName_Title"}}
title_table.add{type="label", name="rampantFixed_surfaceIgnored_Title", caption={"description.rampantFixed--surfaceIgnored_Title"}}
for _,surface in pairs(game.surfaces) do
local surfaceName = surface.name
if not constants.isExcludedSurface(surfaceName) then
title_table.add{type="label", name="rampantFixed_surfaceName_"..tostring(surface.index), caption=surfaceName}
title_table.add{type="button", name="rampantFixed_surfaceStatus_"..tostring(surface.index), caption=surfaceStatusCaption(SURFACE_IGNORED(surface, universe)), style = "rampantFixed_surfaceStatus_button"}
end
end
end
function setSurfaceStatus(surface, newStatus)
if not surface then
-- game.print("setSurfaceStatus: no surface") -- debug
return true
end
if newStatus then
universe.surfaceIgnoringSet[surface.index] = 1
--game.print("setSurfaceStatus: ignored") -- debug
if universe.maps[surface.index] then
--game.print("setSurfaceStatus: call onSurfaceDeleted") -- debug
onSurfaceDeleted({surface_index = surface.index})
end
else
--game.print("setSurfaceStatus: prepmap") -- debug
universe.surfaceIgnoringSet[surface.index] = 0
prepMap(surface)
end
return newStatus
end
function surfaceStatusClick(guiElement)
local surfaceIndex = tonumber(string.sub(guiElement.name, 28))
local surface = game.surfaces[surfaceIndex]
if not surface then
game.print("Surface #"..surfaceIndex.." is not found")
return
end
local newStatus = setSurfaceStatus(surface, not SURFACE_IGNORED(surface, universe))
guiElement.caption = surfaceStatusCaption(newStatus)
if newStatus then
game.print("Surface <"..surface.name.."> now is ignored")
else
game.print("Surface <"..surface.name.."> now processed")
end
end
local function replaceNewEnemiesNests()
local totalReplaced = 0
for _,surface in pairs(game.surfaces) do
local buildings = surface.find_entities_filtered({force = "enemy", type={"turret", "unit-spawner"}})
local buildingsTotal = #buildings
for i=1,buildingsTotal do
local building = buildings[i]
local entityName
local entityPosition = {x = 0, y = 0}
if building.type == "turret" then
local wormTier = 0
wormTier = universe.buildingTierLookup[building.name]
if wormTier then
if wormTier < 3 then
entityName = "small-worm-turret"
elseif wormTier < 6 then
entityName = "medium-worm-turret"
elseif wormTier < 9 then
entityName = "big-worm-turret"
else
entityName = "behemoth-worm-turret"
end
end
elseif building.type == "unit-spawner" then
local faction = universe.enemyAlignmentLookup[building.name]
local hiveType = universe.buildingHiveTypeLookup[building.name]
if faction then
if hiveType == "spitter-spawner" then
entityName = "spitter-spawner"
else
entityName = "biter-spawner"
end
end
end
if entityName then
entityPosition.x = building.position.x
entityPosition.y = building.position.y
building.destroy()
surface.create_entity({name = entityName, position = entityPosition, force = "enemy"})
totalReplaced = totalReplaced + 1
end
end
if universe.bases then
for i, base in pairs(universe.bases) do
base.thisIsRampantEnemy = false
end
end
end
game.print({"description.rampantFixed--msg_replaceNewEnemiesNests", totalReplaced})
universe.NEW_ENEMIES = false
end
local function create_disableAdminMenu(player)
local gui = player.gui.screen
for i, children in pairs(gui.children) do
if children.name == "rampantFixed_AdminMenu_frame" then
children.destroy()
return
--break
end
end
local root = gui.add{name = "rampantFixed_AdminMenu_frame", type = "frame", direction = "vertical"} -- , style = "non_draggable_frame", caption={"description.rampantFixed--surfaceIteraction_frame"}
root.force_auto_center()
player.opened = root
if not (root and root.valid) then return end -- setting player.opened can cause other scripts to delete UIs
-- Titlebar
local titlebar = root.add {
type = "flow",
name = "rampantFixed_AdminMenuTitle",
alignment = "right",
direction = "horizontal"
}
titlebar.drag_target = root
titlebar.add { -- Title
type = "label",
caption = {"description.rampantFixed--AdminMenu"},
ignored_by_interaction = true,
style = "rampantFixed_menu_label" --"frame_title"
}
titlebar.add {
type = "empty-widget",
ignored_by_interaction = true,
}
titlebar.add { -- Close button
type = "sprite-button",
name="rampantFixed_closeAdminMenu",
sprite = "utility/close_white",
hovered_sprite = "utility/close_black",
clicked_sprite = "utility/close_black",
style = "close_button"
}
---------------
-- local menu_table = root.add{type="table", name="rampantFixed--adminMenu_table", column_count=2, draw_horizontal_lines=false}
-- menu_table.style.horizontally_stretchable = true
-- menu_table.style.column_alignments[1] = "left"
-- menu_table.style.column_alignments[2] = "right"
-- menu_table.drag_target = root
-- menu_table.add{type="label", caption="1."}
root.add{type = "button", name = "rampantFixed_showDisableNewEnemiesDialog", caption = {"description.rampantFixed--showDisableNewEnemies"}, style = "rampantFixed_menu_button"}
-- menu_table.add{type="label", caption="2."}
root.add{type = "button", name = "rampantFixed_showSurfaceIteractionFrame", caption = {"description.rampantFixed--surfaceIteraction_frame"}, style = "rampantFixed_menu_button"}
addDebugButton(player, root)
end
local function create_disableNewEnemies_frame(player)
local gui = player.gui.screen
for i, children in pairs(gui.children) do
if children.name == "rampantFixed--disableNewEnemies_frame" then
children.destroy()
break
end
end
local root = gui.add{name = "rampantFixed--disableNewEnemies_frame", type = "frame", style = "non_draggable_frame", direction = "vertical", caption={"description.rampantFixed--msg-ask-disableNewEnemies"}}
root.force_auto_center()
player.opened = root
if not (root and root.valid) then return end -- setting player.opened can cause other scripts to delete UIs
root.add{type = "label", name = "rampantFixed--disableNewEnemies_text" , caption = {"description.rampantFixed--disableNewEnemies_text"}}
frame = root.add{type="table", name="rampantFixed--disableNewEnemies_table", column_count=2, draw_horizontal_lines=false}
frame.add{type = "button", name = "rampantFixed--button_disableNewEnemies_disable", caption = {"description.rampantFixed--button_disableNewEnemies_disable"}}
frame.add{type = "button", name = "rampantFixed--button_disableNewEnemies_cancel", caption = {"description.rampantFixed--button_disableNewEnemies_cancel"}}
end
local function on_gui_click(event)
local guiElement = event.element
if guiElement.name == "rampantFixed_adminMenuButton" then
create_disableAdminMenu(game.players[event.player_index])
elseif guiElement.name == "rampantFixed_showDisableNewEnemiesDialog" then
create_disableNewEnemies_frame(game.players[event.player_index])
elseif guiElement.name == "rampantFixed_showSurfaceIteractionFrame" then
create_surfaceIteraction_frame(game.players[event.player_index])
elseif guiElement.name == "rampantFixed_closeAdminMenu" then
guiElement.parent.parent.destroy()
elseif guiElement.name == "rampantFixed--button_disableNewEnemies_disable" then
replaceNewEnemiesNests()
guiElement.parent.parent.destroy()
elseif guiElement.name == "rampantFixed--button_disableNewEnemies_cancel" then
guiElement.parent.parent.destroy()
elseif (guiElement.name == "rampantFixed_closeSurfaceStatus") then
guiElement.parent.parent.destroy()
elseif string.sub(guiElement.name, 1 , 27 ) == "rampantFixed_surfaceStatus_" then
surfaceStatusClick(guiElement)
elseif string.sub(guiElement.name, 1 , 18 ) == "rampantFixed_Debug" then
onDebugElementClick(event, universe)
end
end
local function create_disableAdminMenuButton(player, showButton)
local gui = player.gui.top
for i, children in pairs(gui.children) do
if children.name == "rampantFixed_adminMenuButton" then
children.destroy()
break
end
end
if showButton then
gui.add{type = "sprite-button", name = "rampantFixed_adminMenuButton", caption = "RFx", sprite = "entity/big-biter"}
end
end
--- GUI -
local function onModSettingsChange(event)
if not isRampantSetting(event.setting) then
return
end
-- game.print("onModSettingsChange() processing for Rampant")
upgrade.compareTable(universe,
"safeBuildings",
settings.global["rampantFixed--safeBuildings"].value)
upgrade.compareTable(universe.safeEntities,
"curved-rail",
settings.global["rampantFixed--safeBuildings-curvedRail"].value)
upgrade.compareTable(universe.safeEntities,
"straight-rail",
settings.global["rampantFixed--safeBuildings-straightRail"].value)
upgrade.compareTable(universe.safeEntities,
"rail-signal",
settings.global["rampantFixed--safeBuildings-railSignals"].value)
upgrade.compareTable(universe.safeEntities,
"rail-chain-signal",
settings.global["rampantFixed--safeBuildings-railChainSignals"].value)
upgrade.compareTable(universe.safeEntities,
"train-stop",
settings.global["rampantFixed--safeBuildings-trainStops"].value)
upgrade.compareTable(universe.safeEntities,
"lamp",
settings.global["rampantFixed--safeBuildings-lamps"].value)
local changed, newValue = upgrade.compareTable(universe.safeEntities,
"big-electric-pole",
settings.global["rampantFixed--safeBuildings-bigElectricPole"].value)
if changed then
universe.safeEntities["big-electric-pole"] = newValue
universe.safeEntities["big-electric-pole-2"] = newValue
universe.safeEntities["big-electric-pole-3"] = newValue
universe.safeEntities["big-electric-pole-4"] = newValue
universe.safeEntities["lighted-big-electric-pole-4"] = newValue
universe.safeEntities["lighted-big-electric-pole-3"] = newValue
universe.safeEntities["lighted-big-electric-pole-2"] = newValue
universe.safeEntities["lighted-big-electric-pole"] = newValue
end
upgrade.compareTable(universe,
"aiDifficulty",
settings.global["rampantFixed--aiDifficulty"].value)
upgrade.compareTable(universe,
"raidAIToggle",
settings.global["rampantFixed--raidAIToggle"].value)
upgrade.compareTable(universe,
"siegeAIToggle",
settings.global["rampantFixed--siegeAIToggle"].value)
universe.undergroundAttack = settings.global["rampantFixed--undergroundAttack"].value
upgrade.compareTable(universe,
"attackPlayerThreshold",
settings.global["rampantFixed--attackPlayerThreshold"].value)
upgrade.compareTable(universe,
"attackUsePlayer",
settings.global["rampantFixed--attackWaveGenerationUsePlayerProximity"].value)
upgrade.compareTable(universe,
"attackWaveMaxSize",
settings.global["rampantFixed--attackWaveMaxSize"].value)
upgrade.compareTable(universe,
"agressiveStart",
settings.global["rampantFixed--agressiveStart"].value)
upgrade.compareTable(universe,
"attackWaveMaxSizeEvoPercent",
settings.global["rampantFixed--attackWaveMaxSizeEvoPercent"].value)
upgrade.compareTable(universe,
"aiNocturnalMode",
settings.global["rampantFixed--permanentNocturnal"].value)
upgrade.compareTable(universe,
"aiPointsScaler",
settings.global["rampantFixed--aiPointsScaler"].value)
universe.aiPointsPrintGainsToChat = settings.global["rampantFixed--aiPointsPrintGainsToChat"].value
universe.aiPointsPrintSpendingToChat = settings.global["rampantFixed--aiPointsPrintSpendingToChat"].value
universe.enabledMigration = universe.expansion and settings.global["rampantFixed--enableMigration"].value
universe.peacefulAIToggle = settings.global["rampantFixed--peacefulAIToggle"].value
universe.printAIStateChanges = settings.global["rampantFixed--printAIStateChanges"].value
universe.debugTemperament = settings.global["rampantFixed--debugTemperament"].value
upgrade.compareTable(universe,
"AI_MAX_SQUAD_COUNT",
settings.global["rampantFixed--maxNumberOfSquads"].value)
upgrade.compareTable(universe,
"AI_MAX_BUILDER_COUNT",
settings.global["rampantFixed--maxNumberOfBuilders"].value)
for playerIndex, player in pairs(game.players) do
local player_settings = settings.get_player_settings(player)
if player.admin or in_debug_list(player) then
if player_settings["rampantFixed--showAdminMenu"].value then
create_disableAdminMenuButton(player, true)
else
create_disableAdminMenuButton(player, false)
end
end
end
return true
end
local function onConfigChanged()
local version = upgrade.attempt(universe)
if version then
if not universe then
universe = global.universe
end
end
onModSettingsChange({setting="rampantFixed--"})
upgrade.compareTable(universe,
"ENEMY_VARIATIONS",
settings.startup["rampantFixed--newEnemyVariations"].value)
upgrade.compareTable(universe,
"NEW_ENEMIES",
settings.startup["rampantFixed--newEnemies"].value)
-- upgrade.compareTable(universe,
-- "allowExternalControl",
-- settings.startup["rampantFixed--allowExternalControl"].value) -- 1.8.3++
upgrade.rebuildActivePlayerForces(universe)
if universe.NEW_ENEMIES then
rebuildNativeTables(universe)
if not universe["NEW_ENEMIES_SIDE"] then
setNewEnemySide()
elseif (universe["NEW_ENEMIES_SIDE"] ~= settings.startup["rampantFixed--newEnemiesSide"].value)
or (universe["ALLOW_OTHER_ENEMIES"] ~= settings.startup["rampantFixed--allowOtherEnemies"].value)
then
game.print({"description.rampantFixed--NEW_ENEMIES_SIDE_ignored"})
end
else
universe.buildingHiveTypeLookup = {}
universe.buildingHiveTypeLookup["biter-spawner"] = "biter-spawner"
universe.buildingHiveTypeLookup["spitter-spawner"] = "spitter-spawner"
universe.buildingHiveTypeLookup["small-worm-turret"] = "turret"
universe.buildingHiveTypeLookup["medium-worm-turret"] = "turret"
universe.buildingHiveTypeLookup["big-worm-turret"] = "turret"
universe.buildingHiveTypeLookup["behemoth-worm-turret"] = "turret"
end
for _,surface in pairs(game.surfaces) do
if not universe.maps then
universe.maps = {}
end
if (not universe.maps[surface.index]) and (not SURFACE_IGNORED(surface, universe)) then
prepMap(surface)
end
end
end
local function onInit()
global.universe = {}
hookEvents()
onConfigChanged()
setNewEnemySide()
end
script.on_event(defines.events.on_surface_deleted, onSurfaceDeleted)
script.on_event(defines.events.on_surface_cleared, onSurfaceCreated)
script.on_event(defines.events.on_surface_created, onSurfaceCreated)
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,
defines.events.script_raised_set_tiles}, 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_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_chunk_deleted, onChunkDeleted)
script.on_event(defines.events.on_force_created, onForceCreated)
script.on_event(defines.events.on_forces_merged, onForceMerged)
script.on_event(defines.events.on_force_cease_fire_changed, onForceDiplomacyChanged)
script.on_event(defines.events.on_force_friends_changed, onForceDiplomacyChanged)
script.on_event(defines.events.on_unit_group_finished_gathering, onGroupFinishedGathering)
script.on_event(defines.events.on_build_base_arrived, onBuilderArrived)
-- + !КДА 2021.11
script.on_event(defines.events.on_sector_scanned, onSectorScanned)
--script.on_event(defines.events.on_script_trigger_effect, onScriptTriggerEffect)
script.on_event(defines.events.on_entity_damaged, onEntityDamaged, {
{filter="type", type="unit"},
{mode = "and", invert = true, filter="damage-type", type ="fire"},
{mode = "and", invert = true, filter="damage-type", type ="acid"},
{mode = "or", filter="original-damage-amount", value = 200, comparison = ">"},
{mode = "and", filter="type", type="unit"},
})
-- - !КДА 2021.11
script.on_event(defines.events.on_gui_click, on_gui_click)
local function setSurfaces(event)
create_surfaceIteraction_frame(game.players[event.player_index])
end
commands.add_command('rampantSetSurfaces', "", setSurfaces)
local function rampantSetAIState(event)
local surfaceIndex = game.players[event.player_index].surface.index
local map = universe.maps[surfaceIndex]
if not map then
local surfaceName = game.players[event.player_index].surface.name
game.print(surfaceName .. " is excluded from processing ")
return
end
game.print(map.surface.name .. " is in " .. constants.stateEnglish[map.state])
if event.parameter then
local target = tonumber(event.parameter)
if (target == nil) then
game.print("invalid param")
return
end
if (target ~= constants.AI_STATE_PEACEFUL
and target ~= constants.AI_STATE_AGGRESSIVE
and target ~= constants.AI_STATE_RAIDING
and target ~= constants.AI_STATE_MIGRATING
and target ~= constants.AI_STATE_SIEGE
and target ~= constants.AI_STATE_ONSLAUGHT
and target ~= constants.AI_STATE_GROWING
) then
game.print(target .. " is not a valid state")
return
else
map.state = target
if map.state == constants.AI_STATE_GROWING then
aiPlanning.updateBasesToGrow(map, game.tick, false)
end
game.print(map.surface.name .. " is now in " .. constants.stateEnglish[map.state])
end
end
end
commands.add_command('rampantSetAIState', "", rampantSetAIState)
local function rampantForceMutations(event)
local i = 0
local basesTotal = 0
for id, base in pairs(universe.bases) do
basesTotal = basesTotal + 1
if base and base.thisIsRampantEnemy then
base.tier = 0
i = i + 1
end
end
game.print("Bases forces to mutate: ".. i.."/"..basesTotal)
end
commands.add_command('rampantForceMutations', "", rampantForceMutations)
local function rampantCreateCompressedBiter(event)
local newEntity = game.players[event.player_index].surface.create_entity({
name = "small-biter",
position = {0, 0}
})
if newEntity then
local compressedUnits = universe.compressedUnits
compressedUnits[newEntity.unit_number] = {count = 10, entity = newEntity}
end
end
--commands.add_command('rampantCreateCompressedBiter', "", rampantCreateCompressedBiter)