local Public = require 'modules.wave_defense.core' local Event = require 'utils.event' local BiterHealthBooster = require 'modules.biter_health_booster_v2' local Difficulty = require 'modules.difficulty_vote_by_amount' local Alert = require 'utils.alert' local random = math.random local floor = math.floor local sqrt = math.sqrt local round = math.round local function debug_print(msg) local debug = Public.get('debug') if not debug then return end print('WaveDefense: ' .. msg) end local function debug_print_health(msg) local debug = Public.get('debug_health') if not debug then return end print('[HEALTHBOOSTER]: ' .. msg) end local function valid(userdata) if not (userdata and userdata.valid) then return false end return true end local function shuffle(tbl) local size = #tbl for i = size, 1, -1 do local rand = math.random(size) tbl[i], tbl[rand] = tbl[rand], tbl[i] end return tbl end local function find_initial_spot(surface, position) local spot = Public.get('spot') if not spot then local pos = surface.find_non_colliding_position('rocket-silo', position, 128, 1) if not pos then pos = surface.find_non_colliding_position('rocket-silo', position, 148, 1) end if not pos then pos = surface.find_non_colliding_position('rocket-silo', position, 164, 1) end if not pos then pos = surface.find_non_colliding_position('rocket-silo', position, 200, 1) end if not pos then pos = position end if random(1, 2) == 1 then local random_pos = { {x = pos.x - 10, y = pos.y - 5}, {x = pos.x + 10, y = pos.y + 5}, {x = pos.x - 10, y = pos.y + 5}, {x = pos.x + 10, y = pos.y - 5} } local actual_pos = shuffle(random_pos) pos = {x = actual_pos[1].x, y = actual_pos[1].y} end if not pos then pos = position end Public.set('spot', pos) return pos else spot = Public.get('spot') return spot end end local function is_closer(pos1, pos2, pos) return ((pos1.x - pos.x) ^ 2 + (pos1.y - pos.y) ^ 2) < ((pos2.x - pos.x) ^ 2 + (pos2.y - pos.y) ^ 2) end local function shuffle_distance(tbl, position) local size = #tbl for i = size, 1, -1 do local rand = random(size) if is_closer(tbl[i].position, tbl[rand].position, position) and i > rand then tbl[i], tbl[rand] = tbl[rand], tbl[i] end end return tbl end local function is_position_near(pos_to_check, check_against) local function inside(pos) return pos.x >= pos_to_check.x and pos.y >= pos_to_check.y and pos.x <= pos_to_check.x and pos.y <= pos_to_check.y end if inside(check_against) then return true end return false end local function remove_trees(entity) if not valid(entity) then return end local surface = entity.surface local radius = 10 local pos = entity.position local area = {{pos.x - radius, pos.y - radius}, {pos.x + radius, pos.y + radius}} local trees = surface.find_entities_filtered {area = area, type = 'tree'} if #trees > 0 then for i, tree in pairs(trees) do if tree and tree.valid then tree.destroy() end end end end local function remove_rocks(entity) if not valid(entity) then return end local surface = entity.surface local radius = 10 local pos = entity.position local area = {{pos.x - radius, pos.y - radius}, {pos.x + radius, pos.y + radius}} local rocks = surface.find_entities_filtered {area = area, type = 'simple-entity'} if #rocks > 0 then for i, rock in pairs(rocks) do if rock and rock.valid then rock.destroy() end end end end local function fill_tiles(entity, size) if not valid(entity) then return end local surface = entity.surface local radius = size or 10 local pos = entity.position local t = { 'water', 'water-green', 'water-mud', 'water-shallow', 'deepwater', 'deepwater-green' } local area = {{pos.x - radius, pos.y - radius}, {pos.x + radius, pos.y + radius}} local tiles = surface.find_tiles_filtered {area = area, name = t} if #tiles > 0 then for _, tile in pairs(tiles) do surface.set_tiles({{name = 'sand-1', position = tile.position}}, true) end end debug_print('fill_tiles - filled tiles cause we found non-placable tiles.') end local function get_spawn_pos() local surface_index = Public.get('surface_index') local surface = game.surfaces[surface_index] if not surface then return debug_print('get_spawn_pos - surface was not valid?') end local c = 0 ::retry:: local initial_position = Public.get('spawn_position') local located_position = find_initial_spot(surface, initial_position) local valid_position = surface.find_non_colliding_position('stone-furnace', located_position, 32, 1) local debug = Public.get('debug') if debug then if valid_position then local x = valid_position.x local y = valid_position.y game.print('[gps=' .. x .. ',' .. y .. ',' .. surface.name .. ']') end end if not valid_position then local remove_entities = Public.get('remove_entities') if remove_entities then c = c + 1 valid_position = Public.get('spawn_position') debug_print(serpent.block('valid_position - x:' .. valid_position.x .. ' y:' .. valid_position.y)) remove_trees({surface = surface, position = valid_position, valid = true}) remove_rocks({surface = surface, position = valid_position, valid = true}) fill_tiles({surface = surface, position = valid_position, valid = true}) Public.set('spot', 'nil') if c == 5 then return debug_print('get_spawn_pos - we could not find a spawning pos?') end goto retry else return debug_print('get_spawn_pos - we could not find a spawning pos?') end end debug_print(serpent.block('valid_position - x:' .. valid_position.x .. ' y:' .. valid_position.y)) return valid_position end local function is_unit_valid(biter) local max_biter_age = Public.get('max_biter_age') if not biter.entity then debug_print('is_unit_valid - unit destroyed - does no longer exist') return false end if not biter.entity.valid then debug_print('is_unit_valid - unit destroyed - invalid') return false end if not biter.entity.unit_group then debug_print('is_unit_valid - unit destroyed - no unitgroup') return false end if biter.spawn_tick + max_biter_age < game.tick then debug_print('is_unit_valid - unit destroyed - timed out') return false end return true end local function refresh_active_unit_threat() local active_biter_threat = Public.get('active_biter_threat') local generated_units = Public.get('generated_units') debug_print('refresh_active_unit_threat - current value ' .. active_biter_threat) local biter_threat = 0 for k, biter in pairs(generated_units.active_biters) do if valid(biter.entity) then biter_threat = biter_threat + Public.threat_values[biter.entity.name] else generated_units.active_biters[k] = nil end end local biter_health_boost = BiterHealthBooster.get('biter_health_boost') Public.set('active_biter_threat', round(biter_threat * biter_health_boost, 2)) debug_print('refresh_active_unit_threat - new value ' .. active_biter_threat) if generated_units.unit_group_pos.index > 500 then generated_units.unit_group_pos.positions = {} generated_units.unit_group_pos.index = 0 end end local function time_out_biters() local generated_units = Public.get('generated_units') local active_biter_count = Public.get('active_biter_count') local active_biter_threat = Public.get('active_biter_threat') if active_biter_count >= 100 and #generated_units.active_biters <= 10 then Public.set('active_biter_count', 50) end local biter_health_boost = BiterHealthBooster.get('biter_health_boost') for k, biter in pairs(generated_units.active_biters) do if not is_unit_valid(biter) then Public.set('active_biter_count', active_biter_count - 1) if biter.entity then if biter.entity.valid then Public.set('active_biter_threat', active_biter_threat - round(Public.threat_values[biter.entity.name] * biter_health_boost, 2)) if biter.entity.force.index == 2 then biter.entity.destroy() end debug_print('time_out_biters: ' .. k .. ' got deleted.') end end generated_units.active_biters[k] = nil end end end local function get_random_close_spawner() local generated_units = Public.get('generated_units') local target = Public.get('target') local get_random_close_spawner_attempts = Public.get('get_random_close_spawner_attempts') local center = target.position local spawner local retries = 0 for i = 1, get_random_close_spawner_attempts, 1 do ::retry:: if #generated_units.nests < 1 then return false end local k = random(1, #generated_units.nests) local spawner_2 = generated_units.nests[k] if not spawner_2 or not spawner_2.valid then generated_units.nests[k] = nil retries = retries + 1 if retries == 5 then break end goto retry end if not spawner or (center.x - spawner_2.position.x) ^ 2 + (center.y - spawner_2.position.y) ^ 2 < (center.x - spawner.position.x) ^ 2 + (center.y - spawner.position.y) ^ 2 then spawner = spawner_2 if spawner and spawner.position then debug_print('get_random_close_spawner - Found at x' .. spawner.position.x .. ' y' .. spawner.position.y) end end end return spawner end local function get_random_character() local characters = {} local surface_index = Public.get('surface_index') local p = game.connected_players for _, player in pairs(p) do if player.character then if player.character.valid then if player.character.surface.index == surface_index then characters[#characters + 1] = player.character end end end end if not characters[1] then return end return characters[random(1, #characters)] end local function set_main_target() local target = Public.get('target') if target then if target.valid then return end end local unit_groups_size = Public.get('unit_groups_size') if unit_groups_size < 0 then unit_groups_size = 0 end Public.set('unit_groups_size', unit_groups_size) local sec_target = Public.get_side_target() if not sec_target then sec_target = get_random_character() end if not sec_target then return end Public.set('target', sec_target) debug_print( 'set_main_target -- New main target ' .. sec_target.name .. ' at position x' .. sec_target.position.x .. ' y' .. sec_target.position.y .. ' selected.' ) end local function set_group_spawn_position(surface) local spawner = get_random_close_spawner() if not spawner then return end local position = surface.find_non_colliding_position('behemoth-biter', spawner.position, 128, 1) if not position then return end Public.set('spawn_position', {x = position.x, y = position.y}) local spawn_position = get_spawn_pos() debug_print('set_group_spawn_position -- Changed position to x' .. spawn_position.x .. ' y' .. spawn_position.y .. '.') end local function set_enemy_evolution() local wave_number = Public.get('wave_number') local generated_units = Public.get('generated_units') local threat = Public.get('threat') local evolution_factor = wave_number * 0.001 local enemy = game.forces.enemy local biter_health_boost = 1 if evolution_factor > 1 then evolution_factor = 1 end if not next(generated_units.active_biters) then Public.set('active_biter_count', 0) end if threat > 50000 then biter_health_boost = round(biter_health_boost + (threat - 50000) * 0.000033, 3) end BiterHealthBooster.set('biter_health_boost', biter_health_boost) if enemy.evolution_factor == 1 and evolution_factor == 1 then return end enemy.evolution_factor = evolution_factor end local function can_units_spawn() local threat = Public.get('threat') if threat <= 0 then debug_print('can_units_spawn - threat too low') time_out_biters() return false end local active_biter_count = Public.get('active_biter_count') local max_active_biters = Public.get('max_active_biters') if active_biter_count >= max_active_biters then debug_print('can_units_spawn - active biter count too high') time_out_biters() return false end local active_biter_threat = Public.get('active_biter_threat') if active_biter_threat >= threat then debug_print('can_units_spawn - active biter threat too high (' .. active_biter_threat .. ')') time_out_biters() return false end return true end local function get_active_unit_groups_count() local generated_units = Public.get('generated_units') local count = 0 for k, g in pairs(generated_units.unit_groups) do if g.valid then if #g.members > 0 then count = count + 1 else g.destroy() generated_units.unit_groups[k] = nil local unit_groups_size = Public.get('unit_groups_size') Public.set('unit_groups_size', unit_groups_size - 1) end else generated_units.unit_groups[k] = nil generated_units.unit_group_pos.positions[k] = nil local unit_groups_size = Public.get('unit_groups_size') Public.set('unit_groups_size', unit_groups_size - 1) if generated_units.unit_group_last_command[k] then generated_units.unit_group_last_command[k] = nil end end end debug_print('Active unit group count: ' .. count) return count end local function spawn_biter(surface, position, forceSpawn, is_boss_biter, unit_settings) if not forceSpawn then if not is_boss_biter then if not can_units_spawn() then return end end end local boosted_health = BiterHealthBooster.get('biter_health_boost') local name if random(1, 100) > 73 then name = Public.wave_defense_roll_spitter_name() else name = Public.wave_defense_roll_biter_name() end local old_position = position position = surface.find_non_colliding_position('steel-chest', position, 3, 1) if not position then position = old_position end local biter = surface.create_entity({name = name, position = position, force = 'enemy'}) biter.ai_settings.allow_destroy_when_commands_fail = true biter.ai_settings.allow_try_return_to_spawner = false biter.ai_settings.do_separation = true local increase_health_per_wave = Public.get('increase_health_per_wave') if increase_health_per_wave and not is_boss_biter then local modified_unit_health = Public.get('modified_unit_health') local final_health = round(modified_unit_health.current_value * unit_settings.scale_units_by_health[biter.name], 3) debug_print_health('final_health - unit: ' .. biter.name .. ' with h-m: ' .. final_health) BiterHealthBooster.add_unit(biter, final_health) end if is_boss_biter then local increase_boss_health_per_wave = Public.get('increase_boss_health_per_wave') if increase_boss_health_per_wave then local modified_boss_unit_health = Public.get('modified_boss_unit_health') BiterHealthBooster.add_boss_unit(biter, modified_boss_unit_health.current_value, 0.55) else local sum = boosted_health * 5 debug_print('Boss Health Boosted: ' .. sum) BiterHealthBooster.add_boss_unit(biter, sum, 0.55) end end local generated_units = Public.get('generated_units') generated_units.active_biters[biter.unit_number] = {entity = biter, spawn_tick = game.tick} local active_biter_count = Public.get('active_biter_count') Public.set('active_biter_count', active_biter_count + 1) local active_biter_threat = Public.get('active_biter_threat') Public.set('active_biter_threat', active_biter_threat + round(Public.threat_values[name] * boosted_health, 2)) return biter end local function increase_biter_damage() local increase_damage_per_wave = Public.get('increase_damage_per_wave') if not increase_damage_per_wave then return end local e = game.forces.enemy local new = Difficulty.get().difficulty_vote_value * 0.04 local melee = new local bio = new - 0.02 local e_old_melee = e.get_ammo_damage_modifier('melee') local e_old_biological = e.get_ammo_damage_modifier('biological') debug_print('Melee: ' .. melee + e_old_melee) debug_print('Biological: ' .. bio + e_old_biological) e.set_ammo_damage_modifier('melee', melee + e_old_melee) e.set_ammo_damage_modifier('biological', bio + e_old_biological) end local function increase_biters_health() local increase_health_per_wave = Public.get('increase_health_per_wave') if not increase_health_per_wave then return end -- local boosted_health = BiterHealthBooster.get('biter_health_boost') -- local wave_number = Public.get('wave_number') -- this sets normal units health local modified_unit_health = Public.get('modified_unit_health') if modified_unit_health.current_value > modified_unit_health.limit_value then modified_unit_health.current_value = modified_unit_health.limit_value end debug_print_health('modified_unit_health.current_value: ' .. modified_unit_health.current_value) Public.set('modified_unit_health').current_value = modified_unit_health.current_value + modified_unit_health.health_increase_per_boss_wave -- this sets boss units health -- if boosted_health == 1 then -- boosted_health = 1.25 -- end -- boosted_health = round(boosted_health * (wave_number * 0.04), 3) -- debug_print_health('boosted_health: ' .. boosted_health) -- if boosted_health >= 300 then -- boosted_health = 300 -- end -- Public.set('modified_boss_unit_health', boosted_health) -- this sets boss units health local modified_boss_unit_health = Public.get('modified_boss_unit_health') if modified_boss_unit_health.current_value > modified_boss_unit_health.limit_value then modified_boss_unit_health.current_value = modified_boss_unit_health.limit_value end debug_print_health('modified_boss_unit_health.current_value: ' .. modified_boss_unit_health.current_value) Public.set('modified_boss_unit_health').current_value = modified_boss_unit_health.current_value + modified_boss_unit_health.health_increase_per_boss_wave end local function set_next_wave() local wave_number = Public.get('wave_number') Public.set('wave_number', wave_number + 1) wave_number = Public.get('wave_number') local threat_gain_multiplier = Public.get('threat_gain_multiplier') local threat_gain = wave_number * threat_gain_multiplier if wave_number > 1000 then threat_gain = threat_gain * (wave_number * 0.001) end if wave_number % 25 == 0 then increase_biter_damage() increase_biters_health() Public.set('boss_wave', true) Public.set('boss_wave_warning', true) local alert_boss_wave = Public.get('alert_boss_wave') local spawn_position = get_spawn_pos() if alert_boss_wave then local msg = 'Boss Wave: ' .. wave_number local pos = { position = spawn_position } Alert.alert_all_players_location(pos, msg, {r = 0.8, g = 0.1, b = 0.1}) end threat_gain = threat_gain * 2 else local boss_wave_warning = Public.get('boss_wave_warning') if boss_wave_warning then Public.set('boss_wave_warning', false) end end local threat = Public.get('threat') Public.set('threat', threat + floor(threat_gain)) local wave_enforced = Public.get('wave_enforced') local next_wave = Public.get('next_wave') local wave_interval = Public.get('wave_interval') if not wave_enforced then Public.set('last_wave', next_wave) Public.set('next_wave', game.tick + wave_interval) end local clear_corpses = Public.get('clear_corpses') if clear_corpses then local surface_index = Public.get('surface_index') local surface = game.surfaces[surface_index] for _, entity in pairs(surface.find_entities_filtered {type = 'corpse'}) do if random(1, 2) == 1 then entity.destroy() end end end end local function reform_group(group) local unit_group_command_step_length = Public.get('unit_group_command_step_length') local group_position = {x = group.position.x, y = group.position.y} local step_length = unit_group_command_step_length local generated_units = Public.get('generated_units') local position = group.surface.find_non_colliding_position('biter-spawner', group_position, step_length, 4) if position then local new_group = group.surface.create_unit_group {position = position, force = group.force} for key, biter in pairs(group.members) do new_group.add_member(biter) end debug_print('Creating new unit group, because old one was stuck.') generated_units.unit_groups[new_group.group_number] = new_group local unit_groups_size = Public.get('unit_groups_size') Public.set('unit_groups_size', unit_groups_size + 1) return new_group else debug_print('Destroying stuck group.') if generated_units.unit_groups[group.group_number] then if generated_units.unit_group_last_command[group.group_number] then generated_units.unit_group_last_command[group.group_number] = nil end local positions = generated_units.unit_group_pos.positions if positions[group.group_number] then positions[group.group_number] = nil end table.remove(generated_units.unit_groups, group.group_number) local unit_groups_size = Public.get('unit_groups_size') Public.set('unit_groups_size', unit_groups_size - 1) end group.destroy() end return nil end local function get_side_targets(group) local unit_group_command_step_length = Public.get('unit_group_command_step_length') local commands = {} local group_position = {x = group.position.x, y = group.position.y} local step_length = unit_group_command_step_length local side_target = Public.get_side_target() local target_position = side_target.position local distance_to_target = floor(sqrt((target_position.x - group_position.x) ^ 2 + (target_position.y - group_position.y) ^ 2)) local steps = floor(distance_to_target / step_length) + 1 for i = 1, steps, 1 do local old_position = group_position local obstacles = group.surface.find_entities_filtered { position = old_position, radius = step_length * 2, type = {'simple-entity', 'tree'}, limit = 100 } if obstacles then for v = 1, #obstacles, 1 do if obstacles[v].valid then commands[#commands + 1] = { type = defines.command.attack, destination = obstacles[v].position, distraction = defines.distraction.by_anything } end end end commands[#commands + 1] = { type = defines.command.attack, target = side_target, distraction = defines.distraction.by_anything } end return commands end local function get_main_command(group) local unit_group_command_step_length = Public.get('unit_group_command_step_length') local commands = {} local group_position = {x = group.position.x, y = group.position.y} local step_length = unit_group_command_step_length local target = Public.get('target') if not valid(target) then return end debug_print('get_main_command - starting') local target_position = target.position local distance_to_target = floor(sqrt((target_position.x - group_position.x) ^ 2 + (target_position.y - group_position.y) ^ 2)) local steps = floor(distance_to_target / step_length) + 1 local vector = { round((target_position.x - group_position.x) / steps, 3), round((target_position.y - group_position.y) / steps, 3) } debug_print('get_commmands - to main target x' .. target_position.x .. ' y' .. target_position.y) debug_print('get_commmands - distance_to_target:' .. distance_to_target .. ' steps:' .. steps) debug_print('get_commmands - vector ' .. vector[1] .. '_' .. vector[2]) for i = 1, steps, 1 do local old_position = group_position group_position.x = group_position.x + vector[1] group_position.y = group_position.y + vector[2] local obstacles = group.surface.find_entities_filtered { position = old_position, radius = step_length / 2, type = {'simple-entity', 'tree'}, limit = 50 } if obstacles then shuffle_distance(obstacles, old_position) for ii = 1, #obstacles, 1 do if obstacles[ii].valid then commands[#commands + 1] = { type = defines.command.attack, target = obstacles[ii], distraction = defines.distraction.by_anything } end end end local position = group.surface.find_non_colliding_position('behemoth-biter', group_position, step_length, 1) if position then commands[#commands + 1] = { type = defines.command.attack_area, destination = {x = position.x, y = position.y}, radius = 16, distraction = defines.distraction.by_anything } end end commands[#commands + 1] = { type = defines.command.attack_area, destination = {x = target_position.x, y = target_position.y}, radius = 8, distraction = defines.distraction.by_anything } commands[#commands + 1] = { type = defines.command.attack, target = target, distraction = defines.distraction.by_anything } return commands end local function command_to_main_target(group, bypass) if not valid(group) then return end local generated_units = Public.get('generated_units') local unit_group_command_delay = Public.get('unit_group_command_delay') if not bypass then if not generated_units.unit_group_last_command[group.group_number] then generated_units.unit_group_last_command[group.group_number] = game.tick - (unit_group_command_delay + 1) end if generated_units.unit_group_last_command[group.group_number] then if generated_units.unit_group_last_command[group.group_number] + unit_group_command_delay > game.tick then return end end end local fill_tiles_so_biter_can_path = Public.get('fill_tiles_so_biter_can_path') if fill_tiles_so_biter_can_path then fill_tiles(group, 10) end local tile = group.surface.get_tile(group.position) if tile.valid and tile.collides_with('player-layer') then group = reform_group(group) end if not valid(group) then return end local commands = get_main_command(group) debug_print('get_main_command - got commands') local surface_index = Public.get('surface_index') if group.surface.index ~= surface_index then return end group.set_command( { type = defines.command.compound, structure_type = defines.compound_command.return_last, commands = commands } ) debug_print('get_main_command - sent commands') if valid(group) then generated_units.unit_group_last_command[group.group_number] = game.tick end end local function command_to_side_target(group) local generated_units = Public.get('generated_units') local unit_group_command_delay = Public.get('unit_group_command_delay') if not generated_units.unit_group_last_command[group.group_number] then generated_units.unit_group_last_command[group.group_number] = game.tick - (unit_group_command_delay + 1) end if generated_units.unit_group_last_command[group.group_number] then if generated_units.unit_group_last_command[group.group_number] + unit_group_command_delay > game.tick then return end end local tile = group.surface.get_tile(group.position) if tile.valid and tile.collides_with('player-layer') then group = reform_group(group) end local commands = get_side_targets(group) group.set_command( { type = defines.command.compound, structure_type = defines.compound_command.return_last, commands = commands } ) generated_units.unit_group_last_command[group.group_number] = game.tick end local function give_side_commands_to_group() local enable_side_target = Public.get('enable_side_target') if not enable_side_target then return end local target = Public.get('target') if not valid(target) then return end local generated_units = Public.get('generated_units') for k, group in pairs(generated_units.unit_groups) do if type(group) ~= 'number' then if group.valid then command_to_side_target(group) else get_active_unit_groups_count() end end end end local function give_main_command_to_group() local target = Public.get('target') if not valid(target) then return end local generated_units = Public.get('generated_units') for k, group in pairs(generated_units.unit_groups) do if type(group) ~= 'number' then if group.valid then if group.surface.index == target.surface.index then command_to_main_target(group) end else get_active_unit_groups_count() end end end end local function spawn_unit_group(fs, only_bosses) if fs then debug_print('spawn_unit_group - forcing new biters') else if not can_units_spawn() then debug_print('spawn_unit_group - Cant spawn units?') return end end local target = Public.get('target') if not valid(target) then debug_print('spawn_unit_group - Target was not valid?') return end local max_active_unit_groups = Public.get('max_active_unit_groups') if fs then debug_print('spawn_unit_group - forcing new biters') else if get_active_unit_groups_count() >= max_active_unit_groups then debug_print('spawn_unit_group - unit_groups at max') return end end local surface_index = Public.get('surface_index') local remove_entities = Public.get('remove_entities') local surface = game.surfaces[surface_index] set_group_spawn_position(surface) local spawn_position = get_spawn_pos() if not spawn_position then return end local radius = 10 local area = { left_top = {spawn_position.x - radius, spawn_position.y - radius}, right_bottom = {spawn_position.x + radius, spawn_position.y + radius} } for k, v in pairs(surface.find_entities_filtered {area = area, name = 'land-mine'}) do if v and v.valid then debug_print('spawn_unit_group - found land-mines') v.die() end end if remove_entities then remove_trees({surface = surface, position = spawn_position, valid = true}) remove_rocks({surface = surface, position = spawn_position, valid = true}) fill_tiles({surface = surface, position = spawn_position, valid = true}) end local wave_number = Public.get('wave_number') Public.wave_defense_set_unit_raffle(wave_number) debug_print('Spawning unit group at x' .. spawn_position.x .. ' y' .. spawn_position.y) local generated_units = Public.get('generated_units') local unit_group = surface.create_unit_group({position = spawn_position, force = 'enemy'}) generated_units.unit_group_pos.index = generated_units.unit_group_pos.index + 1 generated_units.unit_group_pos.positions[unit_group.group_number] = {position = unit_group.position, index = 0} local average_unit_group_size = Public.get('average_unit_group_size') local unit_settings = Public.get('unit_settings') if not only_bosses then for _ = 1, average_unit_group_size, 1 do local biter = spawn_biter(surface, spawn_position, fs, false, unit_settings) if not biter then debug_print('spawn_unit_group - No biters were found?') break end unit_group.add_member(biter) -- command_to_side_target(unit_group) end end local boss_wave = Public.get('boss_wave') if boss_wave or only_bosses then local count = random(1, floor(wave_number * 0.01) + 2) if count > 16 then count = 16 end if count <= 1 then count = 4 end for _ = 1, count, 1 do local biter = spawn_biter(surface, spawn_position, fs, true, unit_settings) if not biter then debug_print('spawn_unit_group - No biters were found?') break end unit_group.add_member(biter) end Public.set('boss_wave', false) end generated_units.unit_groups[unit_group.group_number] = unit_group local unit_groups_size = Public.get('unit_groups_size') Public.set('unit_groups_size', unit_groups_size + 1) if random(1, 2) == 1 then Public.set('random_group', unit_group) end Public.set('spot', 'nil') return true end local function check_group_positions() local generated_units = Public.get('generated_units') local target = Public.get('target') if not valid(target) then return end for k, group in pairs(generated_units.unit_groups) do if group.valid then local ugp = generated_units.unit_group_pos.positions if group.state == defines.group_state.finished then return command_to_main_target(group, true) end if ugp[group.group_number] then local success = is_position_near(group.position, ugp[group.group_number].position) if success then ugp[group.group_number].index = ugp[group.group_number].index + 1 if ugp[group.group_number].index >= 2 then command_to_main_target(group, true) fill_tiles(group, 30) remove_rocks(group) remove_trees(group) if ugp[group.group_number].index >= 4 then generated_units.unit_group_pos.positions[group.group_number] = nil reform_group(group) end end end end end end end local function log_threat() local threat_log_index = Public.get('threat_log_index') Public.set('threat_log_index', threat_log_index + 1) local threat_log = Public.get('threat_log') local threat = Public.get('threat') threat_log_index = Public.get('threat_log_index') threat_log[threat_log_index] = threat if threat_log_index > 900 then threat_log[threat_log_index - 901] = nil end end local tick_tasks = { [30] = set_main_target, [60] = set_enemy_evolution, [120] = give_main_command_to_group, [150] = Public.build_nest, [180] = Public.build_worm, [1200] = give_side_commands_to_group, [3600] = time_out_biters, [7200] = refresh_active_unit_threat } Public.spawn_unit_group = spawn_unit_group Event.on_nth_tick( 30, function() local tick = game.tick local game_lost = Public.get('game_lost') if game_lost then return end local paused = Public.get('paused') if paused then return end local enable_grace_time = Public.get('enable_grace_time') if enable_grace_time and (not enable_grace_time.enabled) then if not enable_grace_time.set then Public.set('next_wave', game.tick + 100) enable_grace_time.set = true end end local next_wave = Public.get('next_wave') if tick > next_wave then set_next_wave() end local t = tick % 300 local t2 = tick % 18000 if tick_tasks[t] then tick_tasks[t](true) end if tick_tasks[t2] then tick_tasks[t2](true) end local resolve_pathing = Public.get('resolve_pathing') if resolve_pathing then if tick % 60 == 0 then check_group_positions() end end local enable_threat_log = Public.get('enable_threat_log') if enable_threat_log then if tick % 60 == 0 then log_threat() end end local players = game.connected_players for _, player in pairs(players) do Public.update_gui(player) end end ) Event.on_nth_tick( 50, function() local tick_to_spawn_unit_groups = Public.get('tick_to_spawn_unit_groups') local will_not_spawn = game.tick % tick_to_spawn_unit_groups ~= 0 if will_not_spawn then return end local game_lost = Public.get('game_lost') if game_lost then return end local paused = Public.get('paused') if paused then return end spawn_unit_group() end ) return Public