From 4f4945971b7b2645cf0ff7c009e9ed4847be8525 Mon Sep 17 00:00:00 2001 From: Aaron Veden Date: Mon, 20 Nov 2017 23:27:03 -0800 Subject: [PATCH] working adjustments and refactoring --- README.md | 9 +- Upgrade.lua | 24 +- control.lua | 223 ++++------------ data.lua | 5 - info.json | 2 +- libs/AIAttackWave.lua | 50 ++-- libs/AIPredicates.lua | 15 +- libs/BaseRegisterUtils.lua | 118 --------- libs/BaseUtils.lua | 6 +- libs/BuildUtils.lua | 78 ------ libs/ChunkProcessor.lua | 43 ++-- libs/ChunkUtils.lua | 342 ++++++++++++++++++++++--- libs/Constants.lua | 47 +++- libs/EntityUtils.lua | 147 ----------- libs/InventoryUtils.lua | 46 ---- libs/MapProcessor.lua | 57 +++-- libs/MapUtils.lua | 102 ++++---- libs/MovementUtils.lua | 119 ++++++++- libs/NeighborUtils.lua | 119 --------- libs/NestUtils.lua | 16 +- libs/NocturnalUtils.lua | 21 -- libs/PheromoneUtils.lua | 82 +++--- libs/PlayerUtils.lua | 20 -- libs/SquadAttack.lua | 42 +-- libs/SquadDefense.lua | 43 ++-- libs/TendrilUtils.lua | 18 +- libs/WorldProcessor.lua | 83 ------ locale/en/locale.cfg | 8 +- make.rkt | 6 +- prototypes/buildings/ItemCollector.lua | 197 -------------- prototypes/buildings/UnitSpawners.lua | 2 +- prototypes/buildings/tunnel.lua | 2 +- prototypes/enemies/AttackAcidBall.lua | 317 +++++------------------ prototypes/enemies/AttackBobs.lua | 155 +---------- prototypes/enemies/AttackNE.lua | 14 +- prototypes/enemies/BiterUtils.lua | 38 ++- prototypes/enemies/StreamUtils.lua | 5 +- prototypes/enemies/UpdatesBobs.lua | 70 ++--- prototypes/enemies/UpdatesNE.lua | 32 +-- prototypes/enemies/UpdatesVanilla.lua | 30 ++- settings.lua | 11 - sounds/attacks/acid-end.ogg | Bin 0 -> 15838 bytes sounds/attacks/acid-mid.ogg | Bin 0 -> 21170 bytes tests.lua | 31 +-- 44 files changed, 966 insertions(+), 1829 deletions(-) delete mode 100755 libs/BaseRegisterUtils.lua delete mode 100755 libs/BuildUtils.lua delete mode 100755 libs/EntityUtils.lua delete mode 100755 libs/InventoryUtils.lua delete mode 100755 libs/NeighborUtils.lua delete mode 100755 libs/NocturnalUtils.lua delete mode 100755 libs/WorldProcessor.lua delete mode 100755 prototypes/buildings/ItemCollector.lua create mode 100755 sounds/attacks/acid-end.ogg create mode 100755 sounds/attacks/acid-mid.ogg diff --git a/README.md b/README.md index 171968ee..cfe754b5 100755 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ Configure Options not in game menu: - Pathfinding - Unit groups will use potential fields to perform only single step pathfinding allowing for efficient and dynamic pathing - Peace mode - If something sets peace mode, Rampant will respect it - Ion Cannon Reaction - Firing the Ion Cannon will cause nests around the blast site to form into an attack wave and agitate all biters -- Item Collector + Technology to unlock it - An entity that collects the items on the ground around itself # Planned Features @@ -61,6 +60,14 @@ Configure Options not in game menu: # Version History +0.15.23 - +- Fixed: Retreat radius being centered on chunk corner instead of on the unit dying +- Fixed: Spitter flamethrower sound fx +- Moved: Item Collector into separate mod +- Balance: Adjusted spitter and worms damages, ranges, and cooldowns to be more inline with vanilla +- Optimization: Reduced memory footprint for faster saving and loading +- Framework: Reorganization of files + 0.15.22 - - Contribution - Martok88, Improvement: Added optional attack wave message per player diff --git a/Upgrade.lua b/Upgrade.lua index 1823243d..50e40703 100755 --- a/Upgrade.lua +++ b/Upgrade.lua @@ -8,7 +8,6 @@ local mathUtils = require("libs/MathUtils") -- constants local INTERVAL_LOGIC = constants.INTERVAL_LOGIC -local INTERVAL_PROCESS = constants.INTERVAL_PROCESS -- imported functions @@ -16,7 +15,7 @@ local roundToNearest = mathUtils.roundToNearest -- module code -function upgrade.attempt(natives, world) +function upgrade.attempt(natives) local starting = global.version if (global.version == nil) then natives.squads = {} @@ -157,25 +156,14 @@ function upgrade.attempt(natives, world) game.surfaces[1].print("Rampant - Version 0.15.17") global.version = constants.VERSION_27 end - if (global.version < constants.VERSION_28) then + if (global.version < constants.VERSION_33) then - if (world == nil) then - global.world = {} - world = global.world - end - - world.itemCollectorLookup = {} - world.itemCollectorEvents = {} + global.world = nil - game.surfaces[1].print("Rampant - Version 0.15.18") - global.version = constants.VERSION_28 - end - if (global.version < constants.VERSION_32) then - - game.surfaces[1].print("Rampant - Version 0.15.22") - global.version = constants.VERSION_32 + game.surfaces[1].print("Rampant - Version 0.15.23") + global.version = constants.VERSION_33 end - return starting ~= global.version, natives, world + return starting ~= global.version, natives end function upgrade.compareTable(entities, option, new) diff --git a/control.lua b/control.lua index 63a0dc54..5b209c62 100755 --- a/control.lua +++ b/control.lua @@ -1,6 +1,5 @@ -- imports -local entityUtils = require("libs/EntityUtils") local mapUtils = require("libs/MapUtils") local unitGroupUtils = require("libs/UnitGroupUtils") local chunkProcessor = require("libs/ChunkProcessor") @@ -14,14 +13,10 @@ 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 baseRegisterUtils = require("libs/BaseRegisterUtils") local mathUtils = require("libs/MathUtils") local config = require("config") -local worldProcessor = require("libs/WorldProcessor") -local inventoryUtils = require("libs/InventoryUtils") -local playerUtils = require("libs/PlayerUtils") -local buildUtils = require("libs/BuildUtils") -- constants @@ -30,12 +25,7 @@ local INTERVAL_PROCESS = constants.INTERVAL_PROCESS local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE -local DEFINES_INVENTORY_PLAYER_MAIN = defines.inventory.player_main -local DEFINES_INVENTORY_GOD_MAIN = defines.inventory.god_main -local DEFINES_INVENTORY_PLAYER_QUICKBAR = defines.inventory.player_quickbar -local DEFINES_INVENTORY_GOD_QUICKBAR = defines.inventory.god_quickbar - -local ITEM_COLLECTOR_MAX_QUEUE_SIZE = constants.ITEM_COLLECTOR_MAX_QUEUE_SIZE +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK local AI_MAX_OVERFLOW_POINTS = constants.AI_MAX_OVERFLOW_POINTS @@ -49,17 +39,6 @@ local getChunkByPosition = mapUtils.getChunkByPosition local processPendingChunks = chunkProcessor.processPendingChunks -local buildComplexEntity = buildUtils.buildComplexEntity -local mineComplexEntity = buildUtils.mineComplexEntity - -local getPlayerCursorStack = playerUtils.getPlayerCursorStack - -local swapItemStack = inventoryUtils.swapItemStack -local swapItemInventory = inventoryUtils.swapItemInventory -local topOffHand = inventoryUtils.topOffHand - -local processWorld = worldProcessor.processWorld - local processMap = mapProcessor.processMap local processPlayers = mapProcessor.processPlayers local scanMap = mapProcessor.scanMap @@ -80,23 +59,22 @@ local squadsBeginAttack = squadAttack.squadsBeginAttack local retreatUnits = squadDefense.retreatUnits -local addRemovePlayerEntity = entityUtils.addRemovePlayerEntity -local unregisterEnemyBaseStructure = baseRegisterUtils.unregisterEnemyBaseStructure -local registerEnemyBaseStructure = baseRegisterUtils.registerEnemyBaseStructure -local makeImmortalEntity = entityUtils.makeImmortalEntity +local addRemovePlayerEntity = chunkUtils.addRemovePlayerEntity +local unregisterEnemyBaseStructure = chunkUtils.unregisterEnemyBaseStructure +local registerEnemyBaseStructure = chunkUtils.registerEnemyBaseStructure +local makeImmortalEntity = chunkUtils.makeImmortalEntity local processBases = baseProcessor.processBases local mRandom = math.random -local getPlayerInventory = playerUtils.getPlayerInventory +local positionToChunkXY = mapUtils.positionToChunkXY -- local references to global local regionMap -- 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 -local world -- manages the player built structures and any other events in the world -- hook functions @@ -110,13 +88,10 @@ local function onIonCannonFired(event) if (natives.points > AI_MAX_OVERFLOW_POINTS) then natives.points = AI_MAX_OVERFLOW_POINTS end - local chunk = getChunkByPosition(regionMap, event.position.x, event.position.y) - if chunk then - rallyUnits(chunk, - regionMap, - surface, - natives, - event.tick) + local chunkX, chunkY = positionToChunkXY(event.position) + local chunk = getChunkByPosition(regionMap, chunkX, chunkY) + if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then + rallyUnits(chunk, regionMap, surface, natives, event.tick) end end end @@ -134,7 +109,6 @@ local function onLoad() regionMap = global.regionMap natives = global.natives pendingChunks = global.pendingChunks - world = global.world hookEvents() end @@ -158,9 +132,28 @@ local function rebuildRegionMap() regionMap.processQueue = {} regionMap.processIndex = 1 regionMap.scanIndex = 1 + + regionMap.chunkToHives = {} + regionMap.chunkToNests = {} + regionMap.chunkToWorms = {} + regionMap.chunkToRetreats = {} + regionMap.chunkToRallys = {} + regionMap.chunkToPlayerBase = {} + regionMap.chunkToResource = {} + -- preallocating memory to be used in code, making it fast by reducing garbage generated. - regionMap.neighbors = { nil, nil, nil, nil, nil, nil, nil, nil } - regionMap.cardinalNeighbors = { nil, nil, nil, nil } + regionMap.neighbors = { SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK } + regionMap.cardinalNeighbors = { SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK, + SENTINEL_IMPASSABLE_CHUNK } regionMap.chunkTiles = {} regionMap.position = {x=0, y=0} @@ -242,7 +235,7 @@ end local function onConfigChanged() local upgraded - upgraded, natives, world = upgrade.attempt(natives, world) + upgraded, natives = upgrade.attempt(natives) if upgraded and onModSettingsChange(nil) then rebuildRegionMap() end @@ -271,8 +264,6 @@ local function onTick(event) cleanSquads(natives) regroupSquads(natives) - processWorld(surface, world, tick) - processPlayers(players, regionMap, surface, natives, tick) if natives.useCustomAI then @@ -288,7 +279,7 @@ local function onTick(event) end local function onBuild(event) - local entity = buildComplexEntity(event.created_entity, world) + local entity = event.created_entity addRemovePlayerEntity(regionMap, entity, natives, true, false) if natives.safeBuildings then if natives.safeEntities[entity.type] or natives.safeEntityName[entity.name] then @@ -298,24 +289,20 @@ local function onBuild(event) end local function onMine(event) - mineComplexEntity(addRemovePlayerEntity(regionMap, - event.entity, - natives, - false, - false), - world) + addRemovePlayerEntity(regionMap, event.entity, natives, false, false) end local function onDeath(event) local entity = event.entity local surface = entity.surface if (surface.index == 1) then + local entityPosition = entity.position + local chunkX, chunkY = positionToChunkXY(entityPosition) if (entity.force.name == "enemy") then if (entity.type == "unit") then - local entityPosition = entity.position - local deathChunk = getChunkByPosition(regionMap, entityPosition.x, entityPosition.y) + local deathChunk = getChunkByPosition(regionMap, chunkX, chunkY) - if deathChunk then + if (deathChunk ~= SENTINEL_IMPASSABLE_CHUNK) then -- drop death pheromone where unit died deathScent(deathChunk) @@ -324,18 +311,14 @@ local function onDeath(event) retreatUnits(deathChunk, entityPosition, - convertUnitGroupToSquad(natives, - entity.unit_group), + convertUnitGroupToSquad(natives, entity.unit_group), regionMap, surface, natives, tick) + if (mRandom() < natives.rallyThreshold) and not surface.peaceful_mode then - rallyUnits(deathChunk, - regionMap, - surface, - natives, - tick) + rallyUnits(deathChunk, regionMap, surface, natives, tick) end end end @@ -345,32 +328,24 @@ local function onDeath(event) 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) - if victoryChunk then + local victoryChunk = getChunkByPosition(regionMap, chunkX, chunkY) + if (victoryChunk ~= SENTINEL_IMPASSABLE_CHUNK) then victoryScent(victoryChunk, entity.type) end end if creditNatives and natives.safeBuildings and (natives.safeEntities[entity.type] or natives.safeEntityName[entity.name]) then makeImmortalEntity(surface, entity) else - mineComplexEntity(addRemovePlayerEntity(regionMap, - entity, - natives, - false, - creditNatives), - world, - true) + addRemovePlayerEntity(regionMap, entity, natives, false, creditNatives) end end end end local function onEnemyBaseBuild(event) - local entity = event.entity - registerEnemyBaseStructure(regionMap, entity, nil) + registerEnemyBaseStructure(regionMap, event.entity, nil) end local function onSurfaceTileChange(event) @@ -384,128 +359,30 @@ local function onInit() global.regionMap = {} global.pendingChunks = {} global.natives = {} - global.world = {} regionMap = global.regionMap natives = global.natives pendingChunks = global.pendingChunks - world = global.world onConfigChanged() hookEvents() end --- local function onBuildingRotated(event) - --- end - -local function onCursorChange(event) - if settings.startup["rampant-enableBuildings"].value then - local player = game.players[event.player_index] - swapItemStack(getPlayerCursorStack(player), - "item-collector-base-rampant", - "item-collector-base-overlay-rampant") - local inventory = getPlayerInventory(player, - DEFINES_INVENTORY_PLAYER_QUICKBAR, - DEFINES_INVENTORY_GOD_QUICKBAR) - topOffHand(inventory, - player.cursor_stack, - "item-collector-base-rampant", - "item-collector-base-overlay-rampant") - inventory = getPlayerInventory(player, - DEFINES_INVENTORY_PLAYER_MAIN, - DEFINES_INVENTORY_GOD_MAIN) - topOffHand(inventory, - player.cursor_stack, - "item-collector-base-rampant", - "item-collector-base-overlay-rampant") - end -end - -local function onPlayerDropped(event) - if settings.startup["rampant-enableBuildings"].value then - local item = event.entity - if item.valid then - swapItemStack(item.stack, - "item-collector-base-overlay-rampant", - "item-collector-base-rampant") - end - end -end - -local function onMainInventoryChanged(event) - if settings.startup["rampant-enableBuildings"].value then - local player = game.players[event.player_index] - local inventory = getPlayerInventory(player, - DEFINES_INVENTORY_PLAYER_MAIN, - DEFINES_INVENTORY_GOD_MAIN) - swapItemInventory(inventory, - "item-collector-base-overlay-rampant", - "item-collector-base-rampant") - end -end - -local function onQuickInventoryChanged(event) - if settings.startup["rampant-enableBuildings"].value then - local player = game.players[event.player_index] - local inventory = getPlayerInventory(player, - DEFINES_INVENTORY_PLAYER_QUICKBAR, - DEFINES_INVENTORY_GOD_QUICKBAR) - swapItemInventory(inventory, - "item-collector-base-overlay-rampant", - "item-collector-base-rampant") - topOffHand(inventory, - player.cursor_stack, - "item-collector-base-rampant", - "item-collector-base-overlay-rampant") - end -end - - -local function onScannedSector(event) - local radar = event.radar - if (radar.name == "item-collector-base-rampant") then - local count = #world.itemCollectorEvents - if (count <= ITEM_COLLECTOR_MAX_QUEUE_SIZE) then - world.itemCollectorEvents[count+1] = radar.unit_number - end - end -end - -- hooks script.on_init(onInit) script.on_load(onLoad) -script.on_event(defines.events.on_runtime_mod_setting_changed, - onModSettingsChange) +script.on_event(defines.events.on_runtime_mod_setting_changed, onModSettingsChange) script.on_configuration_changed(onConfigChanged) --- script.on_event(defines.events.on_player_rotated_entity, --- onBuildingRotated) - script.on_event(defines.events.on_player_built_tile, onSurfaceTileChange) -script.on_event(defines.events.on_player_cursor_stack_changed, - onCursorChange) - -script.on_event(defines.events.on_player_main_inventory_changed, - onMainInventoryChanged) -script.on_event(defines.events.on_player_quickbar_inventory_changed, - onQuickInventoryChanged) - -script.on_event(defines.events.on_biter_base_built, - onEnemyBaseBuild) +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) + defines.events.on_robot_mined_entity}, onMine) script.on_event({defines.events.on_built_entity, - defines.events.on_robot_built_entity}, - onBuild) -script.on_event(defines.events.on_player_dropped_item, - onPlayerDropped) + defines.events.on_robot_built_entity}, onBuild) -script.on_event(defines.events.on_sector_scanned, - onScannedSector) 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) diff --git a/data.lua b/data.lua index 811c85c1..cb2ce0f1 100755 --- a/data.lua +++ b/data.lua @@ -11,8 +11,3 @@ require("prototypes/enemies/UnitFireSpitters") require("prototypes/enemies/UnitTendril") -if settings.startup["rampant-enableBuildings"].value then - require("prototypes/buildings/ItemCollector") -end - - diff --git a/info.json b/info.json index 107f3602..c04bad47 100755 --- a/info.json +++ b/info.json @@ -1,7 +1,7 @@ { "name" : "Rampant", "factorio_version" : "0.15", - "version" : "0.15.22", + "version" : "0.15.23", "title" : "Rampant", "author" : "Veden", "homepage" : "https://forums.factorio.com/viewtopic.php?f=94&t=31445", diff --git a/libs/AIAttackWave.lua b/libs/AIAttackWave.lua index ab6a1c05..975b9ec1 100755 --- a/libs/AIAttackWave.lua +++ b/libs/AIAttackWave.lua @@ -4,8 +4,9 @@ local aiAttackWave = {} local constants = require("Constants") local mapUtils = require("MapUtils") +local chunkUtils = require("ChunkUtils") local unitGroupUtils = require("UnitGroupUtils") -local neighborUtils = require("NeighborUtils") +local movementUtils = require("MovementUtils") package.path = "../?.lua;" .. package.path local config = require("config") @@ -18,19 +19,21 @@ local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE local AI_SQUAD_COST = constants.AI_SQUAD_COST local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST -local RALLY_TRIGGERED = constants.RALLY_TRIGGERED local INTERVAL_LOGIC = constants.INTERVAL_LOGIC local TRIPLE_CHUNK_SIZE = constants.TRIPLE_CHUNK_SIZE -local PASSABLE = constants.PASSABLE local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS +local CHUNK_SIZE = constants.CHUNK_SIZE + local RALLY_CRY_DISTANCE = constants.RALLY_CRY_DISTANCE local DEFINES_COMMAND_GROUP = defines.command.group local DEFINES_DISTRACTION_NONE = defines.distraction.none -local NEST_COUNT = constants.NEST_COUNT +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK + +local PASSABLE = constants.PASSABLE -- imported functions @@ -38,9 +41,13 @@ local mRandom = math.random local positionFromDirectionAndChunk = mapUtils.positionFromDirectionAndChunk +local getNestCount = chunkUtils.getNestCount +local getRallyTick = chunkUtils.getRallyTick +local setRallyTick = chunkUtils.setRallyTick + local getNeighborChunks = mapUtils.getNeighborChunks -local getChunkByIndex = mapUtils.getChunkByIndex -local scoreNeighborsForFormation = neighborUtils.scoreNeighborsForFormation +local getChunkByPosition = mapUtils.getChunkByPosition +local scoreNeighborsForFormation = movementUtils.scoreNeighborsForFormation local createSquad = unitGroupUtils.createSquad local attackWaveScaling = config.attackWaveScaling @@ -74,21 +81,21 @@ local function scoreUnitGroupLocation(neighborChunk) return neighborChunk[PLAYER_PHEROMONE] + neighborChunk[MOVEMENT_PHEROMONE] + neighborChunk[BASE_PHEROMONE] end -local function validUnitGroupLocation(neighborChunk) - return (neighborChunk[PASSABLE] == CHUNK_ALL_DIRECTIONS) and (neighborChunk[NEST_COUNT] == 0) +local function validUnitGroupLocation(regionMap, neighborChunk) + return neighborChunk[PASSABLE] == CHUNK_ALL_DIRECTIONS and (getNestCount(regionMap, neighborChunk) == 0) end function aiAttackWave.rallyUnits(chunk, regionMap, surface, natives, tick) - if ((tick - chunk[RALLY_TRIGGERED] > INTERVAL_LOGIC) and (natives.points >= AI_VENGENCE_SQUAD_COST) and + if ((tick - getRallyTick(regionMap, chunk) > INTERVAL_LOGIC) and (natives.points >= AI_VENGENCE_SQUAD_COST) and (#natives.squads < natives.maxSquads)) then - chunk[RALLY_TRIGGERED] = tick - local cX = chunk.cX - local cY = chunk.cY - for x=cX - RALLY_CRY_DISTANCE, cX + RALLY_CRY_DISTANCE do - for y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE do + setRallyTick(regionMap, chunk, tick) + local cX = chunk.x + local cY = chunk.y + for x=cX - RALLY_CRY_DISTANCE, cX + RALLY_CRY_DISTANCE, 32 do + for y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE, 32 do if (x ~= cX) and (y ~= cY) then - local rallyChunk = getChunkByIndex(regionMap, x, y) - if rallyChunk and (rallyChunk[NEST_COUNT] ~= 0) then + local rallyChunk = getChunkByPosition(regionMap, x, y) + if (rallyChunk ~= SENTINEL_IMPASSABLE_CHUNK) and (getNestCount(regionMap, rallyChunk) > 0) then aiAttackWave.formSquads(regionMap, surface, natives, rallyChunk, AI_VENGENCE_SQUAD_COST) if (natives.points < AI_VENGENCE_SQUAD_COST) and (#natives.squads < natives.maxSquads) then return @@ -105,16 +112,17 @@ function aiAttackWave.formSquads(regionMap, surface, natives, chunk, cost) if valid and (mRandom() < natives.formSquadThreshold) then - local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(regionMap, chunk.cX, chunk.cY), + local squadPath, squadDirection = scoreNeighborsForFormation(getNeighborChunks(regionMap, chunk.x, chunk.y), validUnitGroupLocation, - scoreUnitGroupLocation) - if squadPath then - local squadPosition = surface.find_non_colliding_position("biter-spawner-hive", + scoreUnitGroupLocation, + regionMap) + if (squadPath ~= SENTINEL_IMPASSABLE_CHUNK) then + local squadPosition = surface.find_non_colliding_position("biter-spawner-hive-rampant", positionFromDirectionAndChunk(squadDirection, chunk, regionMap.position, 0.98), - 32, + CHUNK_SIZE, 4) if squadPosition then local squad = createSquad(squadPosition, surface, natives) diff --git a/libs/AIPredicates.lua b/libs/AIPredicates.lua index ca61ef51..6fde0b28 100755 --- a/libs/AIPredicates.lua +++ b/libs/AIPredicates.lua @@ -3,21 +3,28 @@ local aiPredicates = {} -- imports local constants = require("Constants") -local nocturnalUtils = require("NocturnalUtils") -- constants local AI_STATE_AGGRESSIVE = constants.AI_STATE_AGGRESSIVE +local AI_STATE_NOCTURNAL = constants.AI_STATE_NOCTURNAL -- imported functions -local canAttackNocturnal = nocturnalUtils.canAttack - -- module code function aiPredicates.canAttack(natives, surface) - return (((natives.state == AI_STATE_AGGRESSIVE) or canAttackNocturnal(natives, surface)) + return (((natives.state == AI_STATE_AGGRESSIVE) or aiPredicates.canAttackDark(natives, surface)) and not surface.peaceful_mode and (#natives.squads < natives.maxSquads)) end +function aiPredicates.isDark(surface) + return surface.darkness > 0.65 +end + +function aiPredicates.canAttackDark(natives, surface) + return aiPredicates.isDark(surface) and natives.state == AI_STATE_NOCTURNAL +end + + return aiPredicates diff --git a/libs/BaseRegisterUtils.lua b/libs/BaseRegisterUtils.lua deleted file mode 100755 index 3cb7c0d9..00000000 --- a/libs/BaseRegisterUtils.lua +++ /dev/null @@ -1,118 +0,0 @@ -local baseRegisterUtils = {} - --- imports - -local constants = require("Constants") - -local entityUtils = require("EntityUtils") - --- constants - -local NEST_COUNT = constants.NEST_COUNT -local WORM_COUNT = constants.WORM_COUNT - -local NEST_BASE = constants.NEST_BASE -local WORM_BASE = constants.WORM_BASE - --- imported functions - -local getEntityOverlapChunks = entityUtils.getEntityOverlapChunks - --- module code - -function baseRegisterUtils.addEnemyStructureToChunk(chunk, entity, base) - local indexChunk - local indexBase - local countChunk - if (entity.type == "unit-spawner") then - indexChunk = chunk[NEST_BASE] - if base then - if base.hives[entity.unit_number] then - indexBase = base.hives - else - indexBase = base.nests - end - end - countChunk = NEST_COUNT - elseif (entity.type == "turret") then - indexChunk = chunk[WORM_BASE] - if base then - indexBase = base.worms - end - countChunk = WORM_COUNT - end - chunk[countChunk] = chunk[countChunk] + 1 - if indexBase then - indexChunk[entity.unit_number] = base - indexBase[entity.unit_number] = entity - end -end - -function baseRegisterUtils.removeEnemyStructureFromChunk(chunk, entity) - local indexChunk - local countChunk - if (entity.type == "unit-spawner") then - indexChunk = chunk[NEST_BASE] - countChunk = NEST_COUNT - elseif (entity.type == "turret") then - indexChunk = chunk[WORM_BASE] - countChunk = WORM_COUNT - end - local base = indexChunk[entity.unit_number] - local indexBase - if base then - if (entity.type == "unit-spawner") then - if base.hives[entity.unit_number] then - indexBase = base.hives - else - indexBase = base.nests - end - elseif (entity.type == "turret") then - indexBase = base.worms - end - indexBase[entity.unit_number] = nil - end - chunk[countChunk] = chunk[countChunk] - 1 -end - -function baseRegisterUtils.registerEnemyBaseStructure(regionMap, entity, base) - local entityType = entity.type - if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then - local leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity) - - if leftTop then - baseRegisterUtils.addEnemyStructureToChunk(leftTop, entity, base) - end - if rightTop then - baseRegisterUtils.addEnemyStructureToChunk(rightTop, entity, base) - end - if leftBottom then - baseRegisterUtils.addEnemyStructureToChunk(leftBottom, entity, base) - end - if rightBottom then - baseRegisterUtils.addEnemyStructureToChunk(rightBottom, entity, base) - end - end -end - -function baseRegisterUtils.unregisterEnemyBaseStructure(regionMap, entity) - local entityType = entity.type - if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then - local leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity) - - if leftTop then - baseRegisterUtils.removeEnemyStructureFromChunk(leftTop, entity) - end - if rightTop then - baseRegisterUtils.removeEnemyStructureFromChunk(rightTop, entity) - end - if leftBottom then - baseRegisterUtils.removeEnemyStructureFromChunk(leftBottom, entity) - end - if rightBottom then - baseRegisterUtils.removeEnemyStructureFromChunk(rightBottom, entity) - end - end -end - -return baseRegisterUtils diff --git a/libs/BaseUtils.lua b/libs/BaseUtils.lua index 73a6c658..512c5ac7 100755 --- a/libs/BaseUtils.lua +++ b/libs/BaseUtils.lua @@ -5,7 +5,7 @@ local baseUtils = {} local mathUtils = require("MathUtils") local constants = require("Constants") -local tendrilUtils = require("TendrilUtils") +-- local tendrilUtils = require("TendrilUtils") local nestUtils = require("NestUtils") @@ -26,7 +26,7 @@ local buildHive = nestUtils.buildHive local mFloor = math.floor -local buildTendril = tendrilUtils.buildTendril +-- local buildTendril = tendrilUtils.buildTendril local mRandom = math.random @@ -68,7 +68,7 @@ function baseUtils.createBase(regionMap, natives, position, surface, tick) if not buildHive(regionMap, base, surface) then return nil end - buildTendril(regionMap, natives, base, surface, tick) + -- buildTendril(regionMap, natives, base, surface, tick) bases[#bases+1] = base return base end diff --git a/libs/BuildUtils.lua b/libs/BuildUtils.lua deleted file mode 100755 index 854a7535..00000000 --- a/libs/BuildUtils.lua +++ /dev/null @@ -1,78 +0,0 @@ -local buildUtils = {} - --- module code - -function buildUtils.buildComplexEntity(entity, world) - if (entity.name == "item-collector-base-overlay-rampant") or (entity.name == "item-collector-base-rampant") then - local surface = entity.surface - local x = entity.position.x - local y = entity.position.y - local chest - local position = entity.position - local force = entity.force - if (entity.name == "item-collector-base-overlay-rampant") then - entity.destroy() - chest = surface.create_entity({name = "item-collector-chest-rampant", - position = position, - force = force}) - entity = surface.create_entity({name="item-collector-base-rampant", - position = position, - force = force}) - else - local ghosts = surface.find_entities_filtered({name="entity-ghost", - area={{x=x-1, - y=y-1}, - {x=x+1, - y=y+1}}, - limit=1}) - if (#ghosts > 0) then - local ghost = ghosts[1] - if (ghost.ghost_name == "item-collector-chest-rampant") then - local conflicts - conflicts, chest = ghost.revive() - end - else - chest = surface.create_entity({name = "item-collector-chest-rampant", - position = position, - force = force}) - end - end - if chest and chest.valid then - local pair = { chest, entity } - world.itemCollectorLookup[chest.unit_number] = pair - world.itemCollectorLookup[entity.unit_number] = pair - entity.backer_name = "" - end - end - return entity -end - -function buildUtils.mineComplexEntity(entity, world, destroyed) - if (entity.name == "item-collector-chest-rampant") or (entity.name == "item-collector-base-rampant") then - local pair = world.itemCollectorLookup[entity.unit_number] - if pair then - local chest = pair[1] - local dish = pair[2] - - if chest and chest.valid then - world.itemCollectorLookup[chest.unit_number] = nil - if destroyed and (entity == chest) then - chest.die() - elseif (entity ~= chest) then - chest.destroy() - end - end - if dish and dish.valid then - world.itemCollectorLookup[dish.unit_number] = nil - if destroyed and (entity ~= dish) then - dish.die() - elseif (entity ~= dish) then - dish.destroy() - end - end - end - end - return entity -end - -return buildUtils diff --git a/libs/ChunkProcessor.lua b/libs/ChunkProcessor.lua index 4b821b2c..e4edea07 100755 --- a/libs/ChunkProcessor.lua +++ b/libs/ChunkProcessor.lua @@ -9,6 +9,8 @@ local constants = require("Constants") local CHUNK_SIZE = constants.CHUNK_SIZE +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK + -- imported functions local remakeChunk = chunkUtils.remakeChunk @@ -39,26 +41,31 @@ function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendin local x = topLeft.x local y = topLeft.y local chunk = createChunk(x, y) - - areaBoundingBox[1] = chunk - offset[1] = x + CHUNK_SIZE - offset[2] = y + CHUNK_SIZE - local chunkX = chunk.cX - - if regionMap[chunkX] == nil then - regionMap[chunkX] = {} - end - regionMap[chunkX][chunk.cY] = chunk - - checkChunkPassability(chunkTiles, chunk, surface) - if vanillaAI then - registerChunkEnemies(chunk, surface, query) - else - remakeChunk(regionMap, chunk, surface, natives, tick, query) + chunk = checkChunkPassability(chunkTiles, chunk, surface) + + if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then + + areaBoundingBox[1] = chunk + offset[1] = x + CHUNK_SIZE + offset[2] = y + CHUNK_SIZE + + if vanillaAI then + registerChunkEnemies(regionMap, chunk, surface, query) + else + remakeChunk(regionMap, chunk, surface, natives, tick, query) + end + scoreChunk(regionMap, chunk, surface, natives, query) + + local chunkX = chunk.x + + if regionMap[chunkX] == nil then + regionMap[chunkX] = {} + end + regionMap[chunkX][chunk.y] = chunk + + processQueue[#processQueue+1] = chunk end - scoreChunk(chunk, surface, natives, query) - processQueue[#processQueue+1] = chunk end end diff --git a/libs/ChunkUtils.lua b/libs/ChunkUtils.lua index 33db8746..bac91e74 100755 --- a/libs/ChunkUtils.lua +++ b/libs/ChunkUtils.lua @@ -3,31 +3,25 @@ local chunkUtils = {} -- imports local constants = require("Constants") +local mapUtils = require("MapUtils") -local baseUtils = require("BaseUtils") +-- constants -local baseRegisterUtils = require("BaseRegisterUtils") +local DEFINES_DIRECTION_EAST = defines.direction.east +local DEFINES_WIRE_TYPE_RED = defines.wire_type.red +local DEFINES_WIRE_TYPE_GREEN = defines.wire_type.green --- constants +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK local BASE_PHEROMONE = constants.BASE_PHEROMONE local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES -local NEST_BASE = constants.NEST_BASE -local WORM_BASE = constants.WORM_BASE -local NEST_COUNT = constants.NEST_COUNT -local WORM_COUNT = constants.WORM_COUNT - -local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR -local RESOURCE_GENERATOR = constants.RESOURCE_GENERATOR local CHUNK_NORTH_SOUTH = constants.CHUNK_NORTH_SOUTH local CHUNK_EAST_WEST = constants.CHUNK_EAST_WEST -local PASSABLE = constants.PASSABLE - local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE @@ -35,14 +29,13 @@ local CHUNK_TICK = constants.CHUNK_TICK local PATH_RATING = constants.PATH_RATING -local RETREAT_TRIGGERED = constants.RETREAT_TRIGGERED -local RALLY_TRIGGERED = constants.RALLY_TRIGGERED +local PASSABLE = constants.PASSABLE -- imported functions -local findNearbyBase = baseUtils.findNearbyBase -local createBase = baseUtils.createBase -local addEnemyStructureToChunk = baseRegisterUtils.addEnemyStructureToChunk +local getChunkByPosition = mapUtils.getChunkByPosition + +local mFloor = math.floor -- module code @@ -109,6 +102,126 @@ local function spotCheck(x, y, get_tile) return valid end +local function addEnemyStructureToChunk(regionMap, chunk, entity, base) + local lookup + if (entity.type == "unit-spawner") then + lookup = regionMap.chunkToNests + elseif (entity.type == "turret") then + lookup = regionMap.chunkToWorms + else + return + end + + local entityCollection = lookup[chunk] + if not entityCollection then + entityCollection = {} + lookup[chunk] = entityCollection + end + entityCollection[#entityCollection+1] = entity + + -- if base then + -- local baseCollection = regionMap.chunkToBases[chunk] + -- if not baseCollection then + -- baseCollection = {} + -- regionMap.chunkToBases[chunk] = baseCollection + -- end + -- baseCollection[base.id] = chunk + -- end +end + +local function removeEnemyStructureFromChunk(regionMap, chunk, entity) + local lookup + if (entity.type == "unit-spawner") then + lookup = regionMap.chunkToNests + elseif (entity.type == "turret") then + lookup = regionMap.chunkToWorms + else + return + end + -- local base = indexChunk[entity.unit_number] + -- local indexBase + -- if base then + -- if (entity.type == "unit-spawner") then + -- if base.hives[entity.unit_number] then + -- indexBase = base.hives + -- else + -- indexBase = base.nests + -- end + -- elseif (entity.type == "turret") then + -- indexBase = base.worms + -- end + -- indexBase[entity.unit_number] = nil + -- end + local collection = lookup[chunk] + if collection then + for i=1,#collection do + local e = collection[i] + if e.valid and (e == entity) then + table.remove(collection, i) + break + end + end + end +end + +local function getEntityOverlapChunks(regionMap, entity) + local boundingBox = entity.prototype.selection_box or entity.prototype.collision_box; + + local leftTopChunk = SENTINEL_IMPASSABLE_CHUNK + local rightTopChunk = SENTINEL_IMPASSABLE_CHUNK + local leftBottomChunk = SENTINEL_IMPASSABLE_CHUNK + local rightBottomChunk = SENTINEL_IMPASSABLE_CHUNK + + if (boundingBox ~= nil) then + local center = entity.position + local topXOffset + local topYOffset + + local bottomXOffset + local bottomYOffset + + if (entity.direction == DEFINES_DIRECTION_EAST) then + topXOffset = boundingBox.left_top.y + topYOffset = boundingBox.left_top.x + bottomXOffset = boundingBox.right_bottom.y + bottomYOffset = boundingBox.right_bottom.x + else + topXOffset = boundingBox.left_top.x + topYOffset = boundingBox.left_top.y + bottomXOffset = boundingBox.right_bottom.x + bottomYOffset = boundingBox.right_bottom.y + end + + local leftTopChunkX = mFloor(center.x + topXOffset) + local leftTopChunkY = mFloor(center.y + topYOffset) + + -- used to force things on chunk boundary to not spill over 0.0001 + local rightTopChunkX = mFloor(center.x + bottomXOffset - 0.0001) + local rightTopChunkY = leftTopChunkY + + -- used to force things on chunk boundary to not spill over 0.0001 + local leftBottomChunkX = leftTopChunkX + local leftBottomChunkY = mFloor(center.y + bottomYOffset - 0.0001) + + local rightBottomChunkX = rightTopChunkX + local rightBottomChunkY = leftBottomChunkY + + leftTopChunk = getChunkByPosition(regionMap, leftTopChunkX, leftTopChunkY) + if (leftTopChunkX ~= rightTopChunkX) then + rightTopChunk = getChunkByPosition(regionMap, rightTopChunkX, rightTopChunkY) + end + if (leftTopChunkY ~= leftBottomChunkY) then + leftBottomChunk = getChunkByPosition(regionMap, leftBottomChunkX, leftBottomChunkY) + end + if (leftTopChunkX ~= rightBottomChunkX) and (leftTopChunkY ~= rightBottomChunkY) then + rightBottomChunk = getChunkByPosition(regionMap, rightBottomChunkX, rightBottomChunkY) + end + end + return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk +end + +-- external functions + function chunkUtils.checkChunkPassability(chunkTiles, chunk, surface) local x = chunk.x local y = chunk.y @@ -139,12 +252,17 @@ function chunkUtils.checkChunkPassability(chunkTiles, chunk, surface) pass = CHUNK_NORTH_SOUTH else pass = CHUNK_IMPASSABLE - end + end end else chunk[PATH_RATING] = 1 end - chunk[PASSABLE] = pass + if (pass == CHUNK_IMPASSABLE) then + return SENTINEL_IMPASSABLE_CHUNK + else + chunk[PASSABLE] = pass + return chunk + end end function chunkUtils.remakeChunk(regionMap, chunk, surface, natives, tick, tempQuery) @@ -155,7 +273,7 @@ function chunkUtils.remakeChunk(regionMap, chunk, surface, natives, tick, tempQu for f=1, #enemies do local enemy = enemies[f] local entityType = enemies[f].type - if not ((enemy.name == "small-tendril-biter-rampant") or (enemy.name == "biter-spawner-hive")) then + if not ((enemy.name == "small-tendril-biter-rampant") or (enemy.name == "biter-spawner-hive-rampant")) then if (entityType == "unit-spawner") then points = points + 3 elseif (entityType == "turret") then @@ -166,13 +284,13 @@ function chunkUtils.remakeChunk(regionMap, chunk, surface, natives, tick, tempQu enemy.destroy() end end - local foundBase = findNearbyBase(natives, chunk) or createBase(regionMap, natives, chunk, surface, tick) - if foundBase then - foundBase.upgradePoints = foundBase.upgradePoints + points - end + -- local foundBase = findNearbyBase(natives, chunk) or createBase(regionMap, natives, chunk, surface, tick) + -- if foundBase then + -- foundBase.upgradePoints = foundBase.upgradePoints + points + -- end end -function chunkUtils.registerChunkEnemies(chunk, surface, tempQuery) +function chunkUtils.registerChunkEnemies(regionMap, chunk, surface, tempQuery) tempQuery.force = "enemy" local enemies = surface.find_entities_filtered(tempQuery) @@ -180,15 +298,61 @@ function chunkUtils.registerChunkEnemies(chunk, surface, tempQuery) local enemy = enemies[i] local enemyType = enemy.type if (enemyType == "unit-spawner") or (enemyType == "turret") then - addEnemyStructureToChunk(chunk, enemy, nil) + addEnemyStructureToChunk(regionMap, chunk, enemy, nil) end end end -function chunkUtils.scoreChunk(chunk, surface, natives, tempQuery) +function chunkUtils.getNestCount(regionMap, chunk) + local nests = regionMap.chunkToNests[chunk] + return (nests and #nests) or 0 +end + +function chunkUtils.getWormCount(regionMap, chunk) + local worms = regionMap.chunkToWorms[chunk] + return (worms and #worms) or 0 +end + +function chunkUtils.getRetreatTick(regionMap, chunk) + return regionMap.chunkToRetreats[chunk] or 0 +end + +function chunkUtils.getRallyTick(regionMap, chunk) + return regionMap.chunkToRallys[chunk] or 0 +end + +function chunkUtils.setRallyTick(regionMap, chunk, tick) + regionMap.chunkToRallys[chunk] = tick +end + +function chunkUtils.setRetreatTick(regionMap, chunk, tick) + regionMap.chunkToRetreats[chunk] = tick +end + +function chunkUtils.setResourceGenerator(regionMap, chunk, playerGenerator) + regionMap.chunkToResource[chunk] = playerGenerator +end + +function chunkUtils.getResourceGenerator(regionMap, chunk) + return regionMap.chunkToResource[chunk] or 0 +end + +function chunkUtils.getPlayerBaseGenerator(regionMap, chunk) + return regionMap.chunkToPlayerBase[chunk] or 0 +end + +function chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerGenerator) + regionMap.chunkToPlayerBase[chunk] = playerGenerator +end + +function chunkUtils.addPlayerBaseGenerator(regionMap, chunk, playerGenerator) + regionMap.chunkToPlayerBase[chunk] = regionMap.chunkToPlayerBase[chunk] + playerGenerator +end + +function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, tempQuery) tempQuery.force = nil tempQuery.type = "resource" - chunk[RESOURCE_GENERATOR] = surface.count_entities_filtered(tempQuery) * 0.001 + chunkUtils.setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(tempQuery) * 0.001) tempQuery.type = nil tempQuery.force = "player" @@ -207,36 +371,27 @@ function chunkUtils.scoreChunk(chunk, surface, natives, tempQuery) end local entityScore = BUILDING_PHEROMONES[entityType] - if (entityScore ~= nil) then + if entityScore then playerObjects = playerObjects + entityScore end end - chunk[PLAYER_BASE_GENERATOR] = playerObjects + chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerObjects) end function chunkUtils.createChunk(topX, topY) local chunk = { x = topX, - y = topY, - cX = topX * 0.03125, - cY = topY * 0.03125 + y = topY } chunk[MOVEMENT_PHEROMONE] = 0 chunk[BASE_PHEROMONE] = 0 chunk[PLAYER_PHEROMONE] = 0 chunk[RESOURCE_PHEROMONE] = 0 - chunk[PLAYER_BASE_GENERATOR] = 0 - chunk[RESOURCE_GENERATOR] = 0 chunk[PASSABLE] = 0 chunk[CHUNK_TICK] = 0 - chunk[RETREAT_TRIGGERED] = 0 - chunk[RALLY_TRIGGERED] = 0 - chunk[NEST_BASE] = {} - chunk[WORM_BASE] = {} - chunk[NEST_COUNT] = 0 - chunk[WORM_COUNT] = 0 chunk[PATH_RATING] = 0 + return chunk end @@ -250,4 +405,109 @@ function chunkUtils.colorChunk(x, y, tileType, surface) surface.set_tiles(tiles, false) end +function chunkUtils.registerEnemyBaseStructure(regionMap, entity, base) + local entityType = entity.type + if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then + local leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity) + + if (leftTop ~= SENTINEL_IMPASSABLE_CHUNK) then + addEnemyStructureToChunk(regionMap, leftTop, entity, base) + end + if (rightTop ~= SENTINEL_IMPASSABLE_CHUNK) then + addEnemyStructureToChunk(regionMap, rightTop, entity, base) + end + if (leftBottom ~= SENTINEL_IMPASSABLE_CHUNK) then + addEnemyStructureToChunk(regionMap, leftBottom, entity, base) + end + if (rightBottom ~= SENTINEL_IMPASSABLE_CHUNK) then + addEnemyStructureToChunk(regionMap, rightBottom, entity, base) + end + end +end + +function chunkUtils.unregisterEnemyBaseStructure(regionMap, entity) + local entityType = entity.type + if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then + local leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity) + + if (leftTop ~= SENTINEL_IMPASSABLE_CHUNK) then + removeEnemyStructureFromChunk(regionMap, leftTop, entity) + end + if (rightTop ~= SENTINEL_IMPASSABLE_CHUNK) then + removeEnemyStructureFromChunk(regionMap, rightTop, entity) + end + if (leftBottom ~= SENTINEL_IMPASSABLE_CHUNK) then + removeEnemyStructureFromChunk(regionMap, leftBottom, entity) + end + if (rightBottom ~= SENTINEL_IMPASSABLE_CHUNK) then + removeEnemyStructureFromChunk(regionMap, rightBottom, entity) + end + end +end + +function chunkUtils.addRemovePlayerEntity(regionMap, entity, natives, addObject, creditNatives) + local leftTop, rightTop, leftBottom, rightBottom + local entityValue + if (BUILDING_PHEROMONES[entity.type] ~= nil) and (entity.force.name == "player") then + entityValue = BUILDING_PHEROMONES[entity.type] + + leftTop, rightTop, leftBottom, rightBottom = getEntityOverlapChunks(regionMap, entity) + if not addObject then + if creditNatives then + natives.points = natives.points + entityValue + end + entityValue = -entityValue + end + if (leftTop ~= SENTINEL_IMPASSABLE_CHUNK) then + chunkUtils.addPlayerBaseGenerator(regionMap, leftTop, entityValue) + end + if (rightTop ~= SENTINEL_IMPASSABLE_CHUNK) then + chunkUtils.addPlayerBaseGenerator(regionMap, rightTop, entityValue) + end + if (leftBottom ~= SENTINEL_IMPASSABLE_CHUNK) then + chunkUtils.addPlayerBaseGenerator(regionMap, leftBottom, entityValue) + end + if (rightBottom ~= SENTINEL_IMPASSABLE_CHUNK) then + chunkUtils.addPlayerBaseGenerator(regionMap, rightBottom, entityValue) + end + end + return entity +end + +function chunkUtils.makeImmortalEntity(surface, entity) + local repairPosition = entity.position + local repairName = entity.name + local repairForce = entity.force + local repairDirection = entity.direction + + local wires + if (entity.type == "electric-pole") then + wires = entity.neighbours + end + entity.destroy() + local newEntity = surface.create_entity({position=repairPosition, + name=repairName, + direction=repairDirection, + force=repairForce}) + if wires then + for connectType,neighbourGroup in pairs(wires) do + if connectType == "copper" then + for _,v in pairs(neighbourGroup) do + newEntity.connect_neighbour(v); + end + elseif connectType == "red" then + for _,v in pairs(neighbourGroup) do + newEntity.connect_neighbour({wire = DEFINES_WIRE_TYPE_RED, target_entity = v}); + end + elseif connectType == "green" then + for _,v in pairs(neighbourGroup) do + newEntity.connect_neighbour({wire = DEFINES_WIRE_TYPE_GREEN, target_entity = v}); + end + end + end + end + + newEntity.destructible = false +end + return chunkUtils diff --git a/libs/Constants.lua b/libs/Constants.lua index 3e862978..8de85f0a 100755 --- a/libs/Constants.lua +++ b/libs/Constants.lua @@ -15,10 +15,11 @@ constants.VERSION_25 = 25 constants.VERSION_26 = 26 constants.VERSION_27 = 27 constants.VERSION_28 = 28 -constants.VERSION_32 = 32 +constants.VERSION_33 = 33 -- misc +constants.CHUNK_SIZE_DIVIDER = 0.03125 constants.MAGIC_MAXIMUM_NUMBER = 1e99 -- used in loops trying to find the lowest/highest score constants.MAGIC_MAXIMUM_BASE_NUMBER = 100000000 constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL = 10000 @@ -28,7 +29,7 @@ constants.SCAN_QUEUE_SIZE = 5 constants.ITEM_COLLECTOR_QUEUE_SIZE = 6 constants.BASE_QUEUE_SIZE = 1 constants.SQUAD_QUEUE_SIZE = 2 -constants.PROCESS_PLAYER_BOUND = 4 +constants.PROCESS_PLAYER_BOUND = 128 constants.ITEM_COLLECTOR_MAX_QUEUE_SIZE = 20 @@ -42,6 +43,18 @@ constants.PLAYER_PHEROMONE_MULTIPLER = 100 constants.DEV_CUSTOM_AI = false +-- mask + +constants.MASK_PASSABLE = 3 +constants.MASK_PASSABLE_SIZE = 2 +constants.MASK_NEST_COUNT = 511 +constants.MASK_NEST_COUNT_SIZE = 9 +constants.MASK_WORM_COUNT = 511 +constants.MASK_WORM_COUNT_SIZE = 9 +constants.MASK_PASSABLE_AND_NEST_COUNT = 2047 +constants.MASK_PASSABLE_AND_NEST_COUNT_SIZE = 11 + + -- item collector constants.ITEM_COLLECTOR_DISTANCE = 50 @@ -127,19 +140,11 @@ constants.BASE_PHEROMONE = 2 constants.PLAYER_PHEROMONE = 3 constants.RESOURCE_PHEROMONE = 4 -constants.PLAYER_BASE_GENERATOR = 5 -constants.RESOURCE_GENERATOR = 6 +constants.PASSABLE = 5 -constants.PASSABLE = 7 +constants.CHUNK_TICK = 6 -constants.CHUNK_TICK = 8 -constants.RETREAT_TRIGGERED = 9 -constants.RALLY_TRIGGERED = 10 -constants.NEST_BASE = 11 -constants.WORM_BASE = 12 -constants.NEST_COUNT = 13 -constants.WORM_COUNT = 14 -constants.PATH_RATING = 15 +constants.PATH_RATING = 7 -- Squad status @@ -155,7 +160,7 @@ constants.RETREAT_GRAB_RADIUS = 24 constants.BASE_RALLY_CHANCE = 0.02 constants.BONUS_RALLY_CHANCE = 0.06 -constants.RALLY_CRY_DISTANCE = 3 +constants.RALLY_CRY_DISTANCE = 96 constants.GROUP_MERGE_DISTANCE = 28 @@ -211,6 +216,20 @@ constants.UNIT_GROUP_MAX_SPEED_UP = 1.1 constants.UNIT_GROUP_MAX_SLOWDOWN = 1.0 constants.UNIT_GROUP_SLOWDOWN_FACTOR = 0.9 +-- sentinels + +constants.SENTINEL_IMPASSABLE_CHUNK = {} + +constants.SENTINEL_IMPASSABLE_CHUNK[constants.MOVEMENT_PHEROMONE] = constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT +constants.SENTINEL_IMPASSABLE_CHUNK[constants.BASE_PHEROMONE] = constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT +constants.SENTINEL_IMPASSABLE_CHUNK[constants.PLAYER_PHEROMONE] = constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT +constants.SENTINEL_IMPASSABLE_CHUNK[constants.RESOURCE_PHEROMONE] = constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT +constants.SENTINEL_IMPASSABLE_CHUNK[constants.PASSABLE] = constants.CHUNK_IMPASSABLE +constants.SENTINEL_IMPASSABLE_CHUNK[constants.CHUNK_TICK] = 0 +constants.SENTINEL_IMPASSABLE_CHUNK[constants.PATH_RATING] = 0 +constants.SENTINEL_IMPASSABLE_CHUNK.x = 0 +constants.SENTINEL_IMPASSABLE_CHUNK.y = 0 + return constants --[[ diff --git a/libs/EntityUtils.lua b/libs/EntityUtils.lua deleted file mode 100755 index 5c185a8c..00000000 --- a/libs/EntityUtils.lua +++ /dev/null @@ -1,147 +0,0 @@ -local entityUtils = {} - --- imports - -local mapUtils = require("MapUtils") -local constants = require("Constants") - --- constants - -local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES - -local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR - -local DEFINES_DIRECTION_EAST = defines.direction.east -local DEFINES_WIRE_TYPE_RED = defines.wire_type.red -local DEFINES_WIRE_TYPE_GREEN = defines.wire_type.green - --- imported functions - -local getChunkByIndex = mapUtils.getChunkByIndex - -local mFloor = math.floor - --- module code - -function entityUtils.getEntityOverlapChunks(regionMap, entity) - local boundingBox = entity.prototype.selection_box or entity.prototype.collision_box; - - local leftTopChunk - local rightTopChunk - local leftBottomChunk - local rightBottomChunk - - if (boundingBox ~= nil) then - local center = entity.position - local topXOffset - local topYOffset - - local bottomXOffset - local bottomYOffset - - if (entity.direction == DEFINES_DIRECTION_EAST) then - topXOffset = boundingBox.left_top.y - topYOffset = boundingBox.left_top.x - bottomXOffset = boundingBox.right_bottom.y - bottomYOffset = boundingBox.right_bottom.x - else - topXOffset = boundingBox.left_top.x - topYOffset = boundingBox.left_top.y - bottomXOffset = boundingBox.right_bottom.x - bottomYOffset = boundingBox.right_bottom.y - end - - local leftTopChunkX = mFloor((center.x + topXOffset) * 0.03125) - local leftTopChunkY = mFloor((center.y + topYOffset) * 0.03125) - - -- used to force things on chunk boundary to not spill over 0.0001 - local rightTopChunkX = mFloor((center.x + bottomXOffset - 0.0001) * 0.03125) - local rightTopChunkY = leftTopChunkY - - -- used to force things on chunk boundary to not spill over 0.0001 - local leftBottomChunkX = leftTopChunkX - local leftBottomChunkY = mFloor((center.y + bottomYOffset - 0.0001) * 0.03125) - - local rightBottomChunkX = rightTopChunkX - local rightBottomChunkY = leftBottomChunkY - - leftTopChunk = getChunkByIndex(regionMap, leftTopChunkX, leftTopChunkY) - if (leftTopChunkX ~= rightTopChunkX) then - rightTopChunk = getChunkByIndex(regionMap, rightTopChunkX, rightTopChunkY) - end - if (leftTopChunkY ~= leftBottomChunkY) then - leftBottomChunk = getChunkByIndex(regionMap, leftBottomChunkX, leftBottomChunkY) - end - if (leftTopChunkX ~= rightBottomChunkX) and (leftTopChunkY ~= rightBottomChunkY) then - rightBottomChunk = getChunkByIndex(regionMap, rightBottomChunkX, rightBottomChunkY) - end - end - return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk -end - -function entityUtils.addRemovePlayerEntity(regionMap, entity, natives, addObject, creditNatives) - local leftTop, rightTop, leftBottom, rightBottom - local entityValue - if (BUILDING_PHEROMONES[entity.type] ~= nil) and (entity.force.name == "player") then - entityValue = BUILDING_PHEROMONES[entity.type] - - leftTop, rightTop, leftBottom, rightBottom = entityUtils.getEntityOverlapChunks(regionMap, entity) - if not addObject then - if creditNatives then - natives.points = natives.points + entityValue - end - entityValue = -entityValue - end - if leftTop then - leftTop[PLAYER_BASE_GENERATOR] = leftTop[PLAYER_BASE_GENERATOR] + entityValue - end - if rightTop then - rightTop[PLAYER_BASE_GENERATOR] = rightTop[PLAYER_BASE_GENERATOR] + entityValue - end - if leftBottom then - leftBottom[PLAYER_BASE_GENERATOR] = leftBottom[PLAYER_BASE_GENERATOR] + entityValue - end - if rightBottom then - rightBottom[PLAYER_BASE_GENERATOR] = rightBottom[PLAYER_BASE_GENERATOR] + entityValue - end - end - return entity -end - -function entityUtils.makeImmortalEntity(surface, entity) - local repairPosition = entity.position - local repairName = entity.name - local repairForce = entity.force - local repairDirection = entity.direction - - local wires - if (entity.type == "electric-pole") then - wires = entity.neighbours - end - entity.destroy() - local newEntity = surface.create_entity({position=repairPosition, - name=repairName, - direction=repairDirection, - force=repairForce}) - if wires then - for connectType,neighbourGroup in pairs(wires) do - if connectType == "copper" then - for _,v in pairs(neighbourGroup) do - newEntity.connect_neighbour(v); - end - elseif connectType == "red" then - for _,v in pairs(neighbourGroup) do - newEntity.connect_neighbour({wire = DEFINES_WIRE_TYPE_RED, target_entity = v}); - end - elseif connectType == "green" then - for _,v in pairs(neighbourGroup) do - newEntity.connect_neighbour({wire = DEFINES_WIRE_TYPE_GREEN, target_entity = v}); - end - end - end - end - - newEntity.destructible = false -end - -return entityUtils diff --git a/libs/InventoryUtils.lua b/libs/InventoryUtils.lua deleted file mode 100755 index 63153220..00000000 --- a/libs/InventoryUtils.lua +++ /dev/null @@ -1,46 +0,0 @@ -local inventoryUtils = {} - --- imported functions - -local mMin = math.min - --- modules code - -function inventoryUtils.swapItemInventory(inventory, src, dst) - if inventory and inventory.valid then - local itemCount = inventory.get_item_count(src) - if (itemCount > 0) then - inventory.remove({name = src, count = itemCount}) - inventory.insert({name = dst, count = itemCount}) - end - end -end - -function inventoryUtils.topOffHand(inventory, handStack, src, dst) - if inventory and inventory.valid then - local itemCount = inventory.get_item_count(src) - if (itemCount > 0) then - if handStack and handStack.valid and handStack.valid_for_read and (handStack.prototype.name == dst) then - local remaining = mMin(itemCount, handStack.prototype.stack_size - handStack.count) - if (remaining > 0) then - local stack = { name = src, count = remaining + handStack.count } - if (handStack.can_set_stack(stack)) and handStack.set_stack(stack) then - inventory.remove({name = src, count = remaining}) - end - end - end - end - end -end - -function inventoryUtils.swapItemStack(stack, src, dst) - if stack and stack.valid_for_read and (stack.name == src) then - local item = { name = dst, count = stack.count } - if stack.can_set_stack(item) then - stack.set_stack(item) - end - end -end - - -return inventoryUtils diff --git a/libs/MapProcessor.lua b/libs/MapProcessor.lua index 49f32593..41ac5ad3 100755 --- a/libs/MapProcessor.lua +++ b/libs/MapProcessor.lua @@ -3,14 +3,13 @@ local mapProcessor = {} -- imports local unitGroupUtils = require("UnitGroupUtils") - local pheromoneUtils = require("PheromoneUtils") local aiAttackWave = require("AIAttackWave") local aiPredicates = require("AIPredicates") local constants = require("Constants") local mapUtils = require("MapUtils") local playerUtils = require("PlayerUtils") - +local chunkUtils = require("ChunkUtils") local mathUtils = require("MathUtils") -- constants @@ -26,30 +25,33 @@ local TRIPLE_CHUNK_SIZE = constants.TRIPLE_CHUNK_SIZE local PROCESS_PLAYER_BOUND = constants.PROCESS_PLAYER_BOUND local CHUNK_TICK = constants.CHUNK_TICK -local NEST_COUNT = constants.NEST_COUNT -local WORM_COUNT = constants.WORM_COUNT +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK local AI_SQUAD_COST = constants.AI_SQUAD_COST local AI_VENGENCE_SQUAD_COST = constants.AI_VENGENCE_SQUAD_COST local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE -local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR -local RESOURCE_GENERATOR = constants.RESOURCE_GENERATOR local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES -- imported functions local scents = pheromoneUtils.scents local processPheromone = pheromoneUtils.processPheromone +local playerScent = pheromoneUtils.playerScent local formSquads = aiAttackWave.formSquads -local getChunkByIndex = mapUtils.getChunkByIndex local getChunkByPosition = mapUtils.getChunkByPosition +local positionToChunkXY = mapUtils.positionToChunkXY local recycleBiters = unitGroupUtils.recycleBiters -local playerScent = pheromoneUtils.playerScent +local validPlayer = playerUtils.validPlayer + +local setResourceGenerator = chunkUtils.setResourceGenerator +local setPlayerBaseGenerator = chunkUtils.setPlayerBaseGenerator +local getNestCount = chunkUtils.getNestCount +local getWormCount = chunkUtils.getWormCount local canAttack = aiPredicates.canAttack @@ -57,8 +59,6 @@ local euclideanDistanceNamed = mathUtils.euclideanDistanceNamed local mMin = math.min -local validPlayer = playerUtils.validPlayer - local mRandom = math.random -- module code @@ -105,12 +105,12 @@ function mapProcessor.processMap(regionMap, surface, natives, tick) processPheromone(regionMap, chunk) - if squads and (chunk[NEST_COUNT] ~= 0) then + if squads and (getNestCount(regionMap, chunk) > 0) then formSquads(regionMap, surface, natives, chunk, AI_SQUAD_COST) squads = (natives.points >= AI_SQUAD_COST) and (#natives.squads < natives.maxSquads) end - scents(chunk) + scents(regionMap, chunk) end end @@ -141,10 +141,10 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick) for i=1,#playerOrdering do local player = players[playerOrdering[i]] if validPlayer(player) then - local playerPosition = player.character.position - local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y) + local chunkX, chunkY = positionToChunkXY(player.character.position) + local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY) - if playerChunk then + if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then playerScent(playerChunk) end end @@ -152,21 +152,24 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick) for i=1,#playerOrdering do local player = players[playerOrdering[i]] if validPlayer(player) then - local playerPosition = player.character.position - local playerChunk = getChunkByPosition(regionMap, playerPosition.x, playerPosition.y) + local chunkX, chunkY = positionToChunkXY(player.character.position) + local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY) - if playerChunk then - local vengence = allowingAttacks and ((playerChunk[NEST_COUNT] ~= 0) or (playerChunk[WORM_COUNT] ~= 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold)) and (natives.points >= AI_VENGENCE_SQUAD_COST) + if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then + local vengence = (allowingAttacks and + (natives.points >= AI_VENGENCE_SQUAD_COST) and + ((getWormCount(regionMap, playerChunk) > 0) or (getNestCount(regionMap, playerChunk) > 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold))) - for x=playerChunk.cX - PROCESS_PLAYER_BOUND, playerChunk.cX + PROCESS_PLAYER_BOUND do - for y=playerChunk.cY - PROCESS_PLAYER_BOUND, playerChunk.cY + PROCESS_PLAYER_BOUND do - local chunk = getChunkByIndex(regionMap, x, y) - if chunk and (chunk[CHUNK_TICK] ~= tick) then + for x=playerChunk.x - PROCESS_PLAYER_BOUND, playerChunk.x + PROCESS_PLAYER_BOUND, 32 do + for y=playerChunk.y - PROCESS_PLAYER_BOUND, playerChunk.y + PROCESS_PLAYER_BOUND, 32 do + local chunk = getChunkByPosition(regionMap, x, y) + + if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[CHUNK_TICK] ~= tick) then chunk[CHUNK_TICK] = tick processPheromone(regionMap, chunk) - if (chunk[NEST_COUNT] ~= 0) then + if (getNestCount(regionMap, chunk) > 0) then if squads then formSquads(regionMap, surface, natives, chunk, AI_SQUAD_COST) squads = (natives.points >= AI_SQUAD_COST) and (#natives.squads < natives.maxSquads) @@ -177,7 +180,7 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick) end end - scents(chunk) + scents(regionMap, chunk) end end end @@ -232,7 +235,7 @@ function mapProcessor.scanMap(regionMap, surface, natives) end end - chunk[RESOURCE_GENERATOR] = surface.count_entities_filtered(resourceQuery) * 0.001 + setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(resourceQuery) * 0.001) local entities = surface.find_entities_filtered(playerQuery) local playerBaseGenerator = 0 @@ -250,7 +253,7 @@ function mapProcessor.scanMap(regionMap, surface, natives) end end - chunk[PLAYER_BASE_GENERATOR] = playerBaseGenerator + setPlayerBaseGenerator(regionMap, chunk, playerBaseGenerator) end if (endIndex == #processQueue) then diff --git a/libs/MapUtils.lua b/libs/MapUtils.lua index 90fd6084..2fab068b 100755 --- a/libs/MapUtils.lua +++ b/libs/MapUtils.lua @@ -13,10 +13,12 @@ local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS local PASSABLE = constants.PASSABLE -local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE - local CHUNK_SIZE = constants.CHUNK_SIZE +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK + +local CHUNK_SIZE_DIVIDER = constants.CHUNK_SIZE_DIVIDER + -- imported functions local mFloor = math.floor @@ -24,19 +26,17 @@ local mFloor = math.floor -- module code function mapUtils.getChunkByPosition(regionMap, x, y) - local chunkX = regionMap[mFloor(x * 0.03125)] + local chunkX = regionMap[x] if chunkX then - return chunkX[mFloor(y * 0.03125)] + return chunkX[y] or SENTINEL_IMPASSABLE_CHUNK end - return nil + return SENTINEL_IMPASSABLE_CHUNK end -function mapUtils.getChunkByIndex(regionMap, x, y) - local chunkX = regionMap[x] - if chunkX then - return chunkX[y] - end - return nil +function mapUtils.positionToChunkXY(position) + local chunkX = mFloor(position.x * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE + local chunkY = mFloor(position.y * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE + return chunkX, chunkY end --[[ @@ -46,39 +46,39 @@ end /|\ 6 7 8 ]]-- -function mapUtils.getNeighborChunks(regionMap, chunkX, chunkY) +function mapUtils.getNeighborChunks(regionMap, x, y) local neighbors = regionMap.neighbors - local chunkYRow1 = chunkY - 1 - local chunkYRow3 = chunkY + 1 - local xChunks = regionMap[chunkX-1] + local chunkYRow1 = y - CHUNK_SIZE + local chunkYRow3 = y + CHUNK_SIZE + local xChunks = regionMap[x-CHUNK_SIZE] if xChunks then - neighbors[1] = xChunks[chunkYRow1] - neighbors[4] = xChunks[chunkY] - neighbors[6] = xChunks[chunkYRow3] + neighbors[1] = xChunks[chunkYRow1] or SENTINEL_IMPASSABLE_CHUNK + neighbors[4] = xChunks[y] or SENTINEL_IMPASSABLE_CHUNK + neighbors[6] = xChunks[chunkYRow3] or SENTINEL_IMPASSABLE_CHUNK else - neighbors[1] = nil - neighbors[4] = nil - neighbors[6] = nil + neighbors[1] = SENTINEL_IMPASSABLE_CHUNK + neighbors[4] = SENTINEL_IMPASSABLE_CHUNK + neighbors[6] = SENTINEL_IMPASSABLE_CHUNK end - - xChunks = regionMap[chunkX+1] + + xChunks = regionMap[x+CHUNK_SIZE] if xChunks then - neighbors[3] = xChunks[chunkYRow1] - neighbors[5] = xChunks[chunkY] - neighbors[8] = xChunks[chunkYRow3] + neighbors[3] = xChunks[chunkYRow1] or SENTINEL_IMPASSABLE_CHUNK + neighbors[5] = xChunks[y] or SENTINEL_IMPASSABLE_CHUNK + neighbors[8] = xChunks[chunkYRow3] or SENTINEL_IMPASSABLE_CHUNK else - neighbors[3] = nil - neighbors[5] = nil - neighbors[8] = nil + neighbors[3] = SENTINEL_IMPASSABLE_CHUNK + neighbors[5] = SENTINEL_IMPASSABLE_CHUNK + neighbors[8] = SENTINEL_IMPASSABLE_CHUNK end - xChunks = regionMap[chunkX] + xChunks = regionMap[x] if xChunks then - neighbors[2] = xChunks[chunkYRow1] - neighbors[7] = xChunks[chunkYRow3] + neighbors[2] = xChunks[chunkYRow1] or SENTINEL_IMPASSABLE_CHUNK + neighbors[7] = xChunks[chunkYRow3] or SENTINEL_IMPASSABLE_CHUNK else - neighbors[2] = nil - neighbors[7] = nil + neighbors[2] = SENTINEL_IMPASSABLE_CHUNK + neighbors[7] = SENTINEL_IMPASSABLE_CHUNK end return neighbors end @@ -87,41 +87,41 @@ function mapUtils.canMoveChunkDirection(direction, startChunk, endChunk) local canMove = false local startPassable = startChunk[PASSABLE] local endPassable = endChunk[PASSABLE] - if ((direction == 2) or (direction == 7)) and (startPassable == CHUNK_NORTH_SOUTH) and (endPassable == CHUNK_NORTH_SOUTH) then - canMove = true + if (startPassable == CHUNK_ALL_DIRECTIONS) and (endPassable == CHUNK_ALL_DIRECTIONS) then + canMove = true + elseif ((direction == 2) or (direction == 7)) and (startPassable == CHUNK_NORTH_SOUTH) and (endPassable == CHUNK_NORTH_SOUTH) then + canMove = true elseif ((direction == 4) or (direction == 5)) and (startPassable == CHUNK_EAST_WEST) and (endPassable == CHUNK_EAST_WEST) then - canMove = true - elseif (startPassable == CHUNK_ALL_DIRECTIONS) and (endPassable == CHUNK_ALL_DIRECTIONS) then canMove = true - elseif (startPassable ~= CHUNK_IMPASSABLE) and (endPassable == CHUNK_ALL_DIRECTIONS) then + elseif (startChunk ~= SENTINEL_IMPASSABLE_CHUNK) and (endPassable == CHUNK_ALL_DIRECTIONS) then canMove = true end return canMove end -function mapUtils.getCardinalChunks(regionMap, chunkX, chunkY) +function mapUtils.getCardinalChunks(regionMap, x, y) local neighbors = regionMap.cardinalNeighbors - local xChunks = regionMap[chunkX] + local xChunks = regionMap[x] if xChunks then - neighbors[1] = xChunks[chunkY-1] - neighbors[4] = xChunks[chunkY+1] + neighbors[1] = xChunks[y-CHUNK_SIZE] or SENTINEL_IMPASSABLE_CHUNK + neighbors[4] = xChunks[y+CHUNK_SIZE] or SENTINEL_IMPASSABLE_CHUNK else - neighbors[1] = nil - neighbors[4] = nil + neighbors[1] = SENTINEL_IMPASSABLE_CHUNK + neighbors[4] = SENTINEL_IMPASSABLE_CHUNK end - xChunks = regionMap[chunkX-1] + xChunks = regionMap[x-CHUNK_SIZE] if xChunks then - neighbors[2] = xChunks[chunkY] + neighbors[2] = xChunks[y] or SENTINEL_IMPASSABLE_CHUNK else - neighbors[2] = nil + neighbors[2] = SENTINEL_IMPASSABLE_CHUNK end - xChunks = regionMap[chunkX+1] + xChunks = regionMap[x+CHUNK_SIZE] if xChunks then - neighbors[3] = xChunks[chunkY] + neighbors[3] = xChunks[y] or SENTINEL_IMPASSABLE_CHUNK else - neighbors[3] = nil + neighbors[3] = SENTINEL_IMPASSABLE_CHUNK end return neighbors end diff --git a/libs/MovementUtils.lua b/libs/MovementUtils.lua index bafb3dbd..d67628dd 100755 --- a/libs/MovementUtils.lua +++ b/libs/MovementUtils.lua @@ -4,7 +4,7 @@ local movementUtils = {} local constants = require("Constants") local unitGroupUtils = require("UnitGroupUtils") - +local mapUtils = require("MapUtils") local mathUtils = require("MathUtils") -- constants @@ -12,8 +12,16 @@ local mathUtils = require("MathUtils") local MOVEMENT_PHEROMONE_GENERATOR_AMOUNT = constants.MOVEMENT_PHEROMONE_GENERATOR_AMOUNT local MAX_PENALTY_BEFORE_PURGE = constants.MAX_PENALTY_BEFORE_PURGE +local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER + +local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE + +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK + -- imported functions +local canMoveChunkDirection = mapUtils.canMoveChunkDirection + local recycleBiters = unitGroupUtils.recycleBiters local tableRemove = table.remove @@ -31,11 +39,11 @@ function movementUtils.findMovementPosition(surface, position, distort) return (distort and distortPosition(pos)) or pos end -function movementUtils.addMovementPenalty(natives, units, chunkX, chunkY) +function movementUtils.addMovementPenalty(natives, units, x, y) local penalties = units.penalties for i=1,#penalties do local penalty = penalties[i] - if (penalty.x == chunkX) and (penalty.y == chunkY) then + if (penalty.x == x) and (penalty.y == y) then penalty.v = penalty.v + MOVEMENT_PHEROMONE_GENERATOR_AMOUNT if (penalty.v > MAX_PENALTY_BEFORE_PURGE) then local group = units.group @@ -53,15 +61,15 @@ function movementUtils.addMovementPenalty(natives, units, chunkX, chunkY) tableRemove(penalties, 7) end tableInsert(penalties, 1, { v = MOVEMENT_PHEROMONE_GENERATOR_AMOUNT, - x = chunkX, - y = chunkY }) + x = x, + y = y }) end -function movementUtils.lookupMovementPenalty(squad, chunkX, chunkY) +function movementUtils.lookupMovementPenalty(squad, x, y) local penalties = squad.penalties for i=1,#penalties do local penalty = penalties[i] - if (penalty.x == chunkX) and (penalty.y == chunkY) then + if (penalty.x == x) and (penalty.y == y) then return penalty.v end end @@ -69,4 +77,101 @@ function movementUtils.lookupMovementPenalty(squad, chunkX, chunkY) end +--[[ + Expects all neighbors adjacent to a chunk +--]] +function movementUtils.scoreNeighborsForAttack(chunk, neighborDirectionChunks, scoreFunction, squad) + local highestChunk = SENTINEL_IMPASSABLE_CHUNK + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= SENTINEL_IMPASSABLE_CHUNK) and canMoveChunkDirection(x, chunk, neighborChunk) then + local score = scoreFunction(squad, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + + if scoreFunction(squad, chunk) > highestScore then + return SENTINEL_IMPASSABLE_CHUNK, -1 + end + + return highestChunk, highestDirection +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +function movementUtils.scoreNeighborsForResource(chunk, neighborDirectionChunks, scoreFunction, squad, threshold) + local highestChunk = SENTINEL_IMPASSABLE_CHUNK + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= SENTINEL_IMPASSABLE_CHUNK) and canMoveChunkDirection(x, chunk, neighborChunk) and (neighborChunk[RESOURCE_PHEROMONE] > (threshold or 1)) then + local score = scoreFunction(squad, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + + if scoreFunction(squad, chunk) > highestScore then + return SENTINEL_IMPASSABLE_CHUNK, -1 + end + + return highestChunk, highestDirection +end + +--[[ + Expects all neighbors adjacent to a chunk +--]] +function movementUtils.scoreNeighborsForRetreat(chunk, neighborDirectionChunks, scoreFunction, regionMap) + local highestChunk = SENTINEL_IMPASSABLE_CHUNK + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + for x=1,8 do + local neighborChunk = neighborDirectionChunks[x] + if (neighborChunk ~= SENTINEL_IMPASSABLE_CHUNK) and canMoveChunkDirection(x, chunk, neighborChunk) then + local score = scoreFunction(regionMap, neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + + return highestChunk, highestDirection +end + + +--[[ + Expects all neighbors adjacent to a chunk +--]] +function movementUtils.scoreNeighborsForFormation(neighborChunks, validFunction, scoreFunction, regionMap) + local highestChunk = SENTINEL_IMPASSABLE_CHUNK + local highestScore = -MAGIC_MAXIMUM_NUMBER + local highestDirection + for x=1,8 do + local neighborChunk = neighborChunks[x] + if (neighborChunk ~= SENTINEL_IMPASSABLE_CHUNK) and validFunction(regionMap, neighborChunk) then + local score = scoreFunction(neighborChunk) + if (score > highestScore) then + highestScore = score + highestChunk = neighborChunk + highestDirection = x + end + end + end + + return highestChunk, highestDirection +end + return movementUtils diff --git a/libs/NeighborUtils.lua b/libs/NeighborUtils.lua deleted file mode 100755 index 0ea85593..00000000 --- a/libs/NeighborUtils.lua +++ /dev/null @@ -1,119 +0,0 @@ -local neighborUtils = {} - --- imports - -local mapUtils = require("MapUtils") - -local constants = require("Constants") - --- constants - -local MAGIC_MAXIMUM_NUMBER = constants.MAGIC_MAXIMUM_NUMBER - -local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE - --- imported functions - -local canMoveChunkDirection = mapUtils.canMoveChunkDirection - --- module code - - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function neighborUtils.scoreNeighborsForAttack(chunk, neighborDirectionChunks, scoreFunction, squad) - local highestChunk - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if neighborChunk and canMoveChunkDirection(x, chunk, neighborChunk) then - local score = scoreFunction(squad, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - - if scoreFunction(squad, chunk) > highestScore then - return nil, -1 - end - - return highestChunk, highestDirection -end - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function neighborUtils.scoreNeighborsForResource(chunk, neighborDirectionChunks, scoreFunction, squad, threshold) - local highestChunk - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if neighborChunk and canMoveChunkDirection(x, chunk, neighborChunk) and (neighborChunk[RESOURCE_PHEROMONE] > (threshold or 1)) then - local score = scoreFunction(squad, neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - - if scoreFunction(squad, chunk) > highestScore then - return nil, -1 - end - - return highestChunk, highestDirection -end - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function neighborUtils.scoreNeighborsForRetreat(chunk, neighborDirectionChunks, scoreFunction) - local highestChunk - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - for x=1,8 do - local neighborChunk = neighborDirectionChunks[x] - if neighborChunk and canMoveChunkDirection(x, chunk, neighborChunk) then - local score = scoreFunction(neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - - return highestChunk, highestDirection -end - - ---[[ - Expects all neighbors adjacent to a chunk ---]] -function neighborUtils.scoreNeighborsForFormation(neighborChunks, validFunction, scoreFunction) - local highestChunk - local highestScore = -MAGIC_MAXIMUM_NUMBER - local highestDirection - for x=1,8 do - local neighborChunk = neighborChunks[x] - if neighborChunk and validFunction(neighborChunk) then - local score = scoreFunction(neighborChunk) - if (score > highestScore) then - highestScore = score - highestChunk = neighborChunk - highestDirection = x - end - end - end - - return highestChunk, highestDirection -end - -return neighborUtils diff --git a/libs/NestUtils.lua b/libs/NestUtils.lua index 0d845e55..c77c52bf 100755 --- a/libs/NestUtils.lua +++ b/libs/NestUtils.lua @@ -4,10 +4,8 @@ local nestUtils = {} local constants = require("Constants") local mathUtils = require("MathUtils") - -local baseRegisterUtils = require("BaseRegisterUtils") - local mapUtils = require("MapUtils") +local chunkUtils = require("ChunkUtils") -- constants @@ -20,9 +18,11 @@ local NEST_COUNT = constants.NEST_COUNT local DOUBLE_CHUNK_SIZE = constants.DOUBLE_CHUNK_SIZE +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK + -- imported functions -local registerEnemyBaseStructure = baseRegisterUtils.registerEnemyBaseStructure +local registerEnemyBaseStructure = chunkUtils.registerEnemyBaseStructure local gaussianRandomRange = mathUtils.gaussianRandomRange local mRandom = math.random @@ -35,7 +35,7 @@ function nestUtils.buildNest(regionMap, base, surface, targetPosition, name) local position = surface.find_non_colliding_position(name, targetPosition, DOUBLE_CHUNK_SIZE, 2) local chunk = getChunkByPosition(regionMap, position.x, position.y) local nest = nil - if position and chunk and (chunk[NEST_COUNT] < 3) then + if position and (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[NEST_COUNT] < 3) then local biterSpawner = {name=name, position=position} nest = surface.create_entity(biterSpawner) registerEnemyBaseStructure(regionMap, nest, base) @@ -45,7 +45,7 @@ end function nestUtils.buildHive(regionMap, base, surface) local valid = false - local hive = nestUtils.buildNest(regionMap, base, surface, base, "biter-spawner-hive") + local hive = nestUtils.buildNest(regionMap, base, surface, base, "biter-spawner-hive-rampant") if hive then if (#base.hives == 0) then base.x = hive.position.x @@ -101,7 +101,7 @@ function nestUtils.buildOutpost(regionMap, natives, base, surface, tendril) y = position.y + (distortion * math.sin(pos))} local biterSpawner = {name=thing, position=nestPosition} if surface.can_place_entity(biterSpawner) then - registerEnemyBaseStructure(regionMap, surface.create_entity(biterSpawner), base) + registerEnemyBaseStructure(natives, regionMap, surface.create_entity(biterSpawner), base) base.upgradePoints = base.upgradePoints - cost end pos = pos + slice @@ -149,7 +149,7 @@ function nestUtils.buildOrder(regionMap, natives, base, surface) y = base.y + (distortion * math.sin(pos))} local biterSpawner = {name=thing, position=nestPosition} if surface.can_place_entity(biterSpawner) then - registerEnemyBaseStructure(regionMap, surface.create_entity(biterSpawner), base) + registerEnemyBaseStructure(natives, regionMap, surface.create_entity(biterSpawner), base) base.upgradePoints = base.upgradePoints - cost end pos = pos + slice diff --git a/libs/NocturnalUtils.lua b/libs/NocturnalUtils.lua deleted file mode 100755 index 052ba102..00000000 --- a/libs/NocturnalUtils.lua +++ /dev/null @@ -1,21 +0,0 @@ -local nocturnalUtils = {} - --- imports - -local constants = require("Constants") - --- constants - -local AI_STATE_NOCTURNAL = constants.AI_STATE_NOCTURNAL - --- module code - -function nocturnalUtils.isDark(surface) - return surface.darkness > 0.65 -end - -function nocturnalUtils.canAttack(natives, surface) - return nocturnalUtils.isDark(surface) and natives.state == AI_STATE_NOCTURNAL -end - -return nocturnalUtils diff --git a/libs/PheromoneUtils.lua b/libs/PheromoneUtils.lua index 9dce556f..80d25d67 100755 --- a/libs/PheromoneUtils.lua +++ b/libs/PheromoneUtils.lua @@ -4,6 +4,7 @@ local pheromoneUtils = {} local mapUtils = require("MapUtils") local constants = require("Constants") +local chunkUtils = require("ChunkUtils") -- constants @@ -14,9 +15,6 @@ local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE local BUILDING_PHEROMONES = constants.BUILDING_PHEROMONES -local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR -local RESOURCE_GENERATOR = constants.RESOURCE_GENERATOR - local PLAYER_PHEROMONE_GENERATOR_AMOUNT = constants.PLAYER_PHEROMONE_GENERATOR_AMOUNT local DEATH_PHEROMONE_GENERATOR_AMOUNT = constants.DEATH_PHEROMONE_GENERATOR_AMOUNT @@ -25,63 +23,45 @@ local BASE_PHEROMONE_PERSISTANCE = constants.BASE_PHEROMONE_PERSISTANCE local PLAYER_PHEROMONE_PERSISTANCE = constants.PLAYER_PHEROMONE_PERSISTANCE local RESOURCE_PHEROMONE_PERSISTANCE = constants.RESOURCE_PHEROMONE_PERSISTANCE -local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE - -local PASSABLE = constants.PASSABLE - -local NEST_COUNT = constants.NEST_COUNT - local PATH_RATING = constants.PATH_RATING -local IMPASSABLE_TERRAIN_GENERATOR_AMOUNT = constants.IMPASSABLE_TERRAIN_GENERATOR_AMOUNT - -- imported functions local getCardinalChunks = mapUtils.getCardinalChunks local mMax = math.max +local getNestCount = chunkUtils.getNestCount +local getWormCount = chunkUtils.getWormCount +local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator +local getResourceGenerator = chunkUtils.getResourceGenerator -- module code -function pheromoneUtils.scents(chunk) - - if (chunk[PASSABLE] == CHUNK_IMPASSABLE) then - chunk[BASE_PHEROMONE] = IMPASSABLE_TERRAIN_GENERATOR_AMOUNT; - else - chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + chunk[PLAYER_BASE_GENERATOR] - local resourceGenerator = chunk[RESOURCE_GENERATOR] - if (resourceGenerator > 0) and (chunk[NEST_COUNT] == 0) then - chunk[RESOURCE_PHEROMONE] = chunk[RESOURCE_PHEROMONE] + mMax(resourceGenerator * 100, 90) - end +function pheromoneUtils.scents(regionMap, chunk) + chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + getPlayerBaseGenerator(regionMap, chunk) + local resourceGenerator = getResourceGenerator(regionMap, chunk) + if (resourceGenerator > 0) and (getNestCount(regionMap, chunk) == 0) and (getWormCount(regionMap, chunk) == 0) then + chunk[RESOURCE_PHEROMONE] = chunk[RESOURCE_PHEROMONE] + mMax(resourceGenerator * 100, 90) end - end function pheromoneUtils.victoryScent(chunk, entityType) local value = BUILDING_PHEROMONES[entityType] - if value and (chunk[PASSABLE] ~= CHUNK_IMPASSABLE) then + if value then chunk[MOVEMENT_PHEROMONE] = chunk[MOVEMENT_PHEROMONE] + (value * 100000) end end function pheromoneUtils.deathScent(chunk) - if (chunk[PASSABLE] ~= CHUNK_IMPASSABLE) then - chunk[MOVEMENT_PHEROMONE] = chunk[MOVEMENT_PHEROMONE] - DEATH_PHEROMONE_GENERATOR_AMOUNT - end + chunk[MOVEMENT_PHEROMONE] = chunk[MOVEMENT_PHEROMONE] - DEATH_PHEROMONE_GENERATOR_AMOUNT end function pheromoneUtils.playerScent(playerChunk) - if (playerChunk[PASSABLE] ~= CHUNK_IMPASSABLE) then - playerChunk[PLAYER_PHEROMONE] = playerChunk[PLAYER_PHEROMONE] + PLAYER_PHEROMONE_GENERATOR_AMOUNT - end + playerChunk[PLAYER_PHEROMONE] = playerChunk[PLAYER_PHEROMONE] + PLAYER_PHEROMONE_GENERATOR_AMOUNT end function pheromoneUtils.processPheromone(regionMap, chunk) - if (chunk[PASSABLE] == CHUNK_IMPASSABLE) then - return - end - local chunkMovement = chunk[MOVEMENT_PHEROMONE] local chunkBase = chunk[BASE_PHEROMONE] local chunkPlayer = chunk[PLAYER_PHEROMONE] @@ -93,17 +73,31 @@ function pheromoneUtils.processPheromone(regionMap, chunk) local totalPlayer = 0 local totalResource = 0 - local tempNeighbors = getCardinalChunks(regionMap, chunk.cX, chunk.cY) - - for i=1,4 do - local neighborChunk = tempNeighbors[i] - if neighborChunk then - totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunkMovement) - totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunkBase) - totalPlayer = totalPlayer + (neighborChunk[PLAYER_PHEROMONE] - chunkPlayer) - totalResource = totalResource + (neighborChunk[RESOURCE_PHEROMONE] - chunkResource) - end - end + local tempNeighbors = getCardinalChunks(regionMap, chunk.x, chunk.y) + + local neighborChunk = tempNeighbors[1] + totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunkMovement) + totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunkBase) + totalPlayer = totalPlayer + (neighborChunk[PLAYER_PHEROMONE] - chunkPlayer) + totalResource = totalResource + (neighborChunk[RESOURCE_PHEROMONE] - chunkResource) + + neighborChunk = tempNeighbors[2] + totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunkMovement) + totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunkBase) + totalPlayer = totalPlayer + (neighborChunk[PLAYER_PHEROMONE] - chunkPlayer) + totalResource = totalResource + (neighborChunk[RESOURCE_PHEROMONE] - chunkResource) + + neighborChunk = tempNeighbors[3] + totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunkMovement) + totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunkBase) + totalPlayer = totalPlayer + (neighborChunk[PLAYER_PHEROMONE] - chunkPlayer) + totalResource = totalResource + (neighborChunk[RESOURCE_PHEROMONE] - chunkResource) + + neighborChunk = tempNeighbors[4] + totalMovement = totalMovement + (neighborChunk[MOVEMENT_PHEROMONE] - chunkMovement) + totalBase = totalBase + (neighborChunk[BASE_PHEROMONE] - chunkBase) + totalPlayer = totalPlayer + (neighborChunk[PLAYER_PHEROMONE] - chunkPlayer) + totalResource = totalResource + (neighborChunk[RESOURCE_PHEROMONE] - chunkResource) chunk[MOVEMENT_PHEROMONE] = (chunkMovement + (0.125 * totalMovement)) * MOVEMENT_PHEROMONE_PERSISTANCE * chunkPathRating chunk[BASE_PHEROMONE] = (chunkBase + (0.25 * totalBase)) * BASE_PHEROMONE_PERSISTANCE * chunkPathRating diff --git a/libs/PlayerUtils.lua b/libs/PlayerUtils.lua index fa47d131..f53db1c5 100755 --- a/libs/PlayerUtils.lua +++ b/libs/PlayerUtils.lua @@ -25,24 +25,4 @@ function playerUtils.playersWithinProximityToPosition(players, position, distanc return false end -function playerUtils.getPlayerInventory(player, withChar, withoutChar) - local inventory = nil - if player and player.valid and player.connected then - if player.character and player.character.valid then - inventory = player.character.get_inventory(withChar) - else - inventory = player.get_inventory(withoutChar) - end - end - return inventory -end - -function playerUtils.getPlayerCursorStack(player) - local result = nil - if player and player.valid then - result = player.cursor_stack - end - return result -end - return playerUtils diff --git a/libs/SquadAttack.lua b/libs/SquadAttack.lua index dc2b648f..7059bc05 100755 --- a/libs/SquadAttack.lua +++ b/libs/SquadAttack.lua @@ -6,9 +6,9 @@ local constants = require("Constants") local mapUtils = require("MapUtils") local unitGroupUtils = require("UnitGroupUtils") local playerUtils = require("PlayerUtils") -local neighborUtils = require("NeighborUtils") local movementUtils = require("MovementUtils") local mathUtils = require("MathUtils") +local chunkUtils = require("ChunkUtils") -- constants @@ -19,7 +19,7 @@ local BASE_PHEROMONE = constants.BASE_PHEROMONE local SQUAD_RAIDING = constants.SQUAD_RAIDING local SQUAD_GUARDING = constants.SQUAD_GUARDING -local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR +local CHUNK_SIZE = constants.CHUNK_SIZE local PLAYER_PHEROMONE_MULTIPLER = constants.PLAYER_PHEROMONE_MULTIPLER @@ -32,6 +32,8 @@ local DEFINES_GROUP_ATTACKING_TARGET = defines.group_state.attacking_target local DEFINES_DISTRACTION_BY_ENEMY = defines.distraction.by_enemy local DEFINES_DISTRACTION_BY_ANYTHING = defines.distraction.by_anything +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK + -- imported functions local mRandom = math.random @@ -48,14 +50,17 @@ local positionFromDirectionAndChunk = mapUtils.positionFromDirectionAndChunk local euclideanDistanceNamed = mathUtils.euclideanDistanceNamed local playersWithinProximityToPosition = playerUtils.playersWithinProximityToPosition +local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator + +local positionToChunkXY = mapUtils.positionToChunkXY -local scoreNeighborsForAttack = neighborUtils.scoreNeighborsForAttack +local scoreNeighborsForAttack = movementUtils.scoreNeighborsForAttack -- module code local function scoreAttackLocation(squad, neighborChunk) local damage = (2*neighborChunk[MOVEMENT_PHEROMONE]) + neighborChunk[BASE_PHEROMONE] + (neighborChunk[PLAYER_PHEROMONE] * PLAYER_PHEROMONE_MULTIPLER) - return damage - lookupMovementPenalty(squad, neighborChunk.cX, neighborChunk.cY) + return damage - lookupMovementPenalty(squad, neighborChunk.x, neighborChunk.y) end function squadAttack.squadsAttack(regionMap, surface, natives) @@ -67,7 +72,7 @@ function squadAttack.squadsAttack(regionMap, surface, natives) attackPosition = regionMap.position attackCmd = { type = DEFINES_COMMAND_ATTACK_AREA, destination = attackPosition, - radius = 32, + radius = CHUNK_SIZE, distraction = DEFINES_DISTRACTION_BY_ENEMY } end @@ -78,18 +83,17 @@ function squadAttack.squadsAttack(regionMap, surface, natives) local groupState = group.state if (groupState == DEFINES_GROUP_FINISHED) or (groupState == DEFINES_GROUP_GATHERING) or ((groupState == DEFINES_GROUP_MOVING) and (squad.cycles == 0)) then local groupPosition = group.position - local chunk = getChunkByPosition(regionMap, groupPosition.x, groupPosition.y) - if chunk then + local chunkX, chunkY = positionToChunkXY(groupPosition) + local chunk = getChunkByPosition(regionMap, chunkX, chunkY) + if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then local attackChunk, attackDirection = scoreNeighborsForAttack(chunk, - getNeighborChunks(regionMap, - chunk.cX, - chunk.cY), + getNeighborChunks(regionMap, chunkX, chunkY), scoreAttackLocation, squad) - addMovementPenalty(natives, squad, chunk.cX, chunk.cY) - if group.valid and attackChunk then - if (attackChunk[PLAYER_BASE_GENERATOR] == 0) or - ((groupState == DEFINES_GROUP_FINISHED) or (groupState == DEFINES_GROUP_GATHERING)) then + addMovementPenalty(natives, squad, chunkX, chunkY) + if group.valid and (attackChunk ~= SENTINEL_IMPASSABLE_CHUNK) then + local playerBaseGenerator = getPlayerBaseGenerator(regionMap, attackChunk) + if (playerBaseGenerator == 0) or ((groupState == DEFINES_GROUP_FINISHED) or (groupState == DEFINES_GROUP_GATHERING)) then squad.cycles = ((#squad.group.members > 80) and 6) or 4 @@ -102,22 +106,18 @@ function squadAttack.squadsAttack(regionMap, surface, natives) attackCmd.distraction = DEFINES_DISTRACTION_BY_ENEMY end - local position = findMovementPosition(surface, - positionFromDirectionAndChunk(attackDirection, - groupPosition, - attackPosition, - 1.35)) + local position = findMovementPosition(surface, positionFromDirectionAndChunk(attackDirection, groupPosition, attackPosition, 1.35)) if position then attackPosition.x = position.x attackPosition.y = position.y group.set_command(attackCmd) group.start_moving() else - addMovementPenalty(natives, squad, attackChunk.cX, attackChunk.cY) + addMovementPenalty(natives, squad, attackChunk.x, attackChunk.y) end elseif not squad.frenzy and not squad.rabid and ((groupState == DEFINES_GROUP_ATTACKING_DISTRACTION) or (groupState == DEFINES_GROUP_ATTACKING_TARGET) or - (attackChunk[PLAYER_BASE_GENERATOR] ~= 0)) then + (playerBaseGenerator ~= 0)) then squad.frenzy = true squad.frenzyPosition.x = groupPosition.x squad.frenzyPosition.y = groupPosition.y diff --git a/libs/SquadDefense.lua b/libs/SquadDefense.lua index 17dfdd65..64a71691 100755 --- a/libs/SquadDefense.lua +++ b/libs/SquadDefense.lua @@ -5,14 +5,13 @@ local aiDefense = {} local constants = require("Constants") local mapUtils = require("MapUtils") local unitGroupUtils = require("UnitGroupUtils") -local neighborUtils = require("NeighborUtils") local movementUtils = require("MovementUtils") +local chunkUtils = require("ChunkUtils") -- constants local RETREAT_GRAB_RADIUS = constants.RETREAT_GRAB_RADIUS -local PLAYER_BASE_GENERATOR = constants.PLAYER_BASE_GENERATOR local MOVEMENT_PHEROMONE = constants.MOVEMENT_PHEROMONE local PLAYER_PHEROMONE = constants.PLAYER_PHEROMONE local BASE_PHEROMONE = constants.BASE_PHEROMONE @@ -23,12 +22,9 @@ local SQUAD_RETREATING = constants.SQUAD_RETREATING local RETREAT_FILTER = constants.RETREAT_FILTER -local RETREAT_TRIGGERED = constants.RETREAT_TRIGGERED - local INTERVAL_LOGIC = constants.INTERVAL_LOGIC -local NEST_COUNT = constants.NEST_COUNT -local WORM_COUNT = constants.WORM_COUNT +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK -- imported functions @@ -38,42 +34,43 @@ local findNearBySquad = unitGroupUtils.findNearBySquad local addMovementPenalty = movementUtils.addMovementPenalty local createSquad = unitGroupUtils.createSquad local membersToSquad = unitGroupUtils.membersToSquad -local scoreNeighborsForRetreat = neighborUtils.scoreNeighborsForRetreat +local scoreNeighborsForRetreat = movementUtils.scoreNeighborsForRetreat local findMovementPosition = movementUtils.findMovementPosition +local getRetreatTick = chunkUtils.getRetreatTick +local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator +local setRetreatTick = chunkUtils.setRetreatTick +local getNestCount = chunkUtils.getNestCount +local getWormCount = chunkUtils.getWormCount + -- module code -local function scoreRetreatLocation(neighborChunk) - return -(neighborChunk[BASE_PHEROMONE] + -neighborChunk[MOVEMENT_PHEROMONE] + (neighborChunk[PLAYER_PHEROMONE] * 100) + (neighborChunk[PLAYER_BASE_GENERATOR] * 20)) +local function scoreRetreatLocation(regionMap, neighborChunk) + return -(neighborChunk[BASE_PHEROMONE] + -neighborChunk[MOVEMENT_PHEROMONE] + (neighborChunk[PLAYER_PHEROMONE] * 100) + (getPlayerBaseGenerator(regionMap, neighborChunk) * 20)) end function aiDefense.retreatUnits(chunk, position, squad, regionMap, surface, natives, tick) - if (tick - chunk[RETREAT_TRIGGERED] > INTERVAL_LOGIC) and (chunk[NEST_COUNT] == 0) and (chunk[WORM_COUNT] == 0) then + if (tick - getRetreatTick(regionMap, chunk) > INTERVAL_LOGIC) and (getNestCount(regionMap, chunk) == 0) and (getWormCount(regionMap, chunk) == 0) then local performRetreat = false local enemiesToSquad = nil if not squad then - enemiesToSquad = surface.find_enemy_units(chunk, RETREAT_GRAB_RADIUS) + enemiesToSquad = surface.find_enemy_units(position, RETREAT_GRAB_RADIUS) performRetreat = #enemiesToSquad > 0 elseif squad.group.valid and (squad.status ~= SQUAD_RETREATING) and not squad.kamikaze then performRetreat = #squad.group.members > 1 end if performRetreat then - chunk[RETREAT_TRIGGERED] = tick + setRetreatTick(regionMap, chunk, tick) local exitPath,exitDirection = scoreNeighborsForRetreat(chunk, - getNeighborChunks(regionMap, - chunk.cX, - chunk.cY), - scoreRetreatLocation) - if exitPath then + getNeighborChunks(regionMap, chunk.x, chunk.y), + scoreRetreatLocation, + regionMap) + if (exitPath ~= SENTINEL_IMPASSABLE_CHUNK) then local retreatPosition = findMovementPosition(surface, - positionFromDirectionAndChunk(exitDirection, - position, - regionMap.position, - 0.98), + positionFromDirectionAndChunk(exitDirection, position, regionMap.position, 0.98), false) - if not retreatPosition then return @@ -100,7 +97,7 @@ function aiDefense.retreatUnits(chunk, position, squad, regionMap, surface, nati newSquad.rabid = true end end - addMovementPenalty(natives, newSquad, chunk.cX, chunk.cY) + addMovementPenalty(natives, newSquad, chunk.x, chunk.y) end end end diff --git a/libs/TendrilUtils.lua b/libs/TendrilUtils.lua index 499ec1c6..4dfc9664 100755 --- a/libs/TendrilUtils.lua +++ b/libs/TendrilUtils.lua @@ -4,24 +4,18 @@ local tendrilUtils = {} local constants = require("Constants") local mapUtils = require("MapUtils") -local baseRegisterUtils = require("BaseRegisterUtils") -local neighborsUtils = require("NeighborUtils") -local mathUtils = require("MathUtils") - local nestUtils = require("NestUtils") -local mathUtils = require("MathUtils") +local movementUtils = require("MovementUtils") -- constants local RESOURCE_PHEROMONE = constants.RESOURCE_PHEROMONE -local RESOURCE_GENERATOR = constants.RESOURCE_GENERATOR - -local NEST_COUNT = constants.NEST_COUNT +local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK -- imported functions -local scoreNeighborsForResource = neighborsUtils.scoreNeighborsForResource +local scoreNeighborsForResource = movementUtils.scoreNeighborsForResource local getNeighborChunks = mapUtils.getNeighborChunks @@ -74,11 +68,9 @@ local function buildTendrilPath(regionMap, tendril, surface, base, tick, natives end local tendrilPosition = tendrilUnit.position local chunk = getChunkByPosition(regionMap, tendrilPosition.x, tendrilPosition.y) - if chunk then + if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then local tendrilPath,tendrilDirection = scoreNeighborsForResource(chunk, - getNeighborChunks(regionMap, - chunk.cX, - chunk.cY), + getNeighborChunks(regionMap, chunk.x, chunk.y), scoreTendrilChunk, nil) if (tendrilDirection == -1) then diff --git a/libs/WorldProcessor.lua b/libs/WorldProcessor.lua deleted file mode 100755 index df9594f4..00000000 --- a/libs/WorldProcessor.lua +++ /dev/null @@ -1,83 +0,0 @@ -local worldProcessor = {} - --- imports - -local constants = require("Constants") - --- constants - -local ITEM_COLLECTOR_DISTANCE = constants.ITEM_COLLECTOR_DISTANCE - -local ITEM_COLLECTOR_QUEUE_SIZE = constants.ITEM_COLLECTOR_QUEUE_SIZE - --- imported functions - --- module code - -function worldProcessor.processWorld(surface, world, tick) - local collectors = world.itemCollectorEvents - if (#collectors > 0) then - local collectorLookup = world.itemCollectorLookup - - local inserter = {name="", count=0} - local topLeftPosition = {x = 0, y = 0} - local bottomRightPosition = {x = 0, y = 0} - local boundingArea = {topLeftPosition, - bottomRightPosition} - - local count = 0 - for index = #collectors, 1, -1 do - count = count + 1 - local itemCollectorPair = collectorLookup[collectors[index]] - collectors[index] = nil - - if itemCollectorPair then - local chest = itemCollectorPair[1] - local dish = itemCollectorPair[2] - - if chest.valid and dish.valid then - - local collectorPosition = dish.position - - topLeftPosition.x = collectorPosition.x - ITEM_COLLECTOR_DISTANCE - topLeftPosition.y = collectorPosition.y - ITEM_COLLECTOR_DISTANCE - - bottomRightPosition.x = collectorPosition.x + ITEM_COLLECTOR_DISTANCE - bottomRightPosition.y = collectorPosition.y + ITEM_COLLECTOR_DISTANCE - - local items = surface.find_entities_filtered({area = boundingArea, - name = "item-on-ground"}) - - local counts = {} - if (#items > 0) then - for x=1,#items do - local item = items[x] - local itemName = item.stack.name - if not counts[itemName] then - counts[itemName] = {item} - else - counts[itemName][#counts[itemName]+1] = item - end - end - for k,a in pairs(counts) do - inserter.name = k - inserter.count = #a - local stored = chest.insert(inserter) - for i=1,stored do - a[i].destroy() - end - end - -- dish.surface.create_entity({name="item-collector-base-particle-rampant", - -- position=dish.position}) - end - end - end - if (count >= ITEM_COLLECTOR_QUEUE_SIZE) then - return - end - end - end -end - - -return worldProcessor diff --git a/locale/en/locale.cfg b/locale/en/locale.cfg index 219800b6..c3eec167 100755 --- a/locale/en/locale.cfg +++ b/locale/en/locale.cfg @@ -1,6 +1,6 @@ [entity-name] -tunnel-entrance=Tunnel Entrance +tunnel-entrance-rampant=Tunnel Entrance small-suicide-biter=Small Suicide Biter medium-suicide-biter=Medium Suicide Biter @@ -9,7 +9,7 @@ behemoth-suicide-biter=Behemoth Suicide Biter small-fire-spitter=Small Fire Spitter -biter-spawner-hive=Small Hive +biter-spawner-hive-rampant=Small Hive small-tendril-biter-rampant=Small Tendril item-collector-base-rampant=Item Collector @@ -32,7 +32,7 @@ short-range-electrodynamics-1-rampant=Short-range Electrodynamics short-range-electrodynamics-1-rampant=Buildings that generate strong electromagnetic fields [entity-description] -tunnel-entrance=This tunnel is used by the biters to bypass player defenses. Fill the hole using landfill. +tunnel-entrance-rampant=This tunnel is used by the biters to bypass player defenses. Fill the hole using landfill. small-suicide-biter=These biters will explode at close range medium-suicide-biter=These biters will explode at close range @@ -41,7 +41,7 @@ behemoth-suicide-biter=These biters will explode at close range small-fire-spitter=These biters will spit fire -biter-spawner-hive=Small Hive +biter-spawner-hive-rampant=Small Hive small-tendril-biter-rampant=Small Tendril item-collector-base-rampant=Scans the surrounding area for items on the ground, if any items are found they are pulled into the storage chest when the sector is finished scanning. diff --git a/make.rkt b/make.rkt index d8f2ad94..3c6a2664 100755 --- a/make.rkt +++ b/make.rkt @@ -36,6 +36,7 @@ (string->path "README.md") (string->path "NOTICE") (string->path "libs") + (string->path "sounds") (string->path "locale") (string->path "graphics") (string->path "prototypes"))) @@ -72,12 +73,13 @@ (copyFile "tests.lua" modFolder) (copyDirectory "libs" modFolder) (copyDirectory "locale" modFolder) + (copyDirectory "sounds" modFolder) (copyDirectory "graphics" modFolder) (copyDirectory "prototypes" modFolder))) (define (run) - ;;(copyFiles modFolder) + (copyFiles modFolder) ;;(copyFiles zipModFolder) - (makeZip modFolder) + ;;(makeZip modFolder) ) ) diff --git a/prototypes/buildings/ItemCollector.lua b/prototypes/buildings/ItemCollector.lua deleted file mode 100755 index 29058584..00000000 --- a/prototypes/buildings/ItemCollector.lua +++ /dev/null @@ -1,197 +0,0 @@ --- overlays - -local radar = util.table.deepcopy(data.raw["radar"]["radar"]) -radar.name = "item-collector-base-rampant" -radar.icon = "__Rampant__/graphics/icon/itemCollectorIcon.png" -radar.collision_box = {{-0.35, -0.35}, {0.35, 0.35}} -radar.selection_box = {{-0.485, -0.7}, {0.465, -0.1}} -radar.energy_per_sector = "27MJ" -radar.max_distance_of_nearby_sector_revealed = 1 -radar.max_distance_of_sector_revealed = 0 -radar.energy_per_nearby_scan = "27MJ" -radar.energy_usage = "450KW" -radar.flags[#radar.flags+1] = "not-deconstructable" -radar.pictures = { - filename = "__Rampant__/graphics/entities/chest/itemCollector.png", - priority = "low", - width = 46, - height = 49, - apply_projection = false, - direction_count = 64, - line_length = 8, - shift = {0.1875, -0.24} -} -radar.minable = nil - --- local particle = { --- type = "explosion", --- name = "item-collector-base-particle-rampant", --- flags = {"not-on-map"}, --- animations = --- { --- { --- filename = "__Rampant__/graphics/entities/chest/itemCollectorParticle.png", --- priority = "extra-high", --- width = 46, --- height = 49, --- frame_count = 16, --- line_length = 8, --- animation_speed = 0.2 --- } --- }, --- light = {intensity = 1, size = 20, color = {r=1.0, g=1.0, b=1.0}}, --- smoke = "smoke-fast", --- smoke_count = 0, --- smoke_slow_down_factor = 1, --- sound = --- { --- aggregation = --- { --- max_count = 1, --- remove = true --- }, --- variations = --- { --- { --- filename = "__base__/sound/fight/small-explosion-1.ogg", --- volume = 0.75 --- } --- } --- } --- } - -local radarOverlay = util.table.deepcopy(radar) -radarOverlay.name = "item-collector-base-overlay-rampant" -radarOverlay.pictures.filename = "__Rampant__/graphics/entities/chest/itemCollectorOverlay2.png" -radarOverlay.pictures.width = 2048 -radarOverlay.pictures.height = 2048 -radarOverlay.pictures.direction_count = 1 -radarOverlay.pictures.line_length = 1 -radarOverlay.pictures.shift[2] = 0.07 -radarOverlay.pictures.hr_version = { - filename = "__Rampant__/graphics/entities/chest/itemCollectorOverlay2.5.png", - priority = "low", - width = 3200, - height = 3200, - apply_projection = false, - direction_count = 1, - line_length = 1, - shift = {0.1875, -0.24} -} - -local chest = util.table.deepcopy(data.raw["container"]["steel-chest"]) -chest.name = "item-collector-chest-rampant" -chest.picture = { - filename = "__core__/graphics/empty.png", - priority = "low", - width = 46, - height = 49, - line_length = 1, - shift = {0.1875, -0.2} -} -chest.selection_box = {{-0.485, -0.1}, {0.465, 0.6}} -chest.collision_mask = {} -chest.minable.result = "item-collector-base-rampant" - - -data:extend({ - radar, - radarOverlay, - chest-- , - -- particle -}) - -data:extend({ - - { - type = "recipe", - name = "item-collector-base-rampant", - normal = { - enabled = false, - energy_required = 10, - ingredients = { - {"steel-chest", 1}, - {"accumulator", 1}, - {"radar", 1} - }, - result = "item-collector-base-rampant", - requester_paste_multiplier = 4 - }, - expensive = { - enabled = false, - energy_required = 10, - ingredients = { - {"steel-chest", 2}, - {"accumulator", 2}, - {"radar", 2} - }, - result = "item-collector-base-rampant", - requester_paste_multiplier = 4 - } - }, - - { - type = "item", - name = "item-collector-base-rampant", - icon = "__Rampant__/graphics/icon/itemCollectorIcon.png", - flags = {"goes-to-quickbar"}, - subgroup = "storage", - order = "a[items]-c[steel-collector]", - place_result = "item-collector-base-rampant", - stack_size = 50 - }, - - { - type = "item", - name = "item-collector-base-overlay-rampant", - icon = "__Rampant__/graphics/icon/itemCollectorIcon.png", - flags = {"goes-to-quickbar"}, - subgroup = "storage", - order = "a[items]-c[steel-collector]", - place_result = "item-collector-base-overlay-rampant", - stack_size = 50 - }, - - { - type = "item", - name = "item-collector-chest-rampant", - icon = "__Rampant__/graphics/icon/itemCollectorIcon.png", - flags = {"goes-to-quickbar"}, - subgroup = "storage", - order = "a[items]-c[steel-collector]", - place_result = "item-collector-chest-rampant", - stack_size = 50 - } -}) - --- technology insertions - -data:extend({ - { - type = "technology", - name = "short-range-electrodynamics-1-rampant", - icon = "__Rampant__/graphics/technology/itemCollectorTech.png", - icon_size = 128, - localised_name = {"technology-name.short-range-electrodynamics-1-rampant"}, - effects = - { - { - type = "unlock-recipe", - recipe = "item-collector-base-rampant" - } - }, - prerequisites = {"electric-energy-accumulators-1"}, - unit = - { - count = 200, - ingredients = - { - {"science-pack-1", 1}, - {"science-pack-2", 1} - }, - time = 22 - }, - order = "c-e-a", - }, -}) - diff --git a/prototypes/buildings/UnitSpawners.lua b/prototypes/buildings/UnitSpawners.lua index 3d9605a7..81387699 100755 --- a/prototypes/buildings/UnitSpawners.lua +++ b/prototypes/buildings/UnitSpawners.lua @@ -102,7 +102,7 @@ data:extend({ { type = "unit-spawner", - name = "biter-spawner-hive", + name = "biter-spawner-hive-rampant", icon = "__base__/graphics/icons/biter-spawner.png", flags = {"placeable-player", "placeable-enemy", "not-repairable"}, max_health = 350, diff --git a/prototypes/buildings/tunnel.lua b/prototypes/buildings/tunnel.lua index 0544792a..fc34ed3b 100755 --- a/prototypes/buildings/tunnel.lua +++ b/prototypes/buildings/tunnel.lua @@ -1,7 +1,7 @@ data:extend({ { type = "simple-entity", - name = "tunnel-entrance", + name = "tunnel-entrance-rampant", flags = {"placeable-neutral", "placeable-off-grid", "not-on-map"}, icon = "__base__/graphics/icons/small-scorchmark.png", subgroup = "grass", diff --git a/prototypes/enemies/AttackAcidBall.lua b/prototypes/enemies/AttackAcidBall.lua index 7ea50eb5..6bf3144e 100755 --- a/prototypes/enemies/AttackAcidBall.lua +++ b/prototypes/enemies/AttackAcidBall.lua @@ -10,278 +10,95 @@ local makeColor = colorUtils.makeColor -- dumb acid projectiles -makeStream({ - name = "acid-ball", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { +local templateDamage = { amount = 4, type = "acid" } +local templateArea = { + type = "area", + perimeter = 1.2, + action_delivery = + { { - type = "area", - perimeter = 1.5, - action_delivery = + type = "instant", + target_effects = { { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 15, type = "acid" } - } - } + type = "damage", + damage = templateDamage } } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } } } -}) +} + +local templateActions = { + templateArea, + { + type = "direct", + action_delivery = { + type = "instant", + target_effects = { + type= "create-entity", + entity_name = "acid-splash-purple" + } + } + } +} + + +local template = { + name = "acid-ball", + particleTint = {r=0, g=1, b=1, a=0.5}, + spineAnimationTint = {r=0, g=1, b=1, a=0.5}, + softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), + actions = templateActions +} + +makeStream(template) -- -makeStream({ - name = "acid-ball-1", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "area", - perimeter = 1.5, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 22, type = "acid" } - }, - { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - } -}) +template.name = "acid-ball-1" +templateDamage.amount = 9 +templateArea.perimeter = 1.3 +makeStream(template) -- -makeStream({ - name = "acid-ball-2", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "area", - perimeter = 1.5, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 32, type = "acid" } - }, - { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - } -}) +template.name = "acid-ball-2" +templateDamage.amount = 14 +templateArea.perimeter = 1.4 +makeStream(template) -- -makeStream({ - name = "acid-ball-3", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "area", - perimeter = 1.5, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 45, type = "acid" } - }, - { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - } -}) +template.name = "acid-ball-3" +templateDamage.amount = 23 +templateArea.perimeter = 1.5 +makeStream(template) -- -makeStream({ - name = "wide-acid-ball", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "area", - perimeter = 3, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 35, type = "acid" } - }, - { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - } -}) +template.name = "wide-acid-ball" +templateDamage.amount = 18 +templateArea.perimeter = 3 +makeStream(template) -- -makeStream({ - name = "acid-ball-4", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "area", - perimeter = 2, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 95, type = "acid" } - }, - { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - } -}) +template.name = "acid-ball-4" +templateDamage.amount = 25 +templateArea.perimeter = 1.75 +makeStream(template) -- -makeStream({ - name = "acid-ball-5", - particleTint = {r=0, g=1, b=1, a=0.5}, - spineAnimationTint = {r=0, g=1, b=1, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "area", - perimeter = 2, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 145, type = "acid" } - }, - { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - }, - { - type = "direct", - action_delivery = { - type = "instant", - target_effects = { - type= "create-entity", - entity_name = "acid-splash-purple" - } - } - } - } -}) +template.name = "acid-ball-5" +templateDamage.amount = 50 +templateArea.perimeter = 2 +makeStream(template) + +-- + +template.name = "acid-ball-6" +templateDamage.amount = 70 +templateArea.perimeter = 2.5 +makeStream(template) diff --git a/prototypes/enemies/AttackBobs.lua b/prototypes/enemies/AttackBobs.lua index b293d8a5..1c66e14a 100755 --- a/prototypes/enemies/AttackBobs.lua +++ b/prototypes/enemies/AttackBobs.lua @@ -57,57 +57,7 @@ makeStream({ { { type = "damage", - damage = { amount = 35, type = "explosion" } - } - } - } - } - } -}) - --- - -makeStream({ - name = "bob-explosive-ball-1", - particleTint = {r=1, g=0.97, b=0.34, a=0.5}, - spineAnimationTint = {r=1, g=0.97, b=0.34, a=0.5}, - softSmokeTint = makeColor(0.3, 0.75, 0.3, 0.1), - actions = { - { - type = "direct", - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "create-entity", - entity_name = "small-scorchmark", - check_buildability = true - }, - { - type = "create-entity", - entity_name = "big-explosion", - check_buildability = true - }, - { - type = "create-entity", - entity_name = "small-fire-cloud" - } - } - } - }, - { - type = "area", - perimeter = 3, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 55, type = "explosion" } + damage = { amount = 25, type = "explosion" } } } } @@ -166,7 +116,7 @@ makeStream({ }, { type = "damage", - damage = { amount = 43, type = "fire" } + damage = { amount = 20, type = "fire" } } } } @@ -214,47 +164,6 @@ makeStream({ } }) --- - - -makeStream({ - name = "bob-poison-ball-1", - particleTint = {r=0.1, g=0.5, b=1, a=0.5}, - spineAnimationTint = {r=0, g=0, b=1, a=0.5}, - softSmokeTint = makeColor(0.7, 0.4, 0.2, 0.1), - actions = { - { - type = "direct", - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "create-entity", - entity_name = "small-poison-cloud" - } - } - } - }, - { - type = "area", - perimeter = 2, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 43, type = "poison" } - } - } - } - } - } -}) - -- piercing data:extend({ @@ -318,7 +227,7 @@ makeStream({ { { type = "damage", - damage = { amount = 43, type = "bob-pierce" } + damage = { amount = 30, type = "bob-pierce" } } } } @@ -400,47 +309,7 @@ makeStream({ { { type = "damage", - damage = { amount = 10, type = "electric" } - } - } - } - } - } -}) - --- - -makeStream({ - name = "bob-electric-ball-1", - particleTint = {r=0, g=0.1, b=1, a=1}, - spineAnimationTint = {r=0, g=0.1, b=1, a=1}, - softSmokeTint = makeColor(0.7, 0.4, 0.2, 0.1), - actions = { - { - type = "cluster", - cluster_count = 10, - distance = 4, - distance_deviation = 3, - action_delivery = - { - type = "projectile", - projectile = "electric-spike-rampant", - direction_deviation = 0.6, - starting_speed = 0.65, - starting_speed_deviation = 0.0 - } - }, - { - type = "area", - perimeter = 3, - action_delivery = - { - type = "instant", - target_effects = - { - { - type = "damage", - damage = { amount = 55, type = "electric" } + damage = { amount = 25, type = "electric" } } } } @@ -465,7 +334,7 @@ makeStream({ { { type = "create-entity", - entity_name = "small-poison-cloud" + entity_name = "small-fire-cloud" }, { type = "create-entity", @@ -484,19 +353,15 @@ makeStream({ { { type = "damage", - damage = { amount = 15, type = "electric" } - }, - { - type = "damage", - damage = { amount = 15, type = "explosion" } + damage = { amount = 10, type = "electric" } }, { type = "damage", - damage = { amount = 15, type = "fire" } + damage = { amount = 10, type = "explosion" } }, { type = "damage", - damage = { amount = 15, type = "poison" } + damage = { amount = 10, type = "fire" } } } } @@ -615,7 +480,7 @@ makeStream({ }, { type = "damage", - damage = { amount = 20, type = "explosion" } + damage = { amount = 15, type = "explosion" } }, { type = "damage", @@ -631,7 +496,7 @@ makeStream({ }, { type = "damage", - damage = { amount = 20, type = "acid" } + damage = { amount = 15, type = "acid" } } } } diff --git a/prototypes/enemies/AttackNE.lua b/prototypes/enemies/AttackNE.lua index d6a92799..b051cd8c 100755 --- a/prototypes/enemies/AttackNE.lua +++ b/prototypes/enemies/AttackNE.lua @@ -42,7 +42,7 @@ makeStream({ }, { type = "damage", - damage = {amount = 25, type = "poison"} + damage = {amount = 24, type = "poison"} } } } @@ -91,11 +91,11 @@ makeStream({ { { type = "damage", - damage = { amount = 5, type = "explosion" } + damage = { amount = 8, type = "explosion" } }, { type = "damage", - damage = { amount = 15, type = "acid" } + damage = { amount = 18, type = "acid" } } } } @@ -135,11 +135,11 @@ makeStream({ { { type = "damage", - damage = { amount = 3, type = "explosion" } + damage = { amount = 5, type = "explosion" } }, { type = "damage", - damage = { amount = 7, type = "poison" } + damage = { amount = 12, type = "poison" } } } } @@ -183,11 +183,11 @@ makeStream({ { { type = "damage", - damage = { amount = 7, type = "explosion" } + damage = { amount = 5, type = "explosion" } }, { type = "damage", - damage = { amount = 14, type = "acid" } + damage = { amount = 12, type = "acid" } } } } diff --git a/prototypes/enemies/BiterUtils.lua b/prototypes/enemies/BiterUtils.lua index 4c971557..f8dccb6a 100755 --- a/prototypes/enemies/BiterUtils.lua +++ b/prototypes/enemies/BiterUtils.lua @@ -218,22 +218,50 @@ function biterFunctions.createFireAttack(attributes, fireAttack) begin_sound = { { - filename = "__base__/sound/fight/flamethrower-start.ogg", + filename = "__base__/sound/creatures/spitter-1.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-2.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-3.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-4.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-5.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-6.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-7.ogg", + volume = 0.7 + }, + { + filename = "__base__/sound/creatures/spitter-8.ogg", volume = 0.7 } }, middle_sound = { { - filename = "__base__/sound/fight/flamethrower-mid.ogg", - volume = 0.7 + filename = attributes.midSound or "__Rampant__/sounds/attacks/acid-mid.ogg", + volume = 0.5 } }, end_sound = { { - filename = "__base__/sound/fight/flamethrower-end.ogg", - volume = 0.7 + filename = attributes.endSound or "__Rampant__/sounds/attacks/acid-end.ogg", + volume = 0.5 } } } diff --git a/prototypes/enemies/StreamUtils.lua b/prototypes/enemies/StreamUtils.lua index d1bfd930..6d02a7c5 100755 --- a/prototypes/enemies/StreamUtils.lua +++ b/prototypes/enemies/StreamUtils.lua @@ -10,7 +10,8 @@ local makeSmokeSoft = smokeUtils.makeSmokeSoft -- module code -function streamUtils.makeStream(attributes) +function streamUtils.makeStream(info) + local attributes = util.table.deepcopy(info) local softSmokeName = attributes.softSmokeName or makeSmokeSoft(attributes) data:extend( { @@ -79,7 +80,7 @@ function streamUtils.makeStream(attributes) height = 64, frame_count = 32, line_length = 8 - }, + } } } ) diff --git a/prototypes/enemies/UpdatesBobs.lua b/prototypes/enemies/UpdatesBobs.lua index 1c66656e..2b3657e4 100755 --- a/prototypes/enemies/UpdatesBobs.lua +++ b/prototypes/enemies/UpdatesBobs.lua @@ -7,19 +7,19 @@ function bobsUpdates.useDumbProjectiles() turrets["bob-big-explosive-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 25, + cooldown = 60, + range = 26, min_range = 3, turn_range = 1, fire_penalty = 0, scale = 1.2 }, - "bob-explosive-ball-1-stream-rampant") + "bob-explosive-ball-stream-rampant") turrets["bob-big-fire-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 25, + cooldown = 60, + range = 26, min_range = 3, turn_range = 1, fire_penalty = 0, @@ -29,19 +29,19 @@ function bobsUpdates.useDumbProjectiles() turrets["bob-big-poison-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 25, + cooldown = 60, + range = 26, min_range = 3, turn_range = 1, fire_penalty = 0, scale = 1.2 }, - "bob-poison-ball-1-stream-rampant") + "bob-poison-ball-stream-rampant") turrets["bob-big-piercing-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 25, + cooldown = 60, + range = 26, min_range = 3, turn_range = 1, fire_penalty = 0, @@ -51,44 +51,44 @@ function bobsUpdates.useDumbProjectiles() turrets["bob-big-electric-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 25, + cooldown = 60, + range = 26, min_range = 3, turn_range = 1, fire_penalty = 0, scale = 1.2 }, - "bob-electric-ball-1-stream-rampant") + "bob-electric-ball-stream-rampant") turrets["bob-giant-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, + cooldown = 60, range = 28, min_range = 3, turn_range = 1, fire_penalty = 0, scale = 1.6 }, - "acid-ball-4-stream-rampant") + "acid-ball-5-stream-rampant") turrets["bob-behemoth-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, + cooldown = 60, range = 30, min_range = 3, turn_range = 1, fire_penalty = 0, scale = 2 }, - "acid-ball-5-stream-rampant") + "acid-ball-6-stream-rampant") local units = data.raw["unit"] local unit = units["behemoth-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, turn_range = 1, warmup = 30, @@ -102,10 +102,11 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-big-electric-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, + cooldown = 90, range = 15, min_range = 3, turn_range = 1, + damageModifier = 0.6, warmup = 30, fire_penalty = 0, scale = biterUtils.findRunScale(unit), @@ -117,11 +118,12 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-huge-explosive-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, warmup = 30, turn_range = 1, + damageModifier = 0.8, fire_penalty = 15, scale = biterUtils.findRunScale(unit), tint1 = biterUtils.findTint(unit), @@ -132,8 +134,8 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-huge-acid-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, turn_range = 1, warmup = 30, @@ -147,8 +149,8 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-giant-fire-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, turn_range = 1, warmup = 30, @@ -162,8 +164,8 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-giant-poison-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, turn_range = 1, warmup = 30, @@ -177,8 +179,8 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-titan-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, turn_range = 1, warmup = 30, @@ -192,8 +194,8 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-behemoth-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, turn_range = 1, warmup = 30, @@ -208,8 +210,8 @@ function bobsUpdates.useDumbProjectiles() unit = units["bob-leviathan-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 17, min_range = 3, warmup = 30, turn_range = 1, diff --git a/prototypes/enemies/UpdatesNE.lua b/prototypes/enemies/UpdatesNE.lua index 501bc262..96577f4a 100755 --- a/prototypes/enemies/UpdatesNE.lua +++ b/prototypes/enemies/UpdatesNE.lua @@ -7,8 +7,8 @@ function NEUpdates.useNEUnitLaunchers () turrets["medium-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 22, + cooldown = 60, + range = 25, min_range = 3, turn_range = 1, fire_penalty = 0, @@ -19,8 +19,8 @@ function NEUpdates.useNEUnitLaunchers () turrets["big-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 27, + cooldown = 60, + range = 30, min_range = 3, turn_range = 1, fire_penalty = 0, @@ -40,13 +40,13 @@ function NEUpdates.useDumbProjectiles() turret["attack_parameters"].range = 22 turret["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 22, + cooldown = 60, + range = 25, min_range = 3, turn_range = 1, fire_penalty = 0, damageModifier = 4.5, - scale = 1.0 + scale = 1.2 }, "ne-infected-ball-stream-rampant") @@ -54,13 +54,13 @@ function NEUpdates.useDumbProjectiles() turret["attack_parameters"].range = 27 turret["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 27, + cooldown = 60, + range = 30, min_range = 3, turn_range = 1, fire_penalty = 0, damageModifier = 5.5, - scale = 1.2 + scale = 1.6 }, "ne-mutated-ball-stream-rampant") @@ -69,7 +69,7 @@ function NEUpdates.useDumbProjectiles() local unit = units["small-spitter-Mk2"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 150, + cooldown = 100, range = 13, min_range = 3, turn_range = 1, @@ -85,7 +85,7 @@ function NEUpdates.useDumbProjectiles() unit = units["small-spitter-Mk3"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 150, + cooldown = 100, range = 13, min_range = 3, turn_range = 1, @@ -102,7 +102,7 @@ function NEUpdates.useDumbProjectiles() unit = units["medium-spitter-Mk2"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 150, + cooldown = 100, range = 14, min_range = 3, turn_range = 1, @@ -118,7 +118,7 @@ function NEUpdates.useDumbProjectiles() unit = units["medium-spitter-Mk3"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 150, + cooldown = 100, range = 14, min_range = 3, turn_range = 1, @@ -134,7 +134,7 @@ function NEUpdates.useDumbProjectiles() unit = units["big-spitter-Mk2"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 150, + cooldown = 100, range = 15, min_range = 3, turn_range = 1, @@ -151,7 +151,7 @@ function NEUpdates.useDumbProjectiles() unit = units["big-spitter-Mk3"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 150, + cooldown = 100, range = 15, min_range = 3, turn_range = 1, diff --git a/prototypes/enemies/UpdatesVanilla.lua b/prototypes/enemies/UpdatesVanilla.lua index 48a56f01..51d26c60 100755 --- a/prototypes/enemies/UpdatesVanilla.lua +++ b/prototypes/enemies/UpdatesVanilla.lua @@ -7,44 +7,46 @@ function vanillaUpdates.useDumbProjectiles() turrets["small-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 50, - range = 18, + cooldown = 60, + range = 21, min_range = 5, turn_range = 1, fire_penalty = 0, + damageModifier = 0.9, scale = 0.8 }, - "acid-ball-stream-rampant") + "acid-ball-2-stream-rampant") turrets["medium-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 20, + cooldown = 60, + range = 25, min_range = 3, turn_range = 1, fire_penalty = 0, + damageModifier = 0.87, scale = 1 }, - "acid-ball-1-stream-rampant") + "acid-ball-3-stream-rampant") turrets["big-worm-turret"]["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 25, + cooldown = 60, + range = 26, min_range = 3, turn_range = 1, fire_penalty = 0, scale = 1.2 }, - "acid-ball-2-stream-rampant") + "acid-ball-4-stream-rampant") local units = data.raw["unit"]; local unit = units["small-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, + cooldown = 100, range = 13, warmup = 30, min_range = 3, @@ -59,7 +61,7 @@ function vanillaUpdates.useDumbProjectiles() unit = units["medium-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, + cooldown = 95, range = 14, min_range = 3, warmup = 30, @@ -74,7 +76,7 @@ function vanillaUpdates.useDumbProjectiles() unit = units["big-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, + cooldown = 90, range = 15, min_range = 3, warmup = 30, @@ -89,8 +91,8 @@ function vanillaUpdates.useDumbProjectiles() unit = units["behemoth-spitter"] unit["attack_parameters"] = biterUtils.createFireAttack( { - cooldown = 80, - range = 15, + cooldown = 90, + range = 16, min_range = 3, warmup = 30, turn_range = 1, diff --git a/settings.lua b/settings.lua index 980a8cb7..230c4a6e 100755 --- a/settings.lua +++ b/settings.lua @@ -193,17 +193,6 @@ data:extend({ order = "h[modifier]-a[optimize]", per_user = false }, - - - { - type = "bool-setting", - name = "rampant-enableBuildings", - description = "rampant-enableBuildings", - setting_type = "startup", - default_value = true, - order = "i[modifier]-a[buildings]", - per_user = false - }, { type = "bool-setting", diff --git a/sounds/attacks/acid-end.ogg b/sounds/attacks/acid-end.ogg new file mode 100755 index 0000000000000000000000000000000000000000..dfc6c07b3e05d48618369c8b3e431b7fb6360674 GIT binary patch literal 15838 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

RhU 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

yFTgNKPjcf4EKt2ye?%K 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