1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-24 03:47:58 +02:00
MewMew 913d42d6d8 Update
added tooltip for current biter health and damage bonus
moved science gui button, to prevent cut off threat values for spectators
reverted to old biter health boost method
spawners and worms gain health boost too
reduced trees in spawn area 
increased tree generation randomness overall
bigger biter area, less worms
border to biter lands tweaked
biter area starts at ~y512
side area worm turrets start to appear at the same distance as the biter area starts
chunks that are discovered north are mirrored to south as well
biters will try to attack from different angles
removed some console spam
2020-04-13 06:31:39 +02:00

471 lines
16 KiB
Lua

local Public = {}
local bb_config = require "maps.biter_battles_v2.config"
local math_random = math.random
local vector_radius = 256
local attack_vectors = {}
attack_vectors.north = {}
attack_vectors.south = {}
for x = vector_radius * -1, vector_radius, 1 do
for y = 0, vector_radius, 1 do
local r = math.sqrt(x ^ 2 + y ^ 2)
if r < vector_radius and r > vector_radius - 1 then
attack_vectors.north[#attack_vectors.north + 1] = {x, y * -1}
attack_vectors.south[#attack_vectors.south + 1] = {x, y}
end
end
end
local size_of_vectors = #attack_vectors.north
local threat_values = {
["small-spitter"] = 1.5,
["small-biter"] = 1.5,
["medium-spitter"] = 4,
["medium-biter"] = 4,
["big-spitter"] = 12,
["big-biter"] = 12,
["behemoth-spitter"] = 32,
["behemoth-biter"] = 32,
["small-worm-turret"] = 8,
["medium-worm-turret"] = 12,
["big-worm-turret"] = 16,
["behemoth-worm-turret"] = 16,
["biter-spawner"] = 16,
["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
count = count + 1
end
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
end
end
end
local function get_threat_ratio(biter_force_name)
if global.bb_threat[biter_force_name] <= 0 then return 0 end
local t1 = global.bb_threat["north_biters"]
local t2 = global.bb_threat["south_biters"]
if t1 == 0 and t2 == 0 then return 0.5 end
if t1 < 0 then t1 = 0 end
if t2 < 0 then t2 = 0 end
local total_threat = t1 + t2
local ratio = global.bb_threat[biter_force_name] / total_threat
return ratio
end
local function is_biter_inactive(biter, unit_number, biter_force_name)
if not biter.entity then
if global.bb_debug then print("BiterBattles: active unit " .. unit_number .. " removed, possibly died.") end
return true
end
if not biter.entity.valid then
if global.bb_debug then print("BiterBattles: active unit " .. unit_number .. " removed, biter invalid.") end
return true
end
if not biter.entity.unit_group then
if global.bb_debug then print("BiterBattles: active unit " .. unit_number .. " at x" .. biter.entity.position.x .. " y" .. biter.entity.position.y .. " removed, had no unit group.") end
return true
end
if not biter.entity.unit_group.valid then
if global.bb_debug then print("BiterBattles: active unit " .. unit_number .. " removed, unit group invalid.") end
return true
end
if game.tick - biter.active_since > bb_config.biter_timeout then
if global.bb_debug then print("BiterBattles: " .. biter_force_name .. " unit " .. unit_number .. " timed out at tick age " .. game.tick - biter.active_since .. ".") end
biter.entity.destroy()
return true
end
end
local function set_active_biters(group)
if not group.valid then return end
local active_biters = global.active_biters[group.force.name]
for _, unit in pairs(group.members) do
if not active_biters[unit.unit_number] then
active_biters[unit.unit_number] = {entity = unit, active_since = game.tick}
end
end
end
Public.destroy_inactive_biters = function()
local biter_force_name = global.next_attack .. "_biters"
for _, group in pairs(global.unit_groups) do
set_active_biters(group)
end
for unit_number, biter in pairs(global.active_biters[biter_force_name]) do
if is_biter_inactive(biter, unit_number, biter_force_name) then
global.active_biters[biter_force_name][unit_number] = nil
end
end
end
Public.send_near_biters_to_silo = function()
if game.tick < 108000 then return end
if not global.rocket_silo["north"] then return end
if not global.rocket_silo["south"] then return end
game.surfaces["biter_battles"].set_multi_command({
command={
type=defines.command.attack,
target=global.rocket_silo["north"],
distraction=defines.distraction.none
},
unit_count = 16,
force = "north_biters",
unit_search_distance=128
})
game.surfaces["biter_battles"].set_multi_command({
command={
type=defines.command.attack,
target=global.rocket_silo["south"],
distraction=defines.distraction.none
},
unit_count = 16,
force = "south_biters",
unit_search_distance=128
})
end
local function get_random_spawner(biter_force_name)
local spawners = global.unit_spawners[biter_force_name]
local size_of_spawners = #spawners
for _ = 1, 256, 1 do
if size_of_spawners == 0 then return end
local index = math_random(1, size_of_spawners)
local spawner = spawners[index]
if spawner and spawner.valid then
return spawner
else
table.remove(spawners, index)
size_of_spawners = size_of_spawners - 1
end
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
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 threat = global.bb_threat[biter_force_name] * math_random(11,22) * 0.01
local unit_count = 0
local max_unit_count = math.ceil(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}
end
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 commands = {}
local vector = attack_vectors[force_name][math_random(1, size_of_vectors)]
local position = {target.x + vector[1], target.y + vector[2]}
position = unit_group.surface.find_non_colliding_position("stone-furnace", position, 96, 1)
if position then
if math.abs(position.y) < math.abs(unit_group.position.y) then
commands[#commands + 1] = {
type = defines.command.attack_area,
destination = position,
radius = 24,
distraction = defines.distraction.by_enemy
}
end
end
commands[#commands + 1] = {
type = defines.command.attack_area,
destination = target,
radius = 32,
distraction = defines.distraction.by_enemy
}
commands[#commands + 1] = {
type = defines.command.attack,
target = global.rocket_silo[force_name],
distraction = defines.distraction.by_enemy
}
unit_group.set_command({
type = defines.command.compound,
structure_type = defines.compound_command.return_last,
commands = commands
})
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
end
local function get_active_threat(biter_force_name)
local active_threat = 0
for unit_number, biter in pairs(global.active_biters[biter_force_name]) do
if biter.entity then
if biter.entity.valid then
active_threat = active_threat + threat_values[biter.entity.name]
end
end
end
return active_threat
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
if threat <= 0 then return false end
if bb_config.max_active_biters - get_active_biter_count(biter_force_name) < bb_config.max_group_size then
if global.bb_debug then game.print("Not enough slots for biters for team " .. force_name .. ". Available slots: " .. bb_config.max_active_biters - get_active_biter_count(biter_force_name)) end
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
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 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)
global.unit_groups[unit_group.group_number] = unit_group
end
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"
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
global.next_attack = "north"
end
end
Public.wake_up_sleepy_groups = function()
local force_name = global.next_attack
local biter_force_name = force_name .. "_biters"
local entity
local unit_group
for unit_number, biter in pairs(global.active_biters[biter_force_name]) do
entity = biter.entity
if entity then
if entity.valid then
unit_group = entity.unit_group
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)
print("BiterBattles: Woke up Unit Group at x" .. unit_group.position.x .. " y" .. unit_group.position.y .. ".")
return
end
end
end
end
end
end
end
Public.raise_evo = function()
if global.freeze_players then return end
if not global.training_mode and (#game.forces.north.connected_players == 0 or #game.forces.south.connected_players == 0) then return end
if not global.total_passive_feed_redpotion then global.total_passive_feed_redpotion = 0 end
local amount = math.ceil(global.difficulty_vote_value * global.evo_raise_counter)
global.total_passive_feed_redpotion = global.total_passive_feed_redpotion + amount
local biter_teams = {["north_biters"] = "north", ["south_biters"] = "south"}
local a_team_has_players = false
for bf, pf in pairs(biter_teams) do
if #game.forces[pf].connected_players > 0 then
set_evo_and_threat(amount, "automation-science-pack", bf)
a_team_has_players = true
end
end
if not a_team_has_players then return end
global.evo_raise_counter = global.evo_raise_counter + (1 * 0.50)
end
--Biter Threat Value Substraction
function Public.subtract_threat(entity)
if not threat_values[entity.name] then return end
if entity.type == "unit" then
global.active_biters[entity.force.name][entity.unit_number] = nil
end
global.bb_threat[entity.force.name] = global.bb_threat[entity.force.name] - threat_values[entity.name]
return true
end
return Public