1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-10 00:43:27 +02:00
ComfyFactorio/maps/pirates/ai.lua
Piratux b5e61d577b Elite biter changes
Changes:
- Elite biters now appear from league 800 in Easy and Normal difficulties (In Hard and Nightmare difficulties, elite biters appear from league 40). Previously, in Easy and Normal difficulties elite biters never appeared at all.
- Elite biters now also explode and spawn 4 non-elite biters.
2023-06-30 00:47:50 +03:00

964 lines
33 KiB
Lua

-- This file is part of thesixthroc's Pirate Ship softmod, licensed under GPLv3 and stored at https://github.com/danielmartin0/ComfyFactorio-Pirates.
local Memory = require 'maps.pirates.memory'
local Balance = require 'maps.pirates.balance'
local Common = require 'maps.pirates.common'
local CoreData = require 'maps.pirates.coredata'
-- local Utils = require 'maps.pirates.utils_local'
local Math = require 'maps.pirates.math'
local Raffle = require 'maps.pirates.raffle'
local _inspect = require 'utils.inspect'.inspect
-- local Structures = require 'maps.pirates.structures.structures'
local Boats = require 'maps.pirates.structures.boats.boats'
local Surfaces = require 'maps.pirates.surfaces.surfaces'
-- local Islands = require 'maps.pirates.surfaces.islands.islands'
local IslandEnum = require 'maps.pirates.surfaces.islands.island_enum'
-- local Sea = require 'maps.pirates.surfaces.sea.sea'
-- local Crew = require 'maps.pirates.crew'
-- local Quest = require 'maps.pirates.quest'
local SurfacesCommon = require 'maps.pirates.surfaces.common'
local Utils = require 'maps.pirates.utils_local'
local Public = {}
local function fake_boat_target()
local memory = Memory.get_crew_memory()
if memory.boat and memory.boat.position then
return {valid = true, position = {x = memory.boat.position.x - 60, y = memory.boat.position.y} or nil, name = 'boatarea'}
end
end
-- fff 283 discussed pollution mechanics: https://factorio.com/blog/post/fff-283
local side_attack_target_names = {
'character',
'pumpjack',
'radar',
'electric-mining-drill',
'assembling-machine-1',
'solar-panel',
'nuclear-reactor',
'oil-refinery',
'centrifuge',
}
--=== Tick Actions
function Public.Tick_actions(tickinterval)
local memory = Memory.get_crew_memory()
local destination = Common.current_destination()
if destination.type ~= Surfaces.enum.ISLAND then return end
if memory.boat.state ~= Boats.enum_state.LANDED and memory.boat.state ~= Boats.enum_state.RETREATING then return end
if (memory.game_lost) or (destination.dynamic_data.timeratlandingtime and destination.dynamic_data.timer < destination.dynamic_data.timeratlandingtime + Common.seconds_after_landing_to_enable_AI) then return end
if game.tick % (tickinterval * 2) == 0 and memory.boat.state == Boats.enum_state.LANDED then
local extra_evo = 2 * tickinterval/60 * Balance.evolution_per_second()
Common.increment_evo(extra_evo)
destination.dynamic_data.evolution_accrued_time = destination.dynamic_data.evolution_accrued_time + extra_evo
end
-- if destination.subtype == IslandEnum.enum.RED_DESERT then return end -- This was a hack to stop biter boats causing attacks, but, it has the even worse effect of stopping all floating_pollution gathering.
local minute_cycle = {-- warning: use even seconds only
[2] = Public.tell_biters_near_silo_to_attack_it,
[4] = Public.poke_script_groups,
[6] = Public.try_main_attack,
[16] = Public.tell_biters_near_silo_to_attack_it,
[18] = Public.poke_script_groups,
[20] = Public.try_secondary_attack,
[32] = Public.tell_biters_near_silo_to_attack_it,
[34] = Public.poke_script_groups,
[36] = Public.try_rogue_attack,
[46] = Public.poke_inactive_scripted_biters,
[48] = Public.create_mail_delivery_biters,
[54] = Public.try_boat_biters_attack,
}
if minute_cycle[(game.tick / 60) % 60] then
minute_cycle[(game.tick / 60) % 60]()
end
end
function Public.eat_up_fraction_of_all_pollution_wrapped()
local surface = game.surfaces[Common.current_destination().surface_name]
Public.eat_up_fraction_of_all_pollution(surface, 0.05)
end
function Public.eat_up_fraction_of_all_pollution(surface, fraction_of_global_pollution)
local memory = Memory.get_crew_memory()
-- local enemy_force_name = memory.enemy_force_name
local pollution_available = memory.floating_pollution
local chunk_positions = {}
for i = 1, Math.ceil(surface.map_gen_settings.width/32),1 do
for j = 1, Math.ceil(surface.map_gen_settings.height/32),1 do
chunk_positions[#chunk_positions + 1] = {x = 16 + i * 32 - surface.map_gen_settings.width/2, y = 16 + j * 32 - surface.map_gen_settings.height/2}
end
end
for i = 1, #chunk_positions do
local p = chunk_positions[i]
local pollution = surface.get_pollution(p)
local pollution_to_eat = pollution * fraction_of_global_pollution
surface.pollute(p, - pollution_to_eat)
-- Radioactive world doesn't absorb map pollution:
if not (Common.current_destination().subtype == IslandEnum.enum.RADIOACTIVE) then
pollution_available = pollution_available + pollution_to_eat
end
end
-- if _DEBUG then
-- game.print(string.format('ate %f pollution', pollution_available))
-- end
memory.floating_pollution = pollution_available
end
function Public.wave_size_rng() -- random variance in attack sizes
local memory = Memory.get_crew_memory()
local destination = Common.current_destination()
local rng_scale = Balance.crew_scale()^(1/4) -- slightly dampen wave variance for small crews as they can't handle it
-- prevent situation where when player reveals spawner, he immediately gets surrounded by massive amount of biters (especially late game)
if destination and destination.type == SurfacesCommon.enum.ISLAND then
if destination.dynamic_data and destination.dynamic_data.disabled_wave_timer and destination.dynamic_data.disabled_wave_timer > 0 then
return 0
end
end
local wave_percentage_chance = Math.clamp(0, 50, 11 + 8 * memory.floating_pollution/1500)
local wave_size_multiplier = 1
local rng1 = Math.random(100)
if rng1 > wave_percentage_chance then
wave_size_multiplier = 0
elseif memory.overworldx > 0 then
local rng2 = Math.random(1000)
if rng2 >= 110 * rng_scale then
wave_size_multiplier = 1
elseif rng2 >= 30 * rng_scale then
wave_size_multiplier = 1.5
elseif rng2 >= 15 * rng_scale then
wave_size_multiplier = 2
elseif rng2 >= 5 * rng_scale then
wave_size_multiplier = 3
else
wave_size_multiplier = 4
end
end
return wave_size_multiplier
end
function Public.try_main_attack()
Public.eat_up_fraction_of_all_pollution_wrapped()
local wave_size_multiplier = Public.wave_size_rng()
if wave_size_multiplier == 0 then
log('Attacks: ' .. 'Aborted by chance.')
return nil
else
local group = Public.spawn_group_of_scripted_biters(2/3, 6, 350, wave_size_multiplier)
local target = Public.generate_main_attack_target()
Public.group_set_commands(group, Public.attack_target(target))
-- if _DEBUG then game.print(game.tick .. string.format(": sending main attack of %s units from {%f,%f} to %s", #group.members, group.position.x, group.position.y, target.name)) end
end
end
function Public.try_secondary_attack()
Public.eat_up_fraction_of_all_pollution_wrapped()
local wave_size_multiplier = Public.wave_size_rng()
if wave_size_multiplier == 0 then
log('Attacks: ' .. 'Aborted by chance.')
return nil
else
local surface = game.surfaces[Common.current_destination().surface_name]
local group = Public.spawn_group_of_scripted_biters(2/3, 12, 275, wave_size_multiplier)
if not (group and group.valid) then return end
local target
if Math.random(2) == 1 then
target = Public.generate_main_attack_target()
else
target = Public.generate_side_attack_target(surface, group.position)
end
Public.group_set_commands(group, Public.attack_target(target))
-- if _DEBUG then game.print(game.tick .. string.format(": sending main attack of %s units from {%f,%f} to %s", #group.members, group.position.x, group.position.y, target.name)) end
end
end
function Public.try_rogue_attack()
Public.eat_up_fraction_of_all_pollution_wrapped()
local wave_size_multiplier = Public.wave_size_rng()
if wave_size_multiplier == 0 then
log('Attacks: ' .. 'Aborted by chance.')
return nil
else
local surface = game.surfaces[Common.current_destination().surface_name]
local group = Public.spawn_group_of_scripted_biters(1/2, 6, 200, wave_size_multiplier)
if not (group and group.valid) then return end
local target = Public.generate_side_attack_target(surface, group.position)
Public.group_set_commands(group, Public.attack_target(target))
-- if _DEBUG then game.print(game.tick .. string.format(": sending rogue attack of %s units from {%f,%f} to %s", #group.members, group.position.x, group.position.y, target.name)) end
end
end
function Public.tell_biters_near_silo_to_attack_it()
-- careful with this function, you don't want to pull biters onto the silo before any aggro has happened
local memory = Memory.get_crew_memory()
local destination = Common.current_destination()
local surface = game.surfaces[destination.surface_name]
local enemy_force_name = memory.enemy_force_name
-- don't do this too early
if destination.dynamic_data.timer < destination.dynamic_data.timeratlandingtime + Common.seconds_after_landing_to_enable_AI * 4 then return end
if not (destination.dynamic_data.rocketsilos and destination.dynamic_data.rocketsilos[1] and destination.dynamic_data.rocketsilos[1].valid and destination.dynamic_data.rocketsilos[1].destructible) then return end
local attackcommand = Public.attack_target_entity(destination.dynamic_data.rocketsilos[1])
if attackcommand then
surface.set_multi_command(
{
command = attackcommand,
unit_count = Math.random(1, Math.floor(1 + memory.evolution_factor * 100)),
force = enemy_force_name,
unit_search_distance = 10
}
)
end
end
function Public.poke_script_groups()
local memory = Memory.get_crew_memory()
for index, group in pairs(memory.scripted_unit_groups) do
local groupref = group.ref
if not groupref.valid or groupref.surface.index ~= game.surfaces[Common.current_destination().surface_name].index or #groupref.members < 1 then
memory.scripted_unit_groups[index] = nil
else
if groupref.state == defines.group_state.finished then
if Math.random(20) == 20 then
local command = Public.attack_obstacles(groupref.surface, {x = groupref.position.x, y = groupref.position.y})
groupref.set_command(command)
else
groupref.set_autonomous() --means go home, really
end
elseif group.state == defines.group_state.gathering then
groupref.start_moving()
-- elseif group.state == defines.group_state.wander_in_group then
-- groupref.set_autonomous() --means go home, really
end
end
end
end
function Public.poke_inactive_scripted_biters()
local memory = Memory.get_crew_memory()
for unit_number, biter in pairs(memory.scripted_biters) do
if Public.is_biter_inactive(biter) then
memory.scripted_biters[unit_number] = nil
if biter.entity and biter.entity.valid then
local target = Public.nearest_target()
Public.group_set_commands(biter.entity, Public.attack_target(target))
end
end
end
end
function Public.create_mail_delivery_biters() --these travel cross-map between bases to add a little life and spookyness
local memory = Memory.get_crew_memory()
local surface = game.surfaces[Common.current_destination().surface_name]
local enemy_force_name = memory.enemy_force_name
local spawners = Common.get_valid_spawners(surface)
local try_how_many_groups = Math.clamp(0, 4, (#spawners - 8) / 100)
for i = 1, try_how_many_groups do
if Math.random(2) == 1 then
local s1 = spawners[Math.random(#spawners)]
local far_spawners = {}
for j = 1, #spawners do
local s2 = spawners[i]
if not (i == j or Math.distance(s1.position, s2.position) < 250) then
far_spawners[#far_spawners + 1] = s2
end
end
if #far_spawners > 0 then
local s2 = far_spawners[Math.random(#far_spawners)]
memory.floating_pollution = memory.floating_pollution + 64
local units = Public.try_spawner_spend_fraction_of_available_pollution_on_biters(s1.position, 1/4, 4, 32, 1, 'small-biter')
memory.floating_pollution = memory.floating_pollution - 64
if (not units) or (not #units) or (#units == 0) then return end
local start_p = surface.find_non_colliding_position('rocket-silo', s1.position, 256, 2) or s1.position
local unit_group = surface.create_unit_group({position = start_p, force = enemy_force_name})
if not (unit_group and unit_group.valid) then return end
for _, unit in pairs(units) do
unit_group.add_member(unit)
end
memory.scripted_unit_groups[unit_group.group_number] = {ref = unit_group, script_type = 'mail-delivery'}
Public.group_set_commands(unit_group, {
Public.move_to(s2.position),
Public.wander_around(),
})
-- game.print(string.format('%f biters delivering mail from %f, %f to %f, %f', #units, s1.position.x, s1.position.y, s2.position.x, s2.position.y))
end
end
end
end
--=== Spawn scripted biters
function Public.spawn_group_of_scripted_biters(fraction_of_floating_pollution, minimum_avg_units, maximum_units, wave_size_multiplier)
local memory = Memory.get_crew_memory()
local surface = game.surfaces[Common.current_destination().surface_name]
local enemy_force_name = memory.enemy_force_name
local spawner = Common.get_random_valid_spawner(surface)
if not spawner then log('no spawner found') return end
local nearby_units_to_bring
if Public.get_scripted_biter_count() >= 9/10 * CoreData.total_max_biters then
-- pick up nearby units that might be idle:
nearby_units_to_bring = surface.find_units{area = {{spawner.position.x - 8, spawner.position.y - 8}, {spawner.position.x + 8, spawner.position.y + 8}}, force = enemy_force_name, condition = 'same'}
else
nearby_units_to_bring = {}
end
local new_units = Public.try_spawner_spend_fraction_of_available_pollution_on_biters(spawner.position, fraction_of_floating_pollution, minimum_avg_units, maximum_units, wave_size_multiplier)
if (new_units and nearby_units_to_bring and (#new_units + #nearby_units_to_bring) == 0) then return end
local position = surface.find_non_colliding_position('rocket-silo', spawner.position, 256, 2) or spawner.position
local unit_group = surface.create_unit_group({position = position, force = enemy_force_name})
for _, unit in pairs(nearby_units_to_bring) do
unit_group.add_member(unit)
end
for _, unit in pairs(new_units) do
unit_group.add_member(unit)
end
memory.scripted_unit_groups[unit_group.group_number] = {ref = unit_group, script_type = 'attacker'}
return unit_group
end
function Public.try_spawner_spend_fraction_of_available_pollution_on_biters(spawnposition, fraction_of_floating_pollution, minimum_avg_units, maximum_units, wave_size_multiplier, enforce_type)
maximum_units = maximum_units or 400
-- log('ai spawning attempt params: ' .. (fraction_of_floating_pollution or '') .. ' ' .. (minimum_avg_units or '') .. ' ' .. (maximum_units or '') .. ' ' .. (unit_pollutioncost_multiplier or '') .. ' ' .. (enforce_type or ''))
local memory = Memory.get_crew_memory()
local surface = game.surfaces[Common.current_destination().surface_name]
-- local surface = spawner.surface
-- local spawnposition = spawner.position
-- local difficulty = memory.difficulty
local enemy_force_name = memory.enemy_force_name
local evolution = memory.evolution_factor
local units_created_count = 0
local units_created = {}
local temp_floating_pollution = memory.floating_pollution
local budget = fraction_of_floating_pollution * temp_floating_pollution
local initialpollution = memory.floating_pollution
-- local initialbudget = budget
local map_pollution_cost_multiplier = 1
local destination = Common.current_destination()
if destination.dynamic_data and destination.dynamic_data.initial_spawner_count then
local initial_spawner_count = destination.dynamic_data.initial_spawner_count
if initial_spawner_count > 0 then
local spawnerscount = Common.spawner_count(surface)
if spawnerscount > 0 then
-- if Common.current_destination().subtype and Common.current_destination().subtype == IslandEnum.enum.RADIOACTIVE then
-- -- destroying spawners doesn't do quite as much here:
-- base_pollution_cost_multiplier = (initial_spawner_count/spawnerscount)^(1/3)
-- else
-- base_pollution_cost_multiplier = (initial_spawner_count/spawnerscount)^(1/2)
-- end
-- base_pollution_cost_multiplier = (initial_spawner_count/spawnerscount)^(1/2)
-- Now directly proportional:
map_pollution_cost_multiplier = initial_spawner_count/spawnerscount
if memory.overworldx == 0 then
map_pollution_cost_multiplier = Math.max(1, map_pollution_cost_multiplier)
end -- The first map not being fully loaded when you get there commonly means it records too few initial spawners, which this helps fix
else
map_pollution_cost_multiplier = 1000000
end
end
end
-- if memory.overworldx == 0 then
-- -- less biters:
-- base_pollution_cost_multiplier = base_pollution_cost_multiplier * 2.30 --tuned to teach players to defend, but then to feel relaxing once they do
-- end -- moved to scripted_biters_pollution_cost_multiplier
local base_scripted_biters_pollution_cost_multiplier = Balance.scripted_biters_pollution_cost_multiplier()
map_pollution_cost_multiplier = map_pollution_cost_multiplier * base_scripted_biters_pollution_cost_multiplier
if destination.subtype == IslandEnum.enum.SWAMP then
map_pollution_cost_multiplier = map_pollution_cost_multiplier * 0.95 --biters 5% more aggressive
end
-- if destination.subtype == IslandEnum.enum.MAZE then
-- base_pollution_cost_multiplier = base_pollution_cost_multiplier * 1.2 --biters 20% less aggressive
-- end
if budget >= minimum_avg_units * Common.averageUnitPollutionCost(evolution) * map_pollution_cost_multiplier then
local function spawn(name2)
units_created_count = units_created_count + 1
local unit_pollutioncost = CoreData.biterPollutionValues[name2] * map_pollution_cost_multiplier / wave_size_multiplier
local p = surface.find_non_colliding_position(name2, spawnposition, 60, 1)
if not p then
p = spawnposition
log('no position found, using spawnposition')
end
local biter = surface.create_entity({name = name2, force = enemy_force_name, position = p})
Common.try_make_biter_elite(biter)
units_created[#units_created + 1] = biter
memory.scripted_biters[biter.unit_number] = {entity = biter, created_at = game.tick}
temp_floating_pollution = temp_floating_pollution - unit_pollutioncost
budget = budget - unit_pollutioncost
-- flow statistics should reflect the number of biters generated. Therefore it should mismatch the actual pollution spent, because it should miss all the factors that can vary:
game.pollution_statistics.on_flow(name2, - CoreData.biterPollutionValues[name2])
return biter.unit_number
end
local mixed = (Math.random(3) <= 2)
if mixed then
local whilesafety = 5000
local next_name = enforce_type or Common.get_random_unit_type(evolution)
while units_created_count < maximum_units and budget >= CoreData.biterPollutionValues[next_name] * map_pollution_cost_multiplier and #memory.scripted_biters < CoreData.total_max_biters and whilesafety > 0 do
whilesafety = whilesafety - 1
spawn(next_name)
next_name = enforce_type or Common.get_random_unit_type(evolution)
end
else
local name = enforce_type or Common.get_random_unit_type(evolution)
local whilesafety = 5000
while units_created_count < maximum_units and budget >= CoreData.biterPollutionValues[name] * map_pollution_cost_multiplier and #memory.scripted_biters < CoreData.total_max_biters and whilesafety > 0 do
whilesafety = whilesafety - 1
spawn(name)
end
end
memory.floating_pollution = temp_floating_pollution
else
log('Attacks: ' .. 'Insufficient pollution for wave.')
end
if units_created_count > 0 then
log('Attacks: ' .. 'Spent ' .. Math.floor(100 * (initialpollution - temp_floating_pollution) / initialpollution) .. '% of ' .. Math.ceil(initialpollution) .. ' pollution budget on biters, at ' .. Math.ceil(map_pollution_cost_multiplier/wave_size_multiplier*100)/100 .. 'x price.')
end
return units_created
end
--=== Misc Functions
function Public.generate_main_attack_target()
local memory = Memory.get_crew_memory()
local destination = Common.current_destination()
local target
local fractioncharged = 0
if (not destination.dynamic_data.rocketlaunched) and destination.dynamic_data.rocketsilos and destination.dynamic_data.rocketsilos[1] and destination.dynamic_data.rocketsilos[1].valid and destination.dynamic_data.rocketsilos[1].destructible and destination.dynamic_data.rocketsiloenergyconsumed and destination.dynamic_data.rocketsiloenergyneeded and destination.dynamic_data.rocketsiloenergyneeded > 0 then
fractioncharged = destination.dynamic_data.rocketsiloenergyconsumed / destination.dynamic_data.rocketsiloenergyneeded
if memory.overworldx > 40*22 then --chance of biters going directly to silo
fractioncharged = fractioncharged + 0.03
end
end
local rng = Math.random()
if rng <= fractioncharged^(1/2) then
target = destination.dynamic_data.rocketsilos[1]
else
target = fake_boat_target()
end
return target
end
function Public.generate_side_attack_target(surface, position)
local entities = surface.find_entities_filtered{name = side_attack_target_names}
if not entities then return end
if Math.random(20) >= #entities then return end
entities = Math.shuffle(entities)
entities = Math.shuffle_distancebiased(entities, position)
local weights = {}
for index, _ in pairs(entities) do
weights[#weights + 1] = 1 + Math.floor((#entities - index) / 2)
end
return Raffle.raffle(entities, weights)
end
function Public.nearest_target(surface, position)
local names = {'rocket-silo'}
for _, name in pairs(side_attack_target_names) do
names[#names + 1] = name
end
local entities = surface.find_entities_filtered{name = names}
local d = 9999
local nearest = nil
for i = 1, #entities do
local e = entities[i]
if e and e.valid and Math.distance(e.position, position) < d then
nearest = e
end
end
return nearest
end
-- function Public.try_spend_pollution(surface, position, amount, flow_statistics_source)
-- local memory = Memory.get_crew_memory()
-- local force_name = memory.force_name
-- flow_statistics_source = flow_statistics_source or 'medium-biter'
-- if not (position and surface and surface.valid) then return end
-- local pollution = surface.get_pollution(position)
-- if pollution > amount then
-- surface.pollute(position, -amount)
-- game.forces[force_name].pollution_statistics.on_flow(flow_statistics_source, -amount)
-- return true
-- end
-- return false
-- end
function Public.is_biter_inactive(biter)
if (not biter.entity) or (not biter.entity.valid) then
return true
end
if game.tick - biter.created_at > 30*60*60 then
biter.entity.destroy()
return true
end
return false
end
function Public.get_scripted_biter_count()
local memory = Memory.get_crew_memory()
local count = 0
for k, biter in pairs(memory.scripted_biters) do
if biter.entity and biter.entity.valid then
count = count + 1
else
memory.scripted_biters[k] = nil
end
end
return count
end
-----------commands-----------
function Public.stop()
local command = {
type = defines.command.stop,
distraction = defines.distraction.none
}
return command
end
function Public.move_to(position)
local command = {
type = defines.command.go_to_location,
destination = position,
distraction = defines.distraction.by_anything
}
return command
end
function Public.attack_target_entity(target)
if not (target and target.valid) then return end
local command = {
type = defines.command.attack,
target = target,
distraction = defines.distraction.by_anything
}
return command
end
function Public.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
function Public.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
Math.shuffle(obstacles)
Math.shuffle_distancebiased(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] = Public.move_to(position)
local command = {
type = defines.command.compound,
structure_type = defines.compound_command.return_last,
commands = commands
}
return command
end
function Public.wander_around(ticks_to_wait) --wander individually inside group radius
local command = {
type = defines.command.wander,
distraction = defines.distraction.by_anything,
ticks_to_wait = ticks_to_wait,
}
return command
end
function Public.group_set_commands(group, commands)
if not (group and group.valid) then return end
if commands and #commands > 0 then
local command = {
type = defines.command.compound,
structure_type = defines.compound_command.return_last,
commands = commands
}
group.set_command(command)
end
end
function Public.attack_target(target)
if not (target and target.valid) then return end
local commands
if target.name == 'boatarea' then
commands = {
Public.attack_area(target.position, 32),
Public.attack_area(target.position, 32),
}
else
commands = {
Public.attack_area(target.position, 8),
Public.attack_target_entity(target),
}
end
-- if Math.random(20) == 20 then
-- commands = {
-- Public.attack_obstacles(group.surface, {x = (group.position.x * 0.90 + target.position.x * 0.10), y = (group.position.y * 0.90 + target.position.y * 0.10)}),
-- attackcommand,
-- }
-- else
-- commands = {attackcommand}
-- end
return commands
end
--- small group of revenge biters ---
function Public.revenge_group(surface, p, target, type, bonus_evo, amount_multiplier)
amount_multiplier = amount_multiplier or 1
bonus_evo = bonus_evo or 0
type = type or 'biter'
local memory = Memory.get_crew_memory()
local enemy_force_name = memory.enemy_force_name
local name, count
if type == 'biter' then
name = Common.get_random_biter_type(memory.evolution_factor + bonus_evo)
if name == 'small-biter' then
count = 5
elseif name == 'medium-biter' then
count = 3
elseif name == 'big-biter' then
count = 2
elseif name == 'behemoth-biter' then
count = 1
end
elseif type == 'spitter' then
name = Common.get_random_spitter_type(memory.evolution_factor + bonus_evo)
if name == 'small-spitter' then
count = 7
elseif name == 'medium-spitter' then
count = 4
elseif name == 'big-spitter' then
count = 3
elseif name == 'behemoth-spitter' then
count = 2
end
end
if (not (name and count and count>0)) then return end
local units = {}
for i = 1, Math.floor(count * amount_multiplier) do
local p2 = surface.find_non_colliding_position('wooden-chest', p, 5, 0.5)
if p2 then
local biter = surface.create_entity({name = name, force = enemy_force_name, position = p})
Common.try_make_biter_elite(biter)
-- local biter = surface.create_entity({name = name, force = enemy_force_name, position = p2})
units[#units + 1] = biter
end
end
if #units > 0 then
local unit_group = surface.create_unit_group({position = p, force = enemy_force_name})
if not (unit_group and unit_group.valid) then return end
for _, unit in pairs(units) do
unit_group.add_member(unit)
end
Public.group_set_commands(unit_group, Public.attack_target(target))
unit_group.set_autonomous()
end
end
----------- biter raiding parties -----------
function Public.spawn_boat_biters(boat, max_evo, count, width)
-- max_evolution_bonus = max_evolution_bonus or 0.3
local memory = Memory.get_crew_memory()
local surface = game.surfaces[boat.surface_name]
-- local difficulty = memory.difficulty
local enemy_force_name = boat.force_name
-- local evolution = memory.evolution_factor
local p = {boat.position.x - width/2, boat.position.y}
local units = {}
for i = 1, count do
local name = Common.get_random_unit_type(max_evo - i * 0.04)
-- local name = Common.get_random_unit_type(evolution + i/15 * max_evolution_bonus)
-- local name = Common.get_random_unit_type(evolution + 3 * i/100)
local p2 = surface.find_non_colliding_position('wooden-chest', p, 8, 0.5) --needs to be wooden-chest for collisions to work properly
if p2 then
local biter = surface.create_entity({name = name, force = enemy_force_name, position = p2})
Common.try_make_biter_elite(biter)
memory.scripted_biters[biter.unit_number] = {entity = biter, created_at = game.tick}
units[#units + 1] = biter
end
end
if #units > 0 then
local unit_group = surface.create_unit_group({position = p, force = enemy_force_name})
for _, unit in pairs(units) do
unit_group.add_member(unit)
end
boat.unit_group = {ref = unit_group, script_type = 'landing-party'}
end
end
function Public.update_landing_party_unit_groups(boat, step_distance)
local memory = Memory.get_crew_memory()
-- move unit groups:
local group = boat.unit_group
local surface = game.surfaces[boat.surface_name]
if not (group and surface and surface.valid) then return end
local groupref = group.ref
if not (groupref and groupref.valid) then return end
local p2 = groupref.position
if not p2 then return end
local enemy_force_name = memory.enemy_force_name
local m = groupref.members
groupref.destroy()
local new_group = surface.create_unit_group({position = {x = p2.x + step_distance, y = p2.y}, force = enemy_force_name})
boat.unit_group = {ref = new_group, script_type = 'landing-party'}
for i = 1, #m do
local b = m[i]
new_group.add_member(b)
end
-- if boat.spawner and boat.spawner.valid then
-- new_group.set_command(Public.move_to(boat.spawner.position))
-- end
end
function Public.send_boat_biters_for_attack(eboat)
local memory = Memory.get_crew_memory()
local enemy_force_name = memory.enemy_force_name
local boat = memory.boat
local units = game.surfaces[eboat.surface_name].find_units{area = {{eboat.position.x - 12, eboat.position.y - 12}, {eboat.position.x + 12, eboat.position.y + 12}}, force = enemy_force_name, condition = 'same'}
if #units > 0 then
local unit_group = game.surfaces[eboat.surface_name].create_unit_group({position = eboat.position, force = enemy_force_name})
for _, unit in pairs(units) do
unit_group.add_member(unit)
end
boat.unit_group = {ref = unit_group, script_type = 'landing-party'}
boat.unit_group.ref.set_command({
type = defines.command.attack_area,
destination = ({memory.boat.position.x - 32, memory.boat.position.y} or {0,0}),
radius = 32,
distraction = defines.distraction.by_enemy
})
end
end
function Public.try_boat_biters_attack()
local memory = Memory.get_crew_memory()
local destination = Common.current_destination()
if not destination.dynamic_data.enemyboats then return end
for i = 1, #destination.dynamic_data.enemyboats do
local eboat = destination.dynamic_data.enemyboats[i]
if eboat and eboat.surface_name and game.surfaces[eboat.surface_name] and game.surfaces[eboat.surface_name].valid then
if eboat.state == Boats.enum_state.LANDED and memory.game_lost == false then
Public.send_boat_biters_for_attack(eboat)
end
else
Utils.fast_remove(destination.dynamic_data.enemyboats, i)
end
end
end
local function on_entity_destroyed(event)
local registration_number = event.registration_number
local p
local biter_name
local surface_name
local memory
for i = 1,3 do
Memory.set_working_id(i)
memory = Memory.get_crew_memory()
if memory.elite_biters_stream_registrations then
for j, r in pairs(memory.elite_biters_stream_registrations) do
if r.number == registration_number then
p = r.position
biter_name = r.biter_name
surface_name = r.surface_name
memory.elite_biters_stream_registrations = Utils.ordered_table_with_index_removed(memory.elite_biters_stream_registrations, j)
break
end
end
end
if p then break end
end
if p then
local surface = game.surfaces[surface_name]
if not (surface and surface.valid) then return end
local p2 = surface.find_non_colliding_position('medium-biter', p, 10, 0.2)
if not p2 then return end
surface.create_entity{name = biter_name, position = p2, force = memory.enemy_force_name}
end
end
-- function Public.destroy_inactive_scripted_biters()
-- local memory = Memory.get_crew_memory()
-- local floating_pollution_accrued = 0
-- for unit_number, biter in pairs(memory.scripted_biters) do
-- if Public.is_biter_inactive(biter) then
-- memory.floating_pollution = memory.floating_pollution + CoreData.biterPollutionValues[biter.entity.name]
-- floating_pollution_accrued = floating_pollution_accrued + CoreData.biterPollutionValues[biter.entity.name]
-- memory.scripted_biters[unit_number] = nil
-- end
-- end
-- if _DEBUG and floating_pollution_accrued > 0 then game.print(game.tick .. string.format(":%f of spare pollution accrued", floating_pollution_accrued)) end
-- end
--=== Data
local event = require 'utils.event'
event.add(defines.events.on_entity_destroyed, on_entity_destroyed)
return Public