1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-06 00:23:49 +02:00
ComfyFactorio/maps/chronosphere/ai.lua
2020-06-18 15:35:49 +02:00

567 lines
20 KiB
Lua

local Chrono_table = require 'maps.chronosphere.table'
local Balance = require 'maps.chronosphere.balance'
local Difficulty = require 'modules.difficulty_vote'
local Rand = require 'maps.chronosphere.random'
local Public = {}
local math_random = math.random
local math_sqrt = math.sqrt
local math_floor = math.floor
local worm_raffle = {'small-worm-turret', 'medium-worm-turret', 'big-worm-turret', 'behemoth-worm-turret'}
local spawner_raffle = {'biter-spawner', 'biter-spawner', 'biter-spawner', 'spitter-spawner'}
local biter_raffle = {
'behemoth-biter',
'behemoth-spitter',
'big-biter',
'big-spitter',
'medium-biter',
'medium-spitter',
'small-biter',
'small-spitter'
}
local vector_radius = 480
local attack_vectors = {}
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[#attack_vectors + 1] = {x, y}
end
end
end
local size_of_vectors = #attack_vectors
local function get_active_biter_count()
local objective = Chrono_table.get_table()
local count = 0
for k, _ in pairs(objective.active_biters) do
count = count + 1
end
return count
end
local function set_biter_raffle_table(surface)
local objective = Chrono_table.get_table()
-- It's fine to only sample the middle
local area = {left_top = {-400, -400}, right_bottom = {400, 400}}
local biters = surface.find_entities_filtered({type = "unit", force = "enemy", area = area})
if not biters[1] then return end
local raffle = objective.biter_raffle
local i = 1
for key, e in pairs(biters) do
if key % 5 == 0 then
raffle[i] = e.name
i = i + 1
end
end
end
local function is_biter_inactive(biter)
if not biter.entity then
--print("AI: active unit " .. unit_number .. " removed, possibly died.")
return true
end
if not biter.entity.valid then
--print("AI: active unit " .. unit_number .. " removed, biter invalid.")
return true
end
if not biter.entity.unit_group then
--print("AI: active unit " .. unit_number .. " at x" .. biter.entity.position.x .. " y" .. biter.entity.position.y .. " removed, had no unit group.")
return true
end
if not biter.entity.unit_group.valid then
--print("AI: active unit " .. unit_number .. " removed, unit group invalid.")
return true
end
if game.tick - biter.active_since > 162000 then
--print("AI: " .. "enemy" .. " unit " .. unit_number .. " timed out at tick age " .. game.tick - biter.active_since .. ".")
biter.entity.destroy()
return true
end
end
local function set_active_biters(group)
local objective = Chrono_table.get_table()
if not group.valid then
return
end
local active_biters = objective.active_biters
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
function Public.destroy_inactive_biters()
local objective = Chrono_table.get_table()
if objective.passivetimer < 60 then
return
end
for _, group in pairs(objective.unit_groups) do
set_active_biters(group)
end
for unit_number, biter in pairs(objective.active_biters) do
if is_biter_inactive(biter, unit_number) then
objective.active_biters[unit_number] = nil
end
end
end
local function colonize(unit_group)
local surface = unit_group.surface
local evo = math_floor(game.forces['enemy'].evolution_factor * 20)
local nests = math_random(1 + evo, 2 + evo * 2)
local commands = {}
local biters =
surface.find_entities_filtered {
position = unit_group.position,
radius = 30,
name = biter_raffle,
force = 'enemy'
}
local goodbiters = {}
if #biters > 1 then
for i = 1, #biters, 1 do
if biters[i].unit_group == unit_group then
goodbiters[#goodbiters + 1] = biters[i]
end
end
end
local eligible_spawns
if #goodbiters < 10 then
--game.print("no biters to colonize")
if #unit_group.members < 10 then
unit_group.destroy()
end
return
else
eligible_spawns = 1 + math_floor(#goodbiters / 10)
end
local success = false
for i = 1, nests, 1 do
if eligible_spawns < i then
break
end
local pos = surface.find_non_colliding_position('rocket-silo', unit_group.position, 20, 1, true)
if not pos then
local items =
surface.find_entities_filtered {
position = unit_group.position,
radius = 32,
type = 'item-entity',
name = 'item-on-ground'
}
if #items > 0 then
for i = 1, #items, 1 do
if items[i].stack.name == 'stone' then
items[i].destroy()
end
end
pos = surface.find_non_colliding_position('rocket-silo', unit_group.position, 20, 1, true)
end
end
if pos then
--game.print("[gps=" .. e.position.x .. "," .. e.position.y .. "]")
success = true
if math_random(1, 5) == 1 then
surface.create_entity(
{
name = worm_raffle[1 + math_floor((game.forces['enemy'].evolution_factor - 0.000001) * 4)],
position = pos,
force = unit_group.force
}
)
else
surface.create_entity(
{name = spawner_raffle[math_random(1, #spawner_raffle)], position = pos, force = unit_group.force}
)
end
else
local obstacles =
surface.find_entities_filtered {
position = unit_group.position,
radius = 10,
type = {'simple-entity', 'tree'},
limit = 50
}
if obstacles then
for i = 1, #obstacles, 1 do
if obstacles[i].valid then
commands[#commands + 1] = {
type = defines.command.attack,
target = obstacles[i],
distraction = defines.distraction.by_enemy
}
end
end
end
end
end
if success then
for i = 1, #goodbiters, 1 do
if goodbiters[i].valid then
goodbiters[i].destroy()
end
end
end
if #commands > 0 then
--game.print("Attacking [gps=" .. commands[1].target.position.x .. "," .. commands[1].target.position.y .. "]")
unit_group.set_command(
{
type = defines.command.compound,
structure_type = defines.compound_command.return_last,
commands = commands
}
)
end
--unit_group.destroy()
end
function Public.send_near_biters_to_objective()
local objective = Chrono_table.get_table()
if objective.chronojumps == 0 then return end
if objective.passivetimer < 60 then return end
if not objective.locomotive then return end
if not objective.locomotive_cargo[1] then return end
if not objective.locomotive_cargo[2] then return end
if not objective.locomotive_cargo[3] then return end
local targets = {objective.locomotive, objective.locomotive, objective.locomotive_cargo[1], objective.locomotive_cargo[2], objective.locomotive_cargo[3]}
local random_target = targets[math_random(1, #targets)]
if objective.game_lost then return end
local surface = random_target.surface
local pollution = surface.get_pollution(random_target.position)
local success = false
local difficulty = Difficulty.get().difficulty_vote_value
if pollution > 4 * Balance.pollution_spent_per_attack(difficulty) or objective.planet[1].type.id == 17 then
local pollution_to_eat = Balance.pollution_spent_per_attack(difficulty)
surface.pollute(random_target.position, -pollution_to_eat)
game.pollution_statistics.on_flow("biter-spawner", -pollution_to_eat)
success = true
else
if objective.chronojumps < 50 then
if math_random(1, 50 - objective.chronojumps) == 1 then success = true end
-- game.print("not enough pollution for objective attack")
else
success = true
end
end
if success then
-- game.print("sending objective wave")
game.surfaces[objective.active_surface_index].set_multi_command({
command={
type=defines.command.attack,
target=random_target,
distraction=defines.distraction.none
},
unit_count = 16 + math_random(1, math_floor(1 + game.forces["enemy"].evolution_factor * 100)) * Difficulty.get().difficulty_vote_value,
force = "enemy",
unit_search_distance=128
})
end
end
local function get_random_close_spawner(surface)
local area = {left_top = {-1100, -500}, right_bottom = {1100, 500}}
local spawners = surface.find_entities_filtered({type = "unit-spawner", force = "enemy", area = area})
if not spawners[1] then return false end
local spawner = spawners[math_random(1,#spawners)]
for i = 1, 5, 1 do
local spawner_2 = spawners[math_random(1,#spawners)]
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
return spawner
end
local function select_units_around_spawner(spawner)
local objective = Chrono_table.get_table()
local difficulty = Difficulty.get().difficulty_vote_value
local biters = spawner.surface.find_enemy_units(spawner.position, 160, "player")
if not biters[1] then return false end
local valid_biters = {}
local unit_count = 0
for _, biter in pairs(biters) do
if unit_count >= Balance.max_new_attack_group_size(difficulty) then break end
if biter.force.name == "enemy" and objective.active_biters[biter.unit_number] == nil then
valid_biters[#valid_biters + 1] = biter
objective.active_biters[biter.unit_number] = {entity = biter, active_since = game.tick}
unit_count = unit_count + 1
end
end
--Manual spawning of additional units
local size_of_biter_raffle = #objective.biter_raffle
if size_of_biter_raffle > 0 then
for _ = 1, Balance.max_new_attack_group_size(difficulty) - unit_count, 1 do
local biter_name = objective.biter_raffle[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 = "enemy", position = position})
valid_biters[#valid_biters + 1] = biter
objective.active_biters[biter.unit_number] = {entity = biter, active_since = game.tick}
end
end
return valid_biters
end
local function generate_attack_target(nearest_player_unit)
local objective = Chrono_table.get_table()
if objective.game_lost then return nil end
local target = Rand.raffle(
{
nearest_player_unit,
objective.locomotive,
objective.locomotive_cargo[1],
objective.locomotive_cargo[2],
objective.locomotive_cargo[3],
"pumpjack",
"radar"
},
{3, 2, 1, 1, 1, 2, 1}
)
if target == "pumpjack" or target == "radar" then
local entities = game.surfaces[objective.active_surface_index].find_entities_filtered({
name = target
})
entities=Rand.shuffle(entities)
local entity = false
for _, e in pairs(entities) do
if e.is_connected_to_electric_network()
then
entity = e
end
end
if entity and entity.valid then
target = entity
else
target = objective.locomotive
end
end
return target
end
local function send_group(unit_group, nearest_player_unit)
local objective = Chrono_table.get_table()
local difficulty = Difficulty.get().difficulty_vote_value
local target = generate_attack_target(nearest_player_unit)
if not target or not target.valid then colonize(unit_group) return end
local surface = target.surface
local pollution = surface.get_pollution(target.position)
if pollution > 4 * Balance.pollution_spent_per_attack(difficulty) or objective.planet[1].type.id == 17 then
local pollution_to_eat = Balance.pollution_spent_per_attack(difficulty)
surface.pollute(target.position, -pollution_to_eat)
game.pollution_statistics.on_flow("biter-spawner", -pollution_to_eat)
if #unit_group.members > 0 then game.pollution_statistics.on_flow(unit_group.members[1].name or "small-biter", - pollution_to_eat) end
local commands = {}
local vector = attack_vectors[math_random(1, size_of_vectors)]
local position = {target.position.x + vector[1], target.position.y + vector[2]}
position = unit_group.surface.find_non_colliding_position("stone-furnace", position, 96, 1)
if position then
commands[#commands + 1] = {
type = defines.command.attack_area,
destination = position,
radius = 24,
distraction = defines.distraction.by_enemy
}
end
commands[#commands + 1] = {
type = defines.command.attack_area,
destination = target.position,
radius = 32,
distraction = defines.distraction.by_enemy
}
commands[#commands + 1] = {
type = defines.command.attack,
target = target,
distraction = defines.distraction.by_enemy
}
unit_group.set_command({
type = defines.command.compound,
structure_type = defines.compound_command.return_last,
commands = commands
})
else
colonize(unit_group)
end
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 = "player", 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.2) , y = (spawner.position.y + nearest_player_unit.position.y * 0.2)}
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
return false
end
return unit_group_position
end
local function create_attack_group(surface)
local objective = Chrono_table.get_table()
if 256 - get_active_biter_count() < 256 then
return false
end
local spawner = get_random_close_spawner(surface)
if not spawner then
return false
end
local nearest_player_unit = surface.find_nearest_enemy({position = spawner.position, max_distance = 1024, force = "enemy"})
if not nearest_player_unit then nearest_player_unit = objective.locomotive end
local unit_group_position = get_unit_group_position(surface, nearest_player_unit, spawner)
local units = select_units_around_spawner(spawner)
if not units then return false end
local unit_group = surface.create_unit_group({position = unit_group_position, force = "enemy"})
for _, unit in pairs(units) do unit_group.add_member(unit) end
send_group(unit_group, nearest_player_unit)
objective.unit_groups[unit_group.group_number] = unit_group
end
-- function Public.rogue_group()
-- local objective = Chrono_table.get_table()
-- if objective.passivetimer < 60 then return end
-- if not objective.locomotive then return end
-- local surface = game.surfaces[objective.active_surface_index]
-- local spawner = get_random_close_spawner(surface)
-- if not spawner then
-- return false
-- end
-- local position = {x = ((spawner.position.x + objective.locomotive.position.x) * 0.5) , y = ((spawner.position.y + objective.locomotive.position.y) * 0.5)}
-- local pos = surface.find_non_colliding_position("rocket-silo", position, 30, 1, true)
-- if not pos then return end
-- surface.set_multi_command({
-- command={
-- type=defines.command.build_base,
-- destination=pos,
-- distraction=defines.distraction.none,
-- ignore_planner = true
-- },
-- unit_count = 16,
-- force = "enemy",
-- unit_search_distance=128
-- })
-- end
function Public.pre_main_attack()
local objective = Chrono_table.get_table()
if objective.chronojumps == 0 then return end
if objective.passivetimer < 60 then return end
local surface = game.surfaces[objective.active_surface_index]
set_biter_raffle_table(surface)
end
function Public.perform_main_attack()
local objective = Chrono_table.get_table()
if objective.chronojumps == 0 then return end
if objective.passivetimer < 60 then return end
local surface = game.surfaces[objective.active_surface_index]
create_attack_group(surface)
end
function Public.wake_up_sleepy_groups()
local objective = Chrono_table.get_table()
if objective.chronojumps == 0 then return end
if objective.passivetimer < 60 then return end
local entity
local unit_group
for _, biter in pairs(objective.active_biters) 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 = "enemy"})
if not nearest_player_unit then nearest_player_unit = objective.locomotive end
local destination = unit_group.surface.find_non_colliding_position("rocket-silo", unit_group.position, 32, 1)
if not destination then destination = {x = unit_group.position.x + math_random(-10,10), y = unit_group.position.y + math_random(-10,10)} end
unit_group.set_command({
type = defines.command.go_to_location,
destination = destination,
distraction = defines.distraction.by_enemy
})
send_group(unit_group, nearest_player_unit)
return
elseif unit_group.state == defines.group_state.gathering and not unit_group.surface.find_non_colliding_position("rocket-silo", unit_group.position, 3, 1) then
local destination = unit_group.surface.find_non_colliding_position("rocket-silo", unit_group.position, 32, 1)
if not destination then destination = {x = unit_group.position.x + math_random(-10,10), y = unit_group.position.y + math_random(-10,10)} end
unit_group.set_command({
type = defines.command.go_to_location,
destination = destination,
distraction = defines.distraction.by_enemy
})
-- local nearest_player_unit = entity.surface.find_nearest_enemy({position = entity.position, max_distance = 2048, force = "enemy"})
-- if not nearest_player_unit then nearest_player_unit = objective.locomotive end
-- send_group(unit_group, nearest_player_unit)
return
end
end
end
end
end
end
end
return Public