if (undergroundAttackG) then return undergroundAttackG end local undergroundAttack = {} local mapUtils = require("libs/MapUtils") local mathUtils = require("libs/MathUtils") local scoreChunks = require("ScoreChunks") local unitGroupUtils = require("libs/UnitGroupUtils") local positionToChunkXY = mapUtils.positionToChunkXY local getChunkListInRange = mapUtils.getChunkListInRange local getChunkByXY = mapUtils.getChunkByXY local scoreAttackLocation = scoreChunks.scoreAttackLocation local createSquad = unitGroupUtils.createSquad local mRandom = math.random local processingInterval = 120 local undergroundSpeed = 5 -- tiles local undergroundScanRange = 15 local playerMilitaryInRange_Query = { --force = universe.activePlayerForces, force = nil, type = {"ammo-turret", "fluid-turret", "electric-turret", "radar"}, position = nil, radius = undergroundScanRange, limit=1 } local playerRandomUndergroundTarget_Query = { --force = universe.activePlayerForces, force = nil, type = {"mining-drill", "generator", "artillery-turret", "radar", "train-stop"} } function undergroundAttack.drawDigInDust(surface, position) surface.create_trivial_smoke({name = "digIn-dust-nonTriggerCloud-rampant", position = position}) end function undergroundAttack.drawDigOutDust(surface, position) surface.create_entity({name = "digOut-dust-cloud-rampant", position = position}) end function undergroundAttack.drawUndegroundDust(surface, position) surface.create_entity({name = "undeground-dust-cloud-rampant", position = position}) surface.create_trivial_smoke({name = "undergroundTrace-dust-nonTriggerCloud-rampant", position = position}) end local sideVectors = {{1,0}, {0,1}, {-1,0}, {0,-1}} local function digOut(undergroundSquad, targetPosition) local map = undergroundSquad.map if (not map) or (not map.surface.valid) then return end local universe = map.universe local surface = map.surface local squadMembers = undergroundSquad.squadMembers local members = squadMembers.members -- game.print("digOut: [gps=" .. undergroundSquad.position.x .. "," .. undergroundSquad.position.y .."]") -- debug local queuedMembers = {} local decomressLater = false local squad = createSquad(undergroundSquad.position, map, nil, false) local group squad.disbandTick = game.tick + 18000 if squad and squad.group and squad.group.valid then squad.rabid = true local digOutPositions = {} ------------------- if targetPosition then digOutPositions = {} local dx local dy if targetPosition.x > undergroundSquad.position.x then dx = 1 else dx = -1 end if targetPosition.y > undergroundSquad.position.y then dy = 1 else dy = -1 end for x = undergroundSquad.position.x - 5*dx, undergroundSquad.position.x, dx do for y = undergroundSquad.position.y - 5*dy, undergroundSquad.position.y, dy do local tile = surface.get_tile({x, y}) if tile and (not tile.collides_with("resource-layer")) then digOutPositions[#digOutPositions+1] = {x = x, y = y} end end end if #digOutPositions < 5 then for x = undergroundSquad.position.x, targetPosition.x, dx do for y = undergroundSquad.position.y, targetPosition.y, dy do local tile = surface.get_tile({x , y}) if tile and (not tile.collides_with("resource-layer")) then digOutPositions[#digOutPositions+1] = {x = x, y = y} end end end end if #digOutPositions == 0 then digOutPositions[1] = {x = targetPosition.x, y = targetPosition.y} end else for dx = 1, 5 do for dy = 1, 5 do for _, vector in pairs(sideVectors) do local x = undergroundSquad.position.x + dx*vector[1] local y = undergroundSquad.position.y + dx*vector[2] local tile = surface.get_tile({x, y}) if tile and (not tile.collides_with("resource-layer")) then digOutPositions[#digOutPositions+1] = {x = x, y = y} end end end if #digOutPositions == 0 then digOutPositions[1] = {x = undergroundSquad.position.x, y = undergroundSquad.position.y} end end end ------------------- group = squad.group local undergroundTotal = 0 local unitsInTile = 999 local digOutIndex local digOutPosition for entityName, count in pairs (members) do if count > 1 then for i = 1, count do if unitsInTile > 5 then unitsInTile = 0 digOutIndex, digOutPosition = next(digOutPositions, digOutIndex) if not digOutIndex then digOutIndex, digOutPosition = next(digOutPositions, nil) end end local prototypeList = game.get_filtered_entity_prototypes({{filter = "name", name = entityName}}) if prototypeList and (#prototypeList>0) then local newEntity = surface.create_entity({ name = entityName, position = digOutPosition, force = "enemy", }) if newEntity and newEntity.valid then unitsInTile = unitsInTile + 1 group.add_member(newEntity) newEntity.destructible = false universe.oneTickImmunityUnits[#universe.oneTickImmunityUnits+1] = {entity = newEntity, tick = game.tick + 1} undergroundTotal = undergroundTotal + 1 if undergroundTotal >= 200 then queuedMembers[newEntity.unit_number] = { count = count - i, name = entityName, position = {x = newEntity.position.x, y = newEntity.position.y}, direction = newEntity.direction, force = "enemy" } decomressLater = true break end end end end end end if undergroundTotal > 0 then undergroundAttack.drawDigOutDust(surface, undergroundSquad.position) universe.squadCount = universe.squadCount + 1 universe.groupNumberToSquad[squad.groupNumber] = squad end universe.undergroundSquads[undergroundSquad.groupNumber] = nil if decomressLater then universe.decomressQueue[group] = queuedMembers end end return squad end local function getRandomUndergroundTarget(map) local surface = map.surface local entities = surface.find_entities_filtered(playerRandomUndergroundTarget_Query) if #entities > 0 then local entity = entities[mRandom(1, #entities)] if entity.valid then return entity else return nil end end return nil end local function showFlyingText(parameters) for i, player in pairs(game.connected_players) do if player.valid then if (not parameters.surface) or (parameters.surface.index == ((player.surface and player.surface.index) or 0)) then player.create_local_flying_text(parameters) end end end end function undergroundAttack.createUndergroudAttack(map, squad) if (not squad) then return end local group = squad.group if not group then return end local universe = map.universe local compressedUnits = universe.compressedUnits local surface = map.surface local squadMembers = {total = 0, members = {}} local members = squadMembers.members local dustCloudPositions = {} for _, entity in pairs(group.members) do if entity.valid then local positionIndex = "x"..math.ceil(entity.position.x).."y"..math.ceil(entity.position.y) local entityIndex = entity.name local compressedUnit = compressedUnits[entity.unit_number] if compressedUnit then members[entityIndex] = (members[entityIndex] or 0) + compressedUnit.count squadMembers.total = squadMembers.total + compressedUnit.count compressedUnits[entity.unit_number] = nil else members[entityIndex] = (members[entityIndex] or 0) + 1 squadMembers.total = squadMembers.total + 1 end dustCloudPositions[positionIndex] = {x = entity.position.x, y = entity.position.y} entity.destroy() end end local undergroundSquad = createSquad(group.position, map, group, false) undergroundSquad.position = {x = group.position.x, y = group.position.y} undergroundSquad.nextTick = game.tick + processingInterval undergroundSquad.squadMembers = squadMembers if squad.undergoundAttack == "randomTarget" then playerRandomUndergroundTarget_Query.force = universe.activePlayerForces local target = getRandomUndergroundTarget(map) if target then undergroundSquad.targetPosition = {x = target.position.x, y = target.position.y} end elseif (squad.undergoundAttack == "position") and (squad.targetPosition) then undergroundSquad.targetPosition = {x = squad.targetPosition.x, y = squad.targetPosition.y} end -- debug -- if undergroundSquad.targetPosition then -- rendering.draw_line{surface = group.surface, from = group.position, to = undergroundSquad.targetPosition, color = {1,0,0}, width = 2, time_to_live = 3600} -- end for _, position in pairs(dustCloudPositions) do undergroundAttack.drawDigInDust(surface, position) end map.universe.undergroundSquads[undergroundSquad.groupNumber] = undergroundSquad -- game.print(undergroundSquad.groupNumber..": undergroundAttack.nextTick = "..undergroundSquad.nextTick.."[gps=" .. squad.group.position.x .. "," .. squad.group.position.y .."]") -- debug group.destroy() -- squad will be deleted in squadAttack.cleanSquads return undergroundSquad end function undergroundAttack.onUnitKilled_DigIn(map, entity, cause) if (not entity) or not (entity.valid) then return end local group = entity.unit_group if not group then return end local squad = map.universe.groupNumberToSquad[group.group_number] -- or universe.nonRampantCompressedSquads[group.group_number] if (not squad) or squad.rabid or squad.frenzy then return end local roll = math.random() if roll < 0.2 then squad.undergoundAttack = "randomTarget" elseif roll < 0.6 then squad.undergoundAttack = "common" else squad.undergoundAttack = "position" squad.targetPosition = {x = cause.position.x, y = cause.position.y} end undergroundAttack.createUndergroudAttack(map, squad) end local function findNextAttackChunk(map, undergroundSquad) local x, y = positionToChunkXY(undergroundSquad.position) local chunk = getChunkByXY(map, x, y) local chunkList = getChunkListInRange(map, x, y, undergroundSquad.range or 2) local highestScore = -1 local highestChunk = -1 if chunk ~= -1 then highestScore = scoreAttackLocation(map, chunk) end for i = 1, #chunkList do local neighborChunk = chunkList[i] if neighborChunk~=-1 then local score = scoreAttackLocation(map, neighborChunk) if (score > highestScore) then highestScore = score highestChunk = neighborChunk end end end return highestChunk end -- move to target position, check for military, dig out. local function processUndergroundSquad(map, undergroundSquad) local surface = map.surface local traveled = 0 local dx = 0 local dy = 0 local range = 1 if undergroundSquad.targetPosition then dx = undergroundSquad.targetPosition.x - undergroundSquad.position.x dy = undergroundSquad.targetPosition.y - undergroundSquad.position.y range = mathUtils.euclideanDistancePoints(undergroundSquad.position.x, undergroundSquad.position.y, undergroundSquad.targetPosition.x, undergroundSquad.targetPosition.y) end if range <= undergroundSpeed then if undergroundSquad.targetPosition then undergroundSquad.position.x = undergroundSquad.targetPosition.x undergroundSquad.position.y = undergroundSquad.targetPosition.y end local nextAttackChunk = findNextAttackChunk(map, undergroundSquad) if nextAttackChunk == - 1 then --game.print("processUndergroundSquad : no nextAttackChunk") -- debug digOut(undergroundSquad) return else undergroundSquad.targetPosition = {x = nextAttackChunk.x, y = nextAttackChunk.y} traveled = range dx = undergroundSquad.targetPosition.x - undergroundSquad.position.x dy = undergroundSquad.targetPosition.y - undergroundSquad.position.y range = mathUtils.euclideanDistancePoints(undergroundSquad.position.x, undergroundSquad.position.y, undergroundSquad.targetPosition.x, undergroundSquad.targetPosition.y) end end local kf = math.min((undergroundSpeed - traveled)/ range , 1) undergroundSquad.position.x = math.ceil(undergroundSquad.position.x + dx * kf) undergroundSquad.position.y = math.ceil(undergroundSquad.position.y + dy * kf) undergroundSquad.nextTick = game.tick + processingInterval playerMilitaryInRange_Query.force = map.universe.activePlayerForces playerMilitaryInRange_Query.position = undergroundSquad.position local entities = surface.find_entities_filtered(playerMilitaryInRange_Query) if #entities > 0 then -- game.print("processUndergroundSquad : digOut, military ") -- debug digOut(undergroundSquad, entities[1].position) else undergroundAttack.drawUndegroundDust(surface, undergroundSquad.position) showFlyingText({text = tostring(undergroundSquad.squadMembers.total).." [entity=big-biter]", surface = surface, position = undergroundSquad.position, color = {1, 0, 0}, speed = 1, time_to_live = 30}) if not map.universe.firtstUndergroundAlertShown then local chunkX = math.floor(undergroundSquad.position.x / 32) local chunkY = math.floor(undergroundSquad.position.y / 32) if game.forces["player"].is_chunk_visible(surface, {x = chunkX, y = chunkY}) then game.print({"", {"description.rampantFixed--firstUndergroundAttackWarning"},": ", ("[gps=" .. undergroundSquad.position.x .. "," .. undergroundSquad.position.y .."]")}) map.universe.firtstUndergroundAlertShown = true end end end end local maxSquadsPerProcessing = 10 function undergroundAttack.processUndergroundSquads(universe) if not universe.undergroundAttack then return end local squadsProcessed = 0 for i, undergroundSquad in pairs(universe.undergroundSquads) do map = undergroundSquad.map if map and map.surface and map.surface.valid then if undergroundSquad.nextTick <= game.tick then processUndergroundSquad(map, undergroundSquad) squadsProcessed = squadsProcessed + 1 if squadsProcessed >= maxSquadsPerProcessing then break end end else universe.undergroundSquads[i] = nil end end end function undergroundAttack.updateUndergroundAttackProbability(universe) if not universe.undergroundAttack then return end local newUndergroundAttackProbability local evolution_factor = game.forces.enemy.evolution_factor local minEvo = 0.4 local maxEvo = 0.8 if evolution_factor <= minEvo then newUndergroundAttackProbability = 0 elseif evolution_factor >= maxEvo then newUndergroundAttackProbability = settings.global["rampantFixed--undergroundAttackProbability"].value else newUndergroundAttackProbability = settings.global["rampantFixed--undergroundAttackProbability"].value * ((evolution_factor - minEvo) / (maxEvo - minEvo)) if newUndergroundAttackProbability < 0.001 then newUndergroundAttackProbability = 0 end end if universe.undergroundAttackProbability == 0 then universe.undergroundAttackProbability = newUndergroundAttackProbability if newUndergroundAttackProbability > 0 then game.print({"description.rampantFixed--undergroundAttackWarning"}) end else universe.undergroundAttackProbability = math.max(newUndergroundAttackProbability, 0.0001) end end undergroundAttackG = undergroundAttack return undergroundAttack