--luacheck: ignore local Public = {} local math_random = math.random local math_floor = math.floor local math_sqrt = math.sqrt local math_round = math.round local table_size = table.size local table_insert = table.insert local table_remove = table.remove local table_shuffle = table.shuffle_table local Global = require 'utils.global' local tick_schedule = {} Global.register( tick_schedule, function(t) tick_schedule = t end ) local Table = require 'modules.scrap_towny_ffa.table' local Evolution = require 'modules.scrap_towny_ffa.evolution' local function get_commmands(target, group) local commands = {} local group_position = {x = group.position.x, y = group.position.y} local step_length = 128 local target_position = target.position local distance_to_target = math_floor(math_sqrt((target_position.x - group_position.x) ^ 2 + (target_position.y - group_position.y) ^ 2)) local steps = math_floor(distance_to_target / step_length) + 1 local vector = {math_round((target_position.x - group_position.x) / steps, 3), math_round((target_position.y - group_position.y) / steps, 3)} for _ = 1, steps, 1 do group_position.x = group_position.x + vector[1] group_position.y = group_position.y + vector[2] local position = group.surface.find_non_colliding_position('small-biter', group_position, step_length, 2) if position then commands[#commands + 1] = { type = defines.command.attack_area, destination = {x = position.x, y = position.y}, radius = 16, distraction = defines.distraction.by_damage } end end commands[#commands + 1] = { type = defines.command.attack_area, destination = target.position, radius = 12, distraction = defines.distraction.by_enemy } commands[#commands + 1] = { type = defines.command.attack, target = target, distraction = defines.distraction.by_anything } return commands end local function roll_market() local ffatable = Table.get_table() local town_centers = ffatable.town_centers if town_centers == nil or table_size(town_centers) == 0 then return end local keyset = {} for town_name, _ in pairs(town_centers) do table_insert(keyset, town_name) end local tc = math_random(1, #keyset) return town_centers[keyset[tc]] end local function get_random_close_spawner(surface, market, radius) local units = surface.find_enemy_units(market.position, radius, market.force) if units ~= nil and #units > 0 then -- found units, shuffle the list table_shuffle(units) while units[1] do local unit = units[1] if unit.spawner then return unit.spawner end table_remove(units, 1) end end end local function is_swarm_valid(swarm) local group = swarm.group if not group then return end if not group.valid then return end if game.tick >= swarm.timeout then group.destroy() return end return true end function Public.validate_swarms() local ffatable = Table.get_table() for k, swarm in pairs(ffatable.swarms) do if not is_swarm_valid(swarm) then table_remove(ffatable.swarms, k) end end end function Public.unit_groups_start_moving() local ffatable = Table.get_table() for _, swarm in pairs(ffatable.swarms) do if swarm.group then if swarm.group.valid then swarm.group.start_moving() end end end end function Public.swarm(town_center, radius) if town_center == nil then return end local ffatable = Table.get_table() local r = radius or 32 local tc = town_center or roll_market() if not tc or r > 512 then return end -- skip if town evolution < 0.25 if town_center.get_biter_evolution < 0.25 then return end -- skip if we have to many swarms already local count = table_size(ffatable.swarms) local towns = table_size(ffatable.town_centers) if count > 3 * towns then return end local market = tc.market local surface = market.surface -- find a spawner local spawner = get_random_close_spawner(surface, market, r) if not spawner then r = r + 16 local future = game.tick + 1 -- schedule to run this method again with a higher radius on next tick if not tick_schedule[future] then tick_schedule[future] = {} end tick_schedule[future][#tick_schedule[future] + 1] = { callback = 'swarm', params = {tc, r} } return end -- get our evolution local evolution = 0 if spawner.name == 'spitter-spawner' then evolution = Evolution.get_biter_evolution(spawner) else evolution = Evolution.get_spitter_evolution(spawner) end -- get our target amount of enemies local count2 = (evolution * 124) + 4 local units = spawner.surface.find_enemy_units(spawner.position, 16, market.force) if #units < count2 then units = spawner.surface.find_enemy_units(spawner.position, 32, market.force) end if #units < count2 then units = spawner.surface.find_enemy_units(spawner.position, 64, market.force) end if #units < count2 then units = spawner.surface.find_enemy_units(spawner.position, 128, market.force) end if not units[1] then return end local unit_group_position = surface.find_non_colliding_position('biter-spawner', units[1].position, 256, 1) if not unit_group_position then return end local unit_group = surface.create_unit_group({position = unit_group_position, force = units[1].force}) for key, unit in pairs(units) do if key > count2 then break end unit_group.add_member(unit) end unit_group.set_command( { type = defines.command.compound, structure_type = defines.compound_command.return_last, commands = get_commmands(market, unit_group) } ) table_insert(ffatable.swarms, {group = unit_group, timeout = game.tick + 36000}) end local function on_tick() if not tick_schedule[game.tick] then return end for _, token in pairs(tick_schedule[game.tick]) do local callback = token.callback local params = token.params if callback == 'swarm' then Public.swarm(params[1], params[2]) end end tick_schedule[game.tick] = nil end local on_init = function() local ffatable = Table.get_table() ffatable.swarms = {} end local Event = require 'utils.event' Event.on_init(on_init) Event.add(defines.events.on_tick, on_tick) return Public