diff --git a/maps/biter_battles_v2/ai.lua b/maps/biter_battles_v2/ai.lua index e8867a2e..5981bb8b 100644 --- a/maps/biter_battles_v2/ai.lua +++ b/maps/biter_battles_v2/ai.lua @@ -34,6 +34,12 @@ local threat_values = { ["spitter-spawner"] = 16 } +-- these areas are for north +local left_spawner_area = {left_top = {-2000, -1200}, right_bottom = {-800, -700}} +local right_spawner_area = {left_top = {800, -1200}, right_bottom = {2000, -700}} +local middle_spawner_area = {left_top = {-600, -1000}, right_bottom = {600, -400}} +local whole_spawner_area = {left_top = {-2048, -1400}, right_bottom = {2048, -400}} + local function get_active_biter_count(biter_force_name) local count = 0 for _, biter in pairs(global.active_biters[biter_force_name]) do @@ -51,11 +57,17 @@ local unit_evo_limits = { ["big-biter"] = 2, } -function Public.set_biter_raffle_table() - local surface = game.surfaces["biter_battles"] - local biter_force_name = global.next_attack .. "_biters" - local biters = surface.find_entities_filtered({type = "unit", force = biter_force_name}) - if not biters[5] then return end +local function set_biter_raffle_table(surface, biter_force_name) + -- It's fine to only sample the middle + local area = middle_spawner_area + + -- If south_biters: Mirror area along x-axis + if biter_force_name == "south_biters" then + area = {left_top = {area.left_top[1], -1*area.right_bottom[2]}, right_bottom = {area.right_bottom[1], -1*area.left_top[2]}} + end + + local biters = surface.find_entities_filtered({type = "unit", force = biter_force_name, area = area}) + if not biters[1] then return end global.biter_raffle[biter_force_name] = {} local raffle = global.biter_raffle[biter_force_name] local i = 1 @@ -166,15 +178,66 @@ Public.send_near_biters_to_silo = function() end local function get_random_close_spawner(surface, biter_force_name) - local spawners = surface.find_entities_filtered({type = "unit-spawner", force = biter_force_name}) + -- Here we have two options: + -- 1. Random the area (left, middle, right) => smaller area to search + -- 2. Search the whole area => larger area => if it's not cached we get a lag spike (~20ms) + + -- + -- Option 1 + -- + local rand_value = math_random(1, 10) + + -- assume north_biters first + local area = nil + if rand_value == 1 then + area_name = "left" + area = left_spawner_area + elseif rand_value == 2 then + area_name = "right" + area = right_spawner_area + else + area_name = "middle" + area = middle_spawner_area + end + + -- + -- Option 2 + -- + + -- area = whole_spawner_area + -- area_name = whole + + -- After here it's the same + + -- If south_biters: Mirror area along x-axis + if biter_force_name == "south_biters" then + area_name = area_name .. "_south" + area = {left_top = {area.left_top[1], -1*area.right_bottom[2]}, right_bottom = {area.right_bottom[1], -1*area.left_top[2]}} + end + + -- If possible get the spawners from cache + -- TODO: If a spawner was destroyed, we still have it in cache, this is fine for now as we just use spawner.position + local spawners = nil + if not global.cached_spawners then global.cached_spawners = {} end + if global.cached_spawners[area_name] then + spawners = global.cached_spawners[area_name] + else + spawners = surface.find_entities_filtered({type = "unit-spawner", force = biter_force_name, area = area}) + global.cached_spawners[area_name] = spawners + end + if not spawners[1] then return false end - + local spawner = spawners[math_random(1,#spawners)] + -- game.forces["north"].add_chart_tag("biter_battles", {icon={type="virtual", name="signal-green"}, position=spawner.position}) for i = 1, 5, 1 do local spawner_2 = spawners[math_random(1,#spawners)] + -- game.forces["north"].add_chart_tag("biter_battles", {icon={type="virtual", name="signal-green"}, position=spawner_2.position}) if spawner_2.position.x ^ 2 + spawner_2.position.y ^ 2 < spawner.position.x ^ 2 + spawner.position.y ^ 2 then spawner = spawner_2 end end + -- rendering.draw_rectangle{color={g=1}, width=5, filled=false, left_top=area.left_top, right_bottom=area.right_bottom, surface="biter_battles"} + -- game.forces["north"].add_chart_tag("biter_battles", {icon={type="virtual", name="signal-blue"}, position=spawner.position}) return spawner end @@ -330,7 +393,7 @@ local function create_attack_group(surface, force_name, biter_force_name) if global.bb_debug then game.print("No spawner found for team " .. force_name) end return false end - + local nearest_player_unit = surface.find_nearest_enemy({position = spawner.position, max_distance = 2048, force = biter_force_name}) if not nearest_player_unit then nearest_player_unit = global.rocket_silo[force_name] end @@ -345,20 +408,36 @@ local function create_attack_group(surface, force_name, biter_force_name) global.unit_groups[unit_group.group_number] = unit_group end -Public.main_attack = function() +Public.pre_main_attack = function() local surface = game.surfaces["biter_battles"] local force_name = global.next_attack - + if not global.training_mode or (global.training_mode and #game.forces[force_name].connected_players > 0) then local biter_force_name = force_name .. "_biters" - local wave_amount = math.ceil(get_threat_ratio(biter_force_name) * 7) - - for c = 1, wave_amount, 1 do - create_attack_group(surface, force_name, biter_force_name) - end - if global.bb_debug then game.print(wave_amount .. " unit groups designated for " .. force_name .. " biters.") end + global.main_attack_wave_amount = math.ceil(get_threat_ratio(biter_force_name) * 7) + + set_biter_raffle_table(surface, biter_force_name) + + if global.bb_debug then game.print(global.main_attack_wave_amount .. " unit groups designated for " .. force_name .. " biters.") end + else + global.main_attack_wave_amount = 0 end - +end + + +Public.perform_main_attack = function() + if global.main_attack_wave_amount > 0 then + local surface = game.surfaces["biter_battles"] + local force_name = global.next_attack + local biter_force_name = force_name .. "_biters" + + create_attack_group(surface, force_name, biter_force_name) + global.main_attack_wave_amount = global.main_attack_wave_amount - 1 + end +end + +Public.post_main_attack = function() + global.main_attack_wave_amount = 0 if global.next_attack == "north" then global.next_attack = "south" else @@ -420,4 +499,12 @@ function Public.subtract_threat(entity) return true end -return Public \ No newline at end of file +-- Invalidate the cache if a new chunk was generated +-- TODO: This can be more precise +Public.on_chunk_generated = function(event) + if event.area.right_bottom.x < -2048 then return end + if event.area.left_top.x > 2048 then return end + global.cached_spawners = {} +end + +return Public diff --git a/maps/biter_battles_v2/main.lua b/maps/biter_battles_v2/main.lua index 38811d54..0f75bf98 100644 --- a/maps/biter_battles_v2/main.lua +++ b/maps/biter_battles_v2/main.lua @@ -94,32 +94,43 @@ end local tick_minute_functions = { [300 * 1] = Ai.raise_evo, [300 * 2] = Ai.destroy_inactive_biters, - [300 * 3] = Ai.set_biter_raffle_table, - [300 * 4] = Ai.main_attack, - [300 * 5] = Ai.send_near_biters_to_silo, - [300 * 6] = Ai.wake_up_sleepy_groups, + [300 * 3 + 30 * 0] = Ai.pre_main_attack, -- setup for main_attack + [300 * 3 + 30 * 1] = Ai.perform_main_attack, -- call perform_main_attack 7 times on different ticks + [300 * 3 + 30 * 2] = Ai.perform_main_attack, -- some of these might do nothing (if there are no wave left) + [300 * 3 + 30 * 3] = Ai.perform_main_attack, + [300 * 3 + 30 * 4] = Ai.perform_main_attack, + [300 * 3 + 30 * 5] = Ai.perform_main_attack, + [300 * 3 + 30 * 6] = Ai.perform_main_attack, + [300 * 3 + 30 * 7] = Ai.perform_main_attack, + [300 * 3 + 30 * 8] = Ai.post_main_attack, + [300 * 4] = Ai.send_near_biters_to_silo, + [300 * 5] = Ai.wake_up_sleepy_groups, [300 * 7] = Game_over.restart_idle_map, } local function on_tick(event) Mirror_terrain.ticking_work() + + local tick = game.tick - local tick = game.tick - - if tick % 60 ~= 0 then return end - global.bb_threat["north_biters"] = global.bb_threat["north_biters"] + global.bb_threat_income["north_biters"] - global.bb_threat["south_biters"] = global.bb_threat["south_biters"] + global.bb_threat_income["south_biters"] + if tick % 60 == 0 then + global.bb_threat["north_biters"] = global.bb_threat["north_biters"] + global.bb_threat_income["north_biters"] + global.bb_threat["south_biters"] = global.bb_threat["south_biters"] + global.bb_threat_income["south_biters"] + end if tick % 180 == 0 then Gui.refresh() end - if tick % 300 ~= 0 then return end - Gui.spy_fish() - if global.bb_game_won_by_team then - Game_over.reveal_map() - Game_over.server_restart() - return + if tick % 300 == 0 then + Gui.spy_fish() + + if global.bb_game_won_by_team then + Game_over.reveal_map() + Game_over.server_restart() + return + end end + if tick % 30 ~= 0 then return end local key = tick % 3600 if tick_minute_functions[key] then tick_minute_functions[key]() end end @@ -169,9 +180,10 @@ Event.add(defines.events.on_research_finished, on_research_finished) Event.add(defines.events.on_robot_built_entity, on_robot_built_entity) Event.add(defines.events.on_robot_built_tile, on_robot_built_tile) Event.add(defines.events.on_tick, on_tick) +Event.add(defines.events.on_chunk_generated, Ai.on_chunk_generated) Event.on_init(on_init) Event.add_event_filter(defines.events.on_entity_damaged, { filter = "name", name = "rocket-silo" }) require "maps.biter_battles_v2.spec_spy" -require "maps.biter_battles_v2.difficulty_vote" \ No newline at end of file +require "maps.biter_battles_v2.difficulty_vote"