From 00e1f84dad40435a0cde9dc160d21a77663457a1 Mon Sep 17 00:00:00 2001 From: MewMew Date: Sat, 18 Apr 2020 12:10:54 +0200 Subject: [PATCH] Update biter raffle replaced outposts are no longer safe attack group strength is reduced up to 50% for far away outposts biters seek a random target as side target, before making their way to the silo side targets are more likely chosen near the center of the map variety in unit group types (biter only, spitters only, mixed) unit groups spawn more close to their nests --- functions/biter_raffle.lua | 4 +- maps/biter_battles_v2/ai.lua | 260 ++++++++++------------- maps/biter_battles_v2/config.lua | 2 +- maps/biter_battles_v2/functions.lua | 44 ++++ maps/biter_battles_v2/init.lua | 4 +- maps/biter_battles_v2/main.lua | 13 +- maps/biter_battles_v2/mirror_terrain.lua | 3 + 7 files changed, 179 insertions(+), 151 deletions(-) diff --git a/functions/biter_raffle.lua b/functions/biter_raffle.lua index 03f01a32..b767a69f 100644 --- a/functions/biter_raffle.lua +++ b/functions/biter_raffle.lua @@ -13,7 +13,7 @@ local math_floor = math.floor local function get_biter_raffle_table(level) local raffle = { ["small-biter"] = 1000 - level * 1.75, - ["medium-biter"] = level, + ["medium-biter"] = -250 + level * 1.5, ["big-biter"] = 0, ["behemoth-biter"] = 0, } @@ -48,7 +48,7 @@ end local function get_spitter_raffle_table(level) local raffle = { ["small-spitter"] = 1000 - level * 1.75, - ["medium-spitter"] = level, + ["medium-spitter"] = -250 + level * 1.5, ["big-spitter"] = 0, ["behemoth-spitter"] = 0, } diff --git a/maps/biter_battles_v2/ai.lua b/maps/biter_battles_v2/ai.lua index b7140149..7c09f949 100644 --- a/maps/biter_battles_v2/ai.lua +++ b/maps/biter_battles_v2/ai.lua @@ -1,6 +1,9 @@ local Public = {} +local BiterRaffle = require "functions.biter_raffle" +local Functions = require "maps.biter_battles_v2.functions" local bb_config = require "maps.biter_battles_v2.config" local math_random = math.random +local math_abs = math.abs local vector_radius = 512 local attack_vectors = {} @@ -17,6 +20,8 @@ for x = vector_radius * -1, vector_radius, 1 do end local size_of_vectors = #attack_vectors.north +local unit_type_raffle = {"biter", "biter", "biter", "mixed", "mixed", "spitter"} +local size_of_unit_type_raffle = #unit_type_raffle local threat_values = { ["small-spitter"] = 1.5, @@ -35,10 +40,6 @@ local threat_values = { ["spitter-spawner"] = 16 } --- these areas are for north -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 @@ -47,44 +48,19 @@ local function get_active_biter_count(biter_force_name) return count end -local unit_evo_limits = { - ["small-spitter"] = 0.6, - ["small-biter"] = 0.6, - ["medium-spitter"] = 0.8, - ["medium-biter"] = 0.8, - ["big-spitter"] = 2, - ["big-biter"] = 2, -} - -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 - local evolution_factor = global.bb_evolution[biter_force_name] - - for key, e in pairs(biters) do - if key % 5 == 0 then - if unit_evo_limits[e.name] then - if evolution_factor < unit_evo_limits[e.name] then - raffle[i] = e.name - i = i + 1 - end - else - raffle[i] = e.name - i = i + 1 - end +local function get_target_entity(force_name) + local force_index = game.forces[force_name].index + local target_entity = Functions.get_random_target_entity(force_index) + if not target_entity then print("Unable to get target entity for " .. force_name .. ".") return end + for _ = 1, 3, 1 do + local e = Functions.get_random_target_entity(force_index) + if math_abs(e.position.x) < math_abs(target_entity.position.x) then + target_entity = e end - end + end + if not target_entity then print("Unable to get target entity for " .. force_name .. ".") return end + print("Target entity for " .. force_name .. ": " .. target_entity.name .. " at x=" .. target_entity.position.x .. " y=" .. target_entity.position.y) + return target_entity end local function get_threat_ratio(biter_force_name) @@ -159,9 +135,9 @@ Public.send_near_biters_to_silo = function() target=global.rocket_silo["north"], distraction=defines.distraction.none }, - unit_count = 16, + unit_count = 8, force = "north_biters", - unit_search_distance=128 + unit_search_distance = 64 }) game.surfaces["biter_battles"].set_multi_command({ @@ -170,9 +146,9 @@ Public.send_near_biters_to_silo = function() target=global.rocket_silo["south"], distraction=defines.distraction.none }, - unit_count = 16, + unit_count = 8, force = "south_biters", - unit_search_distance=128 + unit_search_distance = 64 }) end @@ -193,70 +169,74 @@ local function get_random_spawner(biter_force_name) end end -local function get_random_close_spawner(surface, biter_force_name) - local nearest_spawner = get_random_spawner(biter_force_name) - if not nearest_spawner then return end - - for _ = 1, 16, 1 do - local spawner = get_random_spawner(biter_force_name) - if spawner.position.x ^ 2 + spawner.position.y ^ 2 < nearest_spawner.position.x ^ 2 + nearest_spawner.position.y ^ 2 then - nearest_spawner = spawner - end - end +local function select_units_around_spawner(spawner, force_name, side_target) + local biter_force_name = spawner.force.name - return nearest_spawner -end - -local function select_units_around_spawner(spawner, force_name, biter_force_name) - local biters = spawner.surface.find_enemy_units(spawner.position, 160, force_name) - if not biters[1] then return false end local valid_biters = {} + local i = 0 local threat = global.bb_threat[biter_force_name] * math_random(8, 32) * 0.01 + --threat modifier for outposts + local m = math_abs(side_target.position.x) - 512 + if m < 0 then m = 0 end + m = 1 - m * 0.001 + if m < 0.5 then m = 0.5 end + threat = threat * m + local unit_count = 0 local max_unit_count = math.floor(global.bb_threat[biter_force_name] * 0.25) + math_random(6,12) if max_unit_count > bb_config.max_group_size then max_unit_count = bb_config.max_group_size end - for _, biter in pairs(biters) do - if unit_count >= max_unit_count then break end - if biter.force.name == biter_force_name and global.active_biters[biter.force.name][biter.unit_number] == nil then - valid_biters[#valid_biters + 1] = biter - global.active_biters[biter.force.name][biter.unit_number] = {entity = biter, active_since = game.tick} - unit_count = unit_count + 1 - threat = threat - threat_values[biter.name] - end - if threat < 0 then break end - end - - --Manual spawning of additional units - local size_of_biter_raffle = #global.biter_raffle[biter_force_name] - if size_of_biter_raffle > 0 then - for c = 1, max_unit_count - unit_count, 1 do - if threat < 0 then break end - local biter_name = global.biter_raffle[biter_force_name][math_random(1, size_of_biter_raffle)] - local position = spawner.surface.find_non_colliding_position(biter_name, spawner.position, 128, 2) - if not position then break end - - local biter = spawner.surface.create_entity({name = biter_name, force = biter_force_name, position = position}) - threat = threat - threat_values[biter.name] - - valid_biters[#valid_biters + 1] = biter - global.active_biters[biter.force.name][biter.unit_number] = {entity = biter, active_since = game.tick} + --Collect biters around spawners + if math_random(1, 2) == 1 then + local biters = spawner.surface.find_enemy_units(spawner.position, 160, force_name) + if biters[1] then + for _, biter in pairs(biters) do + if unit_count >= max_unit_count then break end + if biter.force.name == biter_force_name and global.active_biters[biter.force.name][biter.unit_number] == nil then + i = i + 1 + valid_biters[i] = biter + global.active_biters[biter.force.name][biter.unit_number] = {entity = biter, active_since = game.tick} + unit_count = unit_count + 1 + threat = threat - threat_values[biter.name] + end + if threat < 0 then break end + end end end + --Manual spawning of units + local roll_type = unit_type_raffle[math_random(1, size_of_unit_type_raffle)] + for c = 1, max_unit_count - unit_count, 1 do + if threat < 0 then break end + local unit_name = BiterRaffle.roll(roll_type, global.bb_evolution[biter_force_name]) + local position = spawner.surface.find_non_colliding_position(unit_name, spawner.position, 128, 2) + if not position then break end + local biter = spawner.surface.create_entity({name = unit_name, force = biter_force_name, position = position}) + threat = threat - threat_values[biter.name] + i = i + 1 + valid_biters[i] = biter + global.active_biters[biter.force.name][biter.unit_number] = {entity = biter, active_since = game.tick} + end + if global.bb_debug then game.print(get_active_biter_count(biter_force_name) .. " active units for " .. biter_force_name) end return valid_biters end -local function send_group(unit_group, force_name, nearest_player_unit) - local target = nearest_player_unit.position - if math_random(1,2) == 1 then target = global.rocket_silo[force_name].position end +local function send_group(unit_group, force_name, side_target) + local target + if side_target then + target = side_target + else + target = get_target_entity(force_name) + end + if not target then print("No target for " .. force_name .. " biters.") return end - local commands = {} + target = target.position + local commands = {} local vector = attack_vectors[force_name][math_random(1, size_of_vectors)] local distance_modifier = math_random(25, 100) * 0.01 @@ -294,44 +274,13 @@ local function send_group(unit_group, force_name, nearest_player_unit) return true end -local function is_chunk_empty(surface, area) - if surface.count_entities_filtered({type = {"unit-spawner", "unit"}, area = area}) ~= 0 then return false end - if surface.count_entities_filtered({force = {"north", "south"}, area = area}) ~= 0 then return false end - if surface.count_tiles_filtered({name = {"water", "deepwater"}, area = area}) ~= 0 then return false end - return true -end - -local function get_unit_group_position(surface, nearest_player_unit, spawner) - - if math_random(1,3) ~= 1 then - local spawner_chunk_position = {x = math.floor(spawner.position.x / 32), y = math.floor(spawner.position.y / 32)} - local valid_chunks = {} - for x = -2, 2, 1 do - for y = -2, 2, 1 do - local chunk = {x = spawner_chunk_position.x + x, y = spawner_chunk_position.y + y} - local area = {{chunk.x * 32, chunk.y * 32},{chunk.x * 32 + 32, chunk.y * 32 + 32}} - if is_chunk_empty(surface, area) then - valid_chunks[#valid_chunks + 1] = chunk - end - end - end - - if #valid_chunks > 0 then - local chunk = valid_chunks[math_random(1, #valid_chunks)] - return {x = chunk.x * 32 + 16, y = chunk.y * 32 + 16} - end - end - - local unit_group_position = {x = (spawner.position.x + nearest_player_unit.position.x) * 0.5, y = (spawner.position.y + nearest_player_unit.position.y) * 0.5} - local pos = surface.find_non_colliding_position("rocket-silo", unit_group_position, 256, 1) - if pos then unit_group_position = pos end - - if not unit_group_position then - if global.bb_debug then game.print("No unit_group_position found for team " .. force_name) end - return false - end - - return unit_group_position +local function get_unit_group_position(spawner) + local p = spawner.surface.find_non_colliding_position("electric-furnace", spawner.position, 512, 1) + if not p then + if global.bb_debug then game.print("No unit_group_position found for team " .. spawner.force.name) end + return + end + return p end local function get_active_threat(biter_force_name) @@ -346,6 +295,27 @@ local function get_active_threat(biter_force_name) return active_threat end +local function get_nearby_biter_nest(target_entity) + local center = target_entity.position + local biter_force_name = target_entity.force.name .. "_biters" + local spawner = get_random_spawner(biter_force_name) + if not spawner then return end + local best_distance = (center.x - spawner.position.x) ^ 2 + (center.y - spawner.position.y) ^ 2 + + for i = 1, 16, 1 do + local new_spawner = get_random_spawner(biter_force_name) + local new_distance = (center.x - new_spawner.position.x) ^ 2 + (center.y - new_spawner.position.y) ^ 2 + if new_distance < best_distance then + spawner = new_spawner + best_distance = new_distance + end + end + + if not spawner then return end + print("Nearby biter nest found at x=" .. spawner.position.x .. " y=" .. spawner.position.y .. ".") + return spawner +end + local function create_attack_group(surface, force_name, biter_force_name) local threat = global.bb_threat[biter_force_name] if get_active_threat(biter_force_name) > threat * 1.20 then return end @@ -356,22 +326,28 @@ local function create_attack_group(surface, force_name, biter_force_name) return false end - local spawner = get_random_close_spawner(surface, biter_force_name) - if not spawner then - if global.bb_debug then game.print("No spawner found for team " .. force_name) end - return false + local side_target = get_target_entity(force_name) + if not side_target then + print("No side target found for " .. force_name .. ".") + return 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 - local unit_group_position = get_unit_group_position(surface, nearest_player_unit, spawner) + local spawner = get_nearby_biter_nest(side_target) + if not spawner then + print("No spawner found for " .. force_name .. ".") + return + end + + local unit_group_position = get_unit_group_position(spawner) + if not unit_group_position then return end + + local units = select_units_around_spawner(spawner, force_name, side_target) + if not units then return end - local units = select_units_around_spawner(spawner, force_name, biter_force_name) - if not units then return false end local unit_group = surface.create_unit_group({position = unit_group_position, force = biter_force_name}) for _, unit in pairs(units) do unit_group.add_member(unit) end - send_group(unit_group, force_name, nearest_player_unit) + + send_group(unit_group, force_name, side_target) global.unit_groups[unit_group.group_number] = unit_group end @@ -384,8 +360,6 @@ Public.pre_main_attack = function() local biter_force_name = force_name .. "_biters" 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 @@ -426,9 +400,7 @@ Public.wake_up_sleepy_groups = function() if unit_group then if unit_group.valid then if unit_group.state == defines.group_state.finished then - local nearest_player_unit = entity.surface.find_nearest_enemy({position = entity.position, max_distance = 2048, force = biter_force_name}) - if not nearest_player_unit then nearest_player_unit = global.rocket_silo[force_name] end - send_group(unit_group, force_name, nearest_player_unit) + send_group(unit_group, force_name) print("BiterBattles: Woke up Unit Group at x" .. unit_group.position.x .. " y" .. unit_group.position.y .. ".") return end diff --git a/maps/biter_battles_v2/config.lua b/maps/biter_battles_v2/config.lua index 83c25643..0bd5aac0 100644 --- a/maps/biter_battles_v2/config.lua +++ b/maps/biter_battles_v2/config.lua @@ -12,7 +12,7 @@ local bb_config = { --BITER SETTINGS-- ["max_active_biters"] = 2000, --Maximum total amount of attacking units per side. - ["max_group_size"] = 256, --Maximum unit group size. + ["max_group_size"] = 288, --Maximum unit group size. ["biter_timeout"] = 162000, --Time it takes in ticks for an attacking unit to be deleted. This prevents perma stuck units. ["bitera_area_distance"] = 512 --Distance to the biter area. } diff --git a/maps/biter_battles_v2/functions.lua b/maps/biter_battles_v2/functions.lua index 49204f08..135f5bbe 100644 --- a/maps/biter_battles_v2/functions.lua +++ b/maps/biter_battles_v2/functions.lua @@ -1,6 +1,8 @@ local string_sub = string.sub local math_random = math.random local math_abs = math.abs +local table_insert = table.insert +local table_remove = table.remove local balance_functions = { ["flamethrower"] = function(force_name) @@ -43,8 +45,50 @@ local landfill_biters = { ["behemoth-spitter"] = true, } +local target_entity_types = { + ["assembling-machine"] = true, + ["boiler"] = true, + ["furnace"] = true, + ["generator"] = true, + ["lab"] = true, + ["mining-drill"] = true, + ["radar"] = true, + ["reactor"] = true, + ["roboport"] = true, + ["rocket-silo"] = true, + ["ammo-turret"] = true, + ["artillery-turret"] = true, + ["beacon"] = true, + ["electric-turret"] = true, + ["fluid-turret"] = true, +} + local Public = {} +function Public.add_target_entity(entity) + if not entity then return end + if not entity.valid then return end + if not target_entity_types[entity.type] then return end + table_insert(global.target_entities[entity.force.index], entity) +end + +function Public.get_random_target_entity(force_index) + local target_entities = global.target_entities[force_index] + local size_of_target_entities = #target_entities + if size_of_target_entities == 0 then return end + for _ = 1, size_of_target_entities, 1 do + local i = math_random(1, size_of_target_entities) + local entity = target_entities[i] + if entity and entity.valid then + return entity + else + table_remove(target_entities, i) + size_of_target_entities = size_of_target_entities - 1 + if size_of_target_entities == 0 then return end + end + end +end + function Public.biters_landfill(entity) if not landfill_biters[entity.name] then return end local position = entity.position diff --git a/maps/biter_battles_v2/init.lua b/maps/biter_battles_v2/init.lua index 097e1951..dc71c47c 100644 --- a/maps/biter_battles_v2/init.lua +++ b/maps/biter_battles_v2/init.lua @@ -174,6 +174,7 @@ function Public.forces() for _, d in pairs(defs) do p.set_allows_action(d, true) end + global.target_entities = {} global.rocket_silo = {} global.spectator_rejoin_delay = {} global.spy_fish_timeout = {} @@ -183,7 +184,6 @@ function Public.forces() global.unit_spawners.south_biters = {} global.active_biters = {} global.unit_groups = {} - global.biter_raffle = {} global.evo_raise_counter = 1 global.next_attack = "north" if math.random(1,2) == 1 then global.next_attack = "south" end @@ -206,9 +206,9 @@ function Public.forces() game.forces[force.name].technologies["artillery-shell-speed-1"].enabled = false game.forces[force.name].technologies["atomic-bomb"].enabled = false game.forces[force.name].research_queue_enabled = true + global.target_entities[force.index] = {} global.spy_fish_timeout[force.name] = 0 global.active_biters[force.name] = {} - global.biter_raffle[force.name] = {} global.bb_evolution[force.name] = 0 global.bb_threat_income[force.name] = 0 global.bb_threat[force.name] = 0 diff --git a/maps/biter_battles_v2/main.lua b/maps/biter_battles_v2/main.lua index 1c9552be..3d7d54ac 100644 --- a/maps/biter_battles_v2/main.lua +++ b/maps/biter_battles_v2/main.lua @@ -57,11 +57,13 @@ end local function on_built_entity(event) Functions.no_turret_creep(event) + Functions.add_target_entity(event.created_entity) end local function on_robot_built_entity(event) Functions.no_turret_creep(event) Terrain.deny_construction_bots(event) + Functions.add_target_entity(event.created_entity) end local function on_entity_died(event) @@ -161,14 +163,21 @@ local function on_init() surface.request_to_generate_chunks({x = 0, y = 0}, 1) surface.force_generate_chunk_requests() - for y = -576, 576, 32 do + for y = 0, 576, 32 do surface.request_to_generate_chunks({x = 80, y = y + 16}, 0) surface.request_to_generate_chunks({x = 48, y = y + 16}, 0) surface.request_to_generate_chunks({x = 16, y = y + 16}, 0) surface.request_to_generate_chunks({x = -16, y = y - 16}, 0) surface.request_to_generate_chunks({x = -48, y = y - 16}, 0) surface.request_to_generate_chunks({x = -80, y = y - 16}, 0) - end + + surface.request_to_generate_chunks({x = 80, y = y * -1 + 16}, 0) + surface.request_to_generate_chunks({x = 48, y = y * -1 + 16}, 0) + surface.request_to_generate_chunks({x = 16, y = y * -1 + 16}, 0) + surface.request_to_generate_chunks({x = -16, y = y * -1 - 16}, 0) + surface.request_to_generate_chunks({x = -48, y = y * -1 - 16}, 0) + surface.request_to_generate_chunks({x = -80, y = y * -1 - 16}, 0) + end local surface = game.surfaces["bb_source"] surface.request_to_generate_chunks({x = 0, y = 0}, 2) diff --git a/maps/biter_battles_v2/mirror_terrain.lua b/maps/biter_battles_v2/mirror_terrain.lua index e81e1b2c..b74d7c58 100644 --- a/maps/biter_battles_v2/mirror_terrain.lua +++ b/maps/biter_battles_v2/mirror_terrain.lua @@ -1,5 +1,6 @@ local Public = {} +local Functions = require "maps.biter_battles_v2.functions" local table_remove = table.remove local table_insert = table.insert @@ -74,6 +75,7 @@ local entity_copy_functions = { if surface.count_entities_filtered({name = "rocket-silo", area = {{target_position.x - 8, target_position.y - 8},{target_position.x + 8, target_position.y + 8}}}) > 0 then return end global.rocket_silo[force_name] = surface.create_entity({name = entity.name, position = target_position, direction = direction_translation[entity.direction], force = force_name}) global.rocket_silo[force_name].minable = false + Functions.add_target_entity(global.rocket_silo[force_name]) end, ["ammo-turret"] = function(surface, entity, target_position, force_name) local direction = 0 @@ -81,6 +83,7 @@ local entity_copy_functions = { local mirror_entity = {name = entity.name, position = target_position, force = force_name, direction = direction} if not surface.can_place_entity(mirror_entity) then return end local e = surface.create_entity(mirror_entity) + Functions.add_target_entity(e) local inventory = entity.get_inventory(defines.inventory.turret_ammo) if inventory.is_empty() then return end for name, count in pairs(inventory.get_contents()) do e.insert({name = name, count = count}) end