1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-06 00:23:49 +02:00
ComfyFactorio/maps/pirates/ai.lua
Piratux 09254b72c3 Elite biters
Changes:
- Some biters in hard and nightmare difficulties have a chance to become elite. Elite biters have 5x more health in hard difficulty and 10x more health in nightmare difficulty.
- Increased uranium-235 requirement for late game Radioactive islands. The amount required remains the same for the first occurrence of this island.
2023-02-04 17:14:48 +02:00

896 lines
32 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 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 (not destination.type) or (not destination.type == Surfaces.enum.ISLAND) then return end
if (not memory.boat.state) or (not (memory.boat.state == Boats.enum_state.LANDED or 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,
}
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 memory = Memory.get_crew_memory()
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()
-- 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 <= 890 then
wave_size_multiplier = 1
elseif rng2 <= 970 then
wave_size_multiplier = 1.5
elseif rng2 <= 985 then
wave_size_multiplier = 2
elseif rng2 <= 995 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()
if not group or not group.valid or not target or not target.valid then return end
-- group.set_command(Public.attack_target(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
if not group or not group.valid or not target or not target.valid then log('target invalid') return end
-- group.set_command(Public.attack_target(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_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)
if (not target) or (not target.valid) then return end
-- group.set_command(Public.attack_target(target))
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()
if target and target.valid then
Public.group_set_commands(biter.entity, Public.attack_target(target))
end
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})
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 #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 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})
for _, unit in pairs(units) do
unit_group.add_member(unit)
end
if target and target.valid then
Public.group_set_commands(unit_group, Public.attack_target(target))
end
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.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
return Public