mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2025-01-06 00:23:49 +02:00
455 lines
15 KiB
Lua
455 lines
15 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 Raffle = require "maps.chronosphere.raffles"
|
|
local Public = {}
|
|
|
|
local random = math.random
|
|
local floor = math.floor
|
|
|
|
local normal_area = {left_top = {-480, -480}, right_bottom = {480, 480}}
|
|
local fish_area = {left_top = {-1100, -400}, right_bottom = {1100, 400}}
|
|
|
|
|
|
-----------commands-----------
|
|
|
|
local function move_to(position)
|
|
local command = {
|
|
type = defines.command.go_to_location,
|
|
destination = position,
|
|
distraction = defines.distraction.by_anything
|
|
}
|
|
return command
|
|
end
|
|
|
|
local function attack_target(target)
|
|
if not target.valid then return end
|
|
local command = {
|
|
type = defines.command.attack,
|
|
target = target,
|
|
distraction = defines.distraction.by_anything,
|
|
}
|
|
return command
|
|
end
|
|
|
|
local function attack_area(position, radius)
|
|
local command = {
|
|
type = defines.command.attack_area,
|
|
destination = position,
|
|
radius = radius or 25,
|
|
distraction = defines.distraction.by_anything
|
|
}
|
|
return command
|
|
end
|
|
|
|
local function attack_obstacles(surface, position)
|
|
local commands = {}
|
|
local obstacles = surface.find_entities_filtered{position = position, radius = 25, type = {"simple-entity", "tree", "simple-entity-with-owner"}, limit = 100}
|
|
if obstacles then
|
|
Rand.shuffle(obstacles)
|
|
Rand.shuffle_distance(obstacles, position)
|
|
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_anything
|
|
}
|
|
end
|
|
end
|
|
end
|
|
commands[#commands + 1] = move_to(position)
|
|
local command = {
|
|
type = defines.command.compound,
|
|
structure_type = defines.compound_command.return_last,
|
|
commands = commands
|
|
}
|
|
return command
|
|
end
|
|
|
|
local function multicommand(group, commands)
|
|
if #commands > 0 then
|
|
local command = {
|
|
type = defines.command.compound,
|
|
structure_type = defines.compound_command.return_last,
|
|
commands = commands
|
|
}
|
|
group.set_command(command)
|
|
end
|
|
end
|
|
|
|
local function multi_attack(surface, target)
|
|
surface.set_multi_command({
|
|
command = attack_target(target),
|
|
unit_count = 16 + random(1, floor(1 + game.forces["enemy"].evolution_factor * 100)) * Difficulty.get().difficulty_vote_value,
|
|
force = "enemy",
|
|
unit_search_distance = 1024
|
|
})
|
|
end
|
|
|
|
------------------------misc functions----------------------
|
|
|
|
local function generate_side_attack_target(surface, position)
|
|
local targets = {
|
|
"character",
|
|
"pumpjack",
|
|
"radar",
|
|
"burner-mining-drill",
|
|
"electric-mining-drill",
|
|
"nuclear-reactor",
|
|
"boiler",
|
|
"assembling-machine-1",
|
|
"assembling-machine-2",
|
|
"assembling-machine-3",
|
|
"oil-refinery",
|
|
"centrifuge",
|
|
"burner-inserter"
|
|
}
|
|
local entities = surface.find_entities_filtered{name = targets}
|
|
if #entities < 1 then return false end
|
|
entities = Rand.shuffle(entities)
|
|
entities = Rand.shuffle_distance(entities, position)
|
|
local weights = {}
|
|
for index, _ in pairs(entities) do
|
|
weights[#weights + 1] = 1 + floor((#entities - index) / 2)
|
|
end
|
|
return Rand.raffle(entities, weights)
|
|
end
|
|
|
|
local function generate_main_attack_target()
|
|
local objective = Chrono_table.get_table()
|
|
local targets = {objective.locomotive, objective.locomotive, objective.locomotive_cargo[1], objective.locomotive_cargo[2], objective.locomotive_cargo[3]}
|
|
return targets[random(1, #targets)]
|
|
end
|
|
|
|
local function generate_expansion_position(start_pos)
|
|
local objective = Chrono_table.get_table()
|
|
local target_pos = objective.locomotive.position
|
|
return {x = (start_pos.x * 0.90 + target_pos.x * 0.10) , y = (start_pos.y * 0.90 + target_pos.y * 0.10)}
|
|
end
|
|
|
|
local function get_random_close_spawner(surface)
|
|
local objective = Chrono_table.get_table()
|
|
local area = normal_area
|
|
if objective.world.id == 7 then area = fish_area end
|
|
|
|
local spawners = surface.find_entities_filtered({type = "unit-spawner", force = "enemy", area = area})
|
|
if not spawners[1] then return false end
|
|
spawners = Rand.shuffle(spawners)
|
|
spawners = Rand.shuffle_distance(spawners, objective.locomotive.position)
|
|
local weights = {}
|
|
for index, _ in pairs(spawners) do
|
|
weights[#weights + 1] = 1 + floor((#spawners - index) / 2)
|
|
end
|
|
return Rand.raffle(spawners, weights)
|
|
end
|
|
|
|
local function is_biter_inactive(biter)
|
|
if not biter.entity then
|
|
return true
|
|
end
|
|
if not biter.entity.valid then
|
|
return true
|
|
end
|
|
if not biter.entity.unit_group then
|
|
return true
|
|
end
|
|
if not biter.entity.unit_group.valid then
|
|
return true
|
|
end
|
|
if game.tick - biter.active_since > 162000 then
|
|
biter.entity.destroy()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function get_active_biter_count()
|
|
local bitertable = Chrono_table.get_biter_table()
|
|
local count = 0
|
|
for k, biter in pairs(bitertable.active_biters) do
|
|
if biter.entity.valid then
|
|
count = count + 1
|
|
else
|
|
bitertable[k] = nil
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
local function select_units_around_spawner(spawner)
|
|
local bitertable = Chrono_table.get_biter_table()
|
|
local difficulty = Difficulty.get().difficulty_vote_value
|
|
|
|
local biters = spawner.surface.find_enemy_units(spawner.position, 50, "player")
|
|
if not biters[1] then return nil 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 bitertable.active_biters[biter.unit_number] == nil then
|
|
valid_biters[#valid_biters + 1] = biter
|
|
bitertable.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 = #bitertable.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 = bitertable.biter_raffle[random(1, size_of_biter_raffle)]
|
|
local position = spawner.surface.find_non_colliding_position(biter_name, spawner.position, 50, 2)
|
|
if not position then break end
|
|
|
|
local biter = spawner.surface.create_entity({name = biter_name, force = "enemy", position = position})
|
|
if bitertable.free_biters > 0 then
|
|
bitertable.free_biters = bitertable.free_biters - 1
|
|
else
|
|
local local_pollution = math.min(spawner.surface.get_pollution(spawner.position), 400 * game.map_settings.pollution.enemy_attack_pollution_consumption_modifier * game.forces.enemy.evolution_factor)
|
|
spawner.surface.pollute(spawner.position, -local_pollution)
|
|
game.pollution_statistics.on_flow("biter-spawner", -local_pollution)
|
|
if local_pollution < 1 then break end
|
|
end
|
|
valid_biters[#valid_biters + 1] = biter
|
|
bitertable.active_biters[biter.unit_number] = {entity = biter, active_since = game.tick}
|
|
end
|
|
end
|
|
return valid_biters
|
|
end
|
|
|
|
local function pollution_requirement(surface, position, main)
|
|
local objective = Chrono_table.get_table()
|
|
if not position then
|
|
position = objective.locomotive.position
|
|
main = true
|
|
end
|
|
if objective.world.id == 7 then return true end
|
|
local pollution = surface.get_pollution(position)
|
|
local pollution_to_eat = Balance.pollution_spent_per_attack(Difficulty.get().difficulty_vote_value)
|
|
local multiplier = 0.5
|
|
if main then multiplier = 4 end
|
|
if pollution > multiplier * pollution_to_eat then
|
|
surface.pollute(position, -pollution_to_eat)
|
|
game.pollution_statistics.on_flow("small-biter", -pollution_to_eat)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function set_biter_raffle_table(surface)
|
|
local objective = Chrono_table.get_table()
|
|
local bitertable = Chrono_table.get_biter_table()
|
|
local area = normal_area
|
|
if objective.world.id == 7 then area = fish_area end
|
|
local biters = surface.find_entities_filtered({type = "unit", force = "enemy", area = area, limit = 100})
|
|
if not biters[1] then return end
|
|
local i = 1
|
|
for key, e in pairs(biters) do
|
|
if key % 5 == 0 then
|
|
bitertable.biter_raffle[i] = e.name
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local function create_attack_group(surface)
|
|
local objective = Chrono_table.get_table()
|
|
local bitertable = Chrono_table.get_biter_table()
|
|
if get_active_biter_count() > 512 * Difficulty.get().difficulty_vote_value then
|
|
return nil
|
|
end
|
|
local spawner = get_random_close_spawner(surface)
|
|
if not spawner then
|
|
return nil
|
|
end
|
|
local position = surface.find_non_colliding_position("rocket-silo", spawner.position, 256, 1)
|
|
local units = select_units_around_spawner(spawner)
|
|
if not units then return nil end
|
|
local unit_group = surface.create_unit_group({position = position, force = "enemy"})
|
|
for _, unit in pairs(units) do unit_group.add_member(unit) end
|
|
bitertable.unit_groups[unit_group.group_number] = unit_group
|
|
return unit_group
|
|
end
|
|
|
|
--------------------------command functions-------------------------------
|
|
|
|
local function colonize(group)
|
|
--if _DEBUG then game.print(game.tick ..": colonizing") end
|
|
local surface = group.surface
|
|
local evo = floor(game.forces['enemy'].evolution_factor * 20)
|
|
local nests = random(1 + evo, 2 + evo * 2)
|
|
local commands = {}
|
|
local biters = surface.find_entities_filtered {position = group.position, radius = 30, name = Raffle.biters, force = 'enemy'}
|
|
local goodbiters = {}
|
|
if #biters > 1 then
|
|
for i = 1, #biters, 1 do
|
|
if biters[i].unit_group == group then
|
|
goodbiters[#goodbiters + 1] = biters[i]
|
|
end
|
|
end
|
|
end
|
|
local eligible_spawns
|
|
if #goodbiters < 10 then
|
|
if #group.members < 10 then
|
|
group.destroy()
|
|
end
|
|
return
|
|
else
|
|
eligible_spawns = 1 + 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('biter-spawner', group.position, 20, 1, true)
|
|
if pos then
|
|
--game.print("[gps=" .. pos.x .. "," .. pos.y .."," .. surface.name .. "]")
|
|
success = true
|
|
if random(1, 5) == 1 then
|
|
local evo = group.force.evolution_factor
|
|
surface.create_entity({name = Raffle.worms[random(1 + floor(evo * 8), floor(1 + evo * 16))], position = pos, force = group.force})
|
|
else
|
|
surface.create_entity({name = Raffle.spawners[random(1, #Raffle.spawners)], position = pos, force = group.force})
|
|
end
|
|
else
|
|
commands = {
|
|
attack_obstacles(surface, group.position)
|
|
}
|
|
end
|
|
end
|
|
if success then
|
|
for i = 1, #goodbiters, 1 do
|
|
if goodbiters[i].valid then
|
|
goodbiters[i].destroy()
|
|
end
|
|
end
|
|
return
|
|
end
|
|
if #commands > 0 then
|
|
--game.print("Attacking [gps=" .. commands[1].target.position.x .. "," .. commands[1].target.position.y .. "]")
|
|
multicommand(group, commands)
|
|
end
|
|
end
|
|
|
|
local function send_near_biters_to_objective()
|
|
local objective = Chrono_table.get_table()
|
|
if objective.game_lost then game.print("lost game") return end
|
|
local target = generate_main_attack_target()
|
|
if not target or not target.valid then game.print("no target") return end
|
|
if pollution_requirement(target.surface, target.position, true) or random(1, math.max(1, 40 - objective.chronojumps)) == 1 then
|
|
if _DEBUG then game.print(game.tick .. ": sending objective wave") end
|
|
multi_attack(target.surface, target)
|
|
end
|
|
end
|
|
|
|
local function attack_check(group, target, main)
|
|
local commands = {}
|
|
if pollution_requirement(group.surface, target.position, main) then
|
|
commands = {
|
|
attack_target(target),
|
|
attack_area(target.position, 32)
|
|
}
|
|
else
|
|
local position = generate_expansion_position(group.position)
|
|
commands = {
|
|
attack_obstacles(group.surface, position)
|
|
}
|
|
end
|
|
multicommand(group, commands)
|
|
end
|
|
|
|
local function give_new_orders(group)
|
|
local target = generate_side_attack_target(group.surface, group.position)
|
|
if not target or not target.valid or not pollution_requirement(group.surface, target.position, false) then
|
|
colonize(group)
|
|
return
|
|
end
|
|
if not group or not group.valid or not target or not target.valid then return end
|
|
local commands = {
|
|
attack_target(target),
|
|
attack_area(target.position, 32)
|
|
}
|
|
multicommand(group, commands)
|
|
end
|
|
|
|
------------------------- tick minute functions--------------------
|
|
|
|
local function destroy_inactive_biters()
|
|
local bitertable = Chrono_table.get_biter_table()
|
|
for unit_number, biter in pairs(bitertable.active_biters) do
|
|
if is_biter_inactive(biter, unit_number) then
|
|
bitertable.active_biters[unit_number] = nil
|
|
bitertable.free_biters = bitertable.free_biters + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local function prepare_biters()
|
|
local objective = Chrono_table.get_table()
|
|
local surface = game.surfaces[objective.active_surface_index]
|
|
set_biter_raffle_table(surface)
|
|
--if _DEBUG then game.print(game.tick .. ": biters prepared") end
|
|
end
|
|
|
|
function Public.perform_rogue_attack()
|
|
local objective = Chrono_table.get_table()
|
|
local surface = game.surfaces[objective.active_surface_index]
|
|
local group = create_attack_group(surface)
|
|
if not group or not group.valid then return end
|
|
local target = generate_side_attack_target(surface, group.position)
|
|
if not target or not target.valid then return end
|
|
attack_check(group, target, false)
|
|
--if _DEBUG then game.print(game.tick ..": sending rogue attack") end
|
|
end
|
|
|
|
function Public.perform_main_attack()
|
|
local objective = Chrono_table.get_table()
|
|
local surface = game.surfaces[objective.active_surface_index]
|
|
local group = create_attack_group(surface)
|
|
local target = generate_main_attack_target()
|
|
if not group or not group.valid or not target or not target.valid then return end
|
|
attack_check(group, target, true)
|
|
--if _DEBUG then game.print(game.tick ..": sending main attack") end
|
|
end
|
|
|
|
local function check_groups()
|
|
local objective = Chrono_table.get_table()
|
|
local bitertable = Chrono_table.get_biter_table()
|
|
for index, group in pairs(bitertable.unit_groups) do
|
|
if not group.valid or group.surface.index ~= objective.active_surface_index or #group.members < 1 then
|
|
bitertable.unit_groups[index] = nil
|
|
else
|
|
if group.state == defines.group_state.finished then
|
|
give_new_orders(group)
|
|
elseif group.state == defines.group_state.gathering then
|
|
group.start_moving()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Public.Tick_actions(tick)
|
|
local objective = Chrono_table.get_table()
|
|
if objective.chronojumps == 0 then return end
|
|
if objective.passivetimer < 60 then return end
|
|
local tick_minute_functions = {
|
|
|
|
[100] = destroy_inactive_biters,
|
|
[200] = prepare_biters, -- setup for main_attack
|
|
[400] = Public.perform_rogue_attack,
|
|
[700] = Public.perform_main_attack,
|
|
[1000] = Public.perform_main_attack,
|
|
[1300] = Public.perform_main_attack,
|
|
[1500] = Public.perform_main_attack, -- call perform_main_attack 7 times on different ticks
|
|
[3200] = send_near_biters_to_objective,
|
|
[3500] = check_groups,
|
|
}
|
|
local key = tick % 3600
|
|
if tick_minute_functions[key] then tick_minute_functions[key]() end
|
|
end
|
|
|
|
return Public
|