2022-06-01 20:50:36 +02:00
-- This file is part of thesixthroc's Pirate Ship softmod, licensed under GPLv3 and stored at https://github.com/danielmartin0/ComfyFactorio-Pirates.
2021-10-13 10:21:53 +02:00
local Memory = require ' maps.pirates.memory '
local Balance = require ' maps.pirates.balance '
local Common = require ' maps.pirates.common '
local CoreData = require ' maps.pirates.coredata '
2022-03-19 23:20:55 +02:00
-- local Utils = require 'maps.pirates.utils_local'
2021-10-13 10:21:53 +02:00
local Math = require ' maps.pirates.math '
2022-05-14 00:35:12 +02:00
local Raffle = require ' maps.pirates.raffle '
2022-03-19 23:20:55 +02:00
local _inspect = require ' utils.inspect ' . inspect
2021-10-13 10:21:53 +02:00
2022-03-19 23:20:55 +02:00
-- local Structures = require 'maps.pirates.structures.structures'
2021-10-13 10:21:53 +02:00
local Boats = require ' maps.pirates.structures.boats.boats '
local Surfaces = require ' maps.pirates.surfaces.surfaces '
2022-10-28 14:21:31 +02:00
-- local Islands = require 'maps.pirates.surfaces.islands.islands'
2022-10-10 21:03:51 +02:00
local IslandEnum = require ' maps.pirates.surfaces.islands.island_enum '
2022-03-19 23:20:55 +02:00
-- local Sea = require 'maps.pirates.surfaces.sea.sea'
-- local Crew = require 'maps.pirates.crew'
-- local Quest = require 'maps.pirates.quest'
2023-01-12 18:58:53 +02:00
local SurfacesCommon = require ' maps.pirates.surfaces.common '
2023-02-26 22:22:31 +02:00
local Utils = require ' maps.pirates.utils_local '
2021-10-13 10:21:53 +02:00
local Public = { }
local function fake_boat_target ( )
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
-- fff 283 discussed pollution mechanics: https://factorio.com/blog/post/fff-283
local side_attack_target_names = {
' character ' ,
' pumpjack ' ,
' radar ' ,
' electric-mining-drill ' ,
2022-03-14 23:38:44 +02:00
' assembling-machine-1 ' ,
' solar-panel ' ,
2021-10-13 10:21:53 +02:00
' nuclear-reactor ' ,
' oil-refinery ' ,
' centrifuge ' ,
}
--=== Tick Actions
function Public . Tick_actions ( tickinterval )
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
local destination = Common.current_destination ( )
2021-10-13 10:21:53 +02:00
2023-02-18 18:57:33 +02:00
if destination.type ~= Surfaces.enum . ISLAND then return end
2023-02-20 20:53:51 +02:00
if memory.boat . state ~= Boats.enum_state . LANDED and memory.boat . state ~= Boats.enum_state . RETREATING then return end
2022-03-19 23:20:55 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
2022-03-04 19:57:58 +02:00
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 ( )
2022-03-01 23:59:48 +02:00
Common.increment_evo ( extra_evo )
2021-10-13 10:21:53 +02:00
destination.dynamic_data . evolution_accrued_time = destination.dynamic_data . evolution_accrued_time + extra_evo
end
2023-02-20 20:53:51 +02:00
-- 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.
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local minute_cycle = { -- warning: use even seconds only
2022-05-05 10:55:48 +02:00
[ 2 ] = Public.tell_biters_near_silo_to_attack_it ,
2023-02-20 20:53:51 +02:00
[ 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 ,
2022-05-05 10:55:48 +02:00
2023-02-20 20:53:51 +02:00
[ 32 ] = Public.tell_biters_near_silo_to_attack_it ,
[ 34 ] = Public.poke_script_groups ,
[ 36 ] = Public.try_rogue_attack ,
2022-05-05 10:55:48 +02:00
2023-02-20 20:53:51 +02:00
[ 46 ] = Public.poke_inactive_scripted_biters ,
[ 48 ] = Public.create_mail_delivery_biters ,
2022-05-05 10:55:48 +02:00
2023-02-20 20:53:51 +02:00
[ 54 ] = Public.try_boat_biters_attack ,
}
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
if minute_cycle [ ( game.tick / 60 ) % 60 ] then
minute_cycle [ ( game.tick / 60 ) % 60 ] ( )
end
2021-10-13 10:21:53 +02:00
end
function Public . eat_up_fraction_of_all_pollution_wrapped ( )
2023-02-20 20:53:51 +02:00
local surface = game.surfaces [ Common.current_destination ( ) . surface_name ]
Public.eat_up_fraction_of_all_pollution ( surface , 0.05 )
2021-10-13 10:21:53 +02:00
end
function Public . eat_up_fraction_of_all_pollution ( surface , fraction_of_global_pollution )
2022-03-19 23:20:55 +02:00
2021-10-13 10:21:53 +02:00
local memory = Memory.get_crew_memory ( )
2022-03-19 23:20:55 +02:00
-- local enemy_force_name = memory.enemy_force_name
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local pollution_available = memory.floating_pollution
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
surface.pollute ( p , - pollution_to_eat )
2021-10-13 10:21:53 +02:00
-- Radioactive world doesn't absorb map pollution:
2023-01-12 18:58:53 +02:00
if not ( Common.current_destination ( ) . subtype == IslandEnum.enum . RADIOACTIVE ) then
2022-03-19 23:20:55 +02:00
pollution_available = pollution_available + pollution_to_eat
2021-10-13 10:21:53 +02:00
end
2023-02-20 20:53:51 +02:00
end
2021-10-13 10:21:53 +02:00
-- if _DEBUG then
-- game.print(string.format('ate %f pollution', pollution_available))
-- end
2023-02-20 20:53:51 +02:00
memory.floating_pollution = pollution_available
2021-10-13 10:21:53 +02:00
end
2022-05-01 12:52:43 +02:00
function Public . wave_size_rng ( ) -- random variance in attack sizes
2022-06-02 14:34:22 +02:00
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
local destination = Common.current_destination ( )
2023-01-12 18:58:53 +02:00
2023-06-24 19:55:43 +02:00
local rng_scale = Balance.crew_scale ( ) ^ ( 1 / 4 ) -- slightly dampen wave variance for small crews as they can't handle it
2023-04-02 20:19:26 +02:00
2023-02-20 20:53:51 +02:00
-- 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
2022-06-02 14:34:22 +02:00
2023-02-20 20:53:51 +02:00
local wave_percentage_chance = Math.clamp ( 0 , 50 , 11 + 8 * memory.floating_pollution / 1500 )
2022-06-02 14:34:22 +02:00
2022-02-24 21:39:03 +02:00
local wave_size_multiplier = 1
2022-05-06 17:16:15 +02:00
local rng1 = Math.random ( 100 )
2023-04-02 20:21:21 +02:00
2022-06-02 14:34:22 +02:00
if rng1 > wave_percentage_chance then
2022-05-06 17:16:15 +02:00
wave_size_multiplier = 0
elseif memory.overworldx > 0 then
local rng2 = Math.random ( 1000 )
2023-04-02 20:19:26 +02:00
if rng2 >= 110 * rng_scale then
2022-05-06 17:16:15 +02:00
wave_size_multiplier = 1
2023-04-02 20:19:26 +02:00
elseif rng2 >= 30 * rng_scale then
2022-05-06 17:16:15 +02:00
wave_size_multiplier = 1.5
2023-04-02 20:19:26 +02:00
elseif rng2 >= 15 * rng_scale then
2022-05-06 17:16:15 +02:00
wave_size_multiplier = 2
2023-04-02 20:19:26 +02:00
elseif rng2 >= 5 * rng_scale then
2022-12-02 21:21:19 +02:00
wave_size_multiplier = 3
2022-05-01 12:52:43 +02:00
else
2022-12-02 21:21:19 +02:00
wave_size_multiplier = 4
2022-05-01 12:52:43 +02:00
end
2022-02-28 18:36:46 +02:00
end
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
return wave_size_multiplier
end
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
function Public . try_main_attack ( )
2022-05-05 10:55:48 +02:00
Public.eat_up_fraction_of_all_pollution_wrapped ( )
2022-05-01 12:52:43 +02:00
local wave_size_multiplier = Public.wave_size_rng ( )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
if wave_size_multiplier == 0 then
2022-05-05 10:55:48 +02:00
log ( ' Attacks: ' .. ' Aborted by chance. ' )
2022-05-01 12:52:43 +02:00
return nil
else
2022-11-13 23:17:46 +02:00
local group = Public.spawn_group_of_scripted_biters ( 2 / 3 , 6 , 350 , wave_size_multiplier )
2022-05-01 12:52:43 +02:00
local target = Public.generate_main_attack_target ( )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
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
2022-02-28 18:36:46 +02:00
end
2022-05-01 12:52:43 +02:00
end
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
function Public . try_secondary_attack ( )
2022-05-05 10:55:48 +02:00
Public.eat_up_fraction_of_all_pollution_wrapped ( )
2022-05-01 12:52:43 +02:00
local wave_size_multiplier = Public.wave_size_rng ( )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
if wave_size_multiplier == 0 then
2022-05-05 10:55:48 +02:00
log ( ' Attacks: ' .. ' Aborted by chance. ' )
2022-05-01 12:52:43 +02:00
return nil
else
local surface = game.surfaces [ Common.current_destination ( ) . surface_name ]
2021-10-13 10:21:53 +02:00
2022-11-13 23:17:46 +02:00
local group = Public.spawn_group_of_scripted_biters ( 2 / 3 , 12 , 275 , wave_size_multiplier )
2022-05-01 12:52:43 +02:00
if not ( group and group.valid ) then return end
2022-03-19 23:20:55 +02:00
2022-05-01 12:52:43 +02:00
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
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
Public.group_set_commands ( group , Public.attack_target ( target ) )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
-- 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
2021-10-13 10:21:53 +02:00
end
function Public . try_rogue_attack ( )
2022-05-05 10:55:48 +02:00
Public.eat_up_fraction_of_all_pollution_wrapped ( )
2022-05-01 12:52:43 +02:00
local wave_size_multiplier = Public.wave_size_rng ( )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
if wave_size_multiplier == 0 then
2022-05-05 10:55:48 +02:00
log ( ' Attacks: ' .. ' Aborted by chance. ' )
2022-05-01 12:52:43 +02:00
return nil
else
local surface = game.surfaces [ Common.current_destination ( ) . surface_name ]
2021-10-13 10:21:53 +02:00
2022-11-13 23:17:46 +02:00
local group = Public.spawn_group_of_scripted_biters ( 1 / 2 , 6 , 200 , wave_size_multiplier )
2022-05-01 12:52:43 +02:00
if not ( group and group.valid ) then return end
local target = Public.generate_side_attack_target ( surface , group.position )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
Public.group_set_commands ( group , Public.attack_target ( target ) )
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
-- 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
2021-10-13 10:21:53 +02:00
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 ( )
2023-02-20 20:53:51 +02:00
local destination = Common.current_destination ( )
2021-10-13 10:21:53 +02:00
local surface = game.surfaces [ destination.surface_name ]
local enemy_force_name = memory.enemy_force_name
2023-02-20 20:53:51 +02:00
-- 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
2021-10-13 10:21:53 +02:00
end
function Public . poke_script_groups ( )
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
function Public . poke_inactive_scripted_biters ( )
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
2022-02-28 18:36:46 +02:00
function Public . create_mail_delivery_biters ( ) --these travel cross-map between bases to add a little life and spookyness
2021-10-13 10:21:53 +02:00
local memory = Memory.get_crew_memory ( )
local surface = game.surfaces [ Common.current_destination ( ) . surface_name ]
local enemy_force_name = memory.enemy_force_name
2023-02-20 20:53:51 +02:00
local spawners = Common.get_valid_spawners ( surface )
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local try_how_many_groups = Math.clamp ( 0 , 4 , ( # spawners - 8 ) / 100 )
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
if # far_spawners > 0 then
local s2 = far_spawners [ Math.random ( # far_spawners ) ]
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2022-03-19 23:20:55 +02:00
2023-02-20 20:53:51 +02:00
if ( not units ) or ( not # units ) or ( # units == 0 ) then return end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local start_p = surface.find_non_colliding_position ( ' rocket-silo ' , s1.position , 256 , 2 ) or s1.position
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2023-02-18 18:57:33 +02:00
2023-02-20 20:53:51 +02:00
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 ' }
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
Public.group_set_commands ( unit_group , {
Public.move_to ( s2.position ) ,
Public.wander_around ( ) ,
} )
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
-- 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
2021-10-13 10:21:53 +02:00
end
--=== Spawn scripted biters
2022-02-24 21:39:03 +02:00
function Public . spawn_group_of_scripted_biters ( fraction_of_floating_pollution , minimum_avg_units , maximum_units , wave_size_multiplier )
2021-10-13 10:21:53 +02:00
local memory = Memory.get_crew_memory ( )
local surface = game.surfaces [ Common.current_destination ( ) . surface_name ]
local enemy_force_name = memory.enemy_force_name
2022-03-19 23:20:55 +02:00
2023-02-20 20:53:51 +02:00
local spawner = Common.get_random_valid_spawner ( surface )
if not spawner then log ( ' no spawner found ' ) return end
2021-10-13 10:21:53 +02:00
2022-03-05 17:01:26 +02:00
local nearby_units_to_bring
2022-03-12 00:53:36 +02:00
if Public.get_scripted_biter_count ( ) >= 9 / 10 * CoreData.total_max_biters then
2022-03-05 17:01:26 +02:00
-- pick up nearby units that might be idle:
2022-03-11 00:11:51 +02:00
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 ' }
2022-03-05 17:01:26 +02:00
else
nearby_units_to_bring = { }
end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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 )
2022-03-05 16:09:35 +02:00
2023-02-20 20:53:51 +02:00
if ( new_units and nearby_units_to_bring and ( # new_units + # nearby_units_to_bring ) == 0 ) then return end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local position = surface.find_non_colliding_position ( ' rocket-silo ' , spawner.position , 256 , 2 ) or spawner.position
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
2022-05-01 12:52:43 +02:00
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 )
2023-02-20 20:53:51 +02:00
maximum_units = maximum_units or 400
2022-02-27 18:42:25 +02:00
2022-02-28 18:36:46 +02:00
-- log('ai spawning attempt params: ' .. (fraction_of_floating_pollution or '') .. ' ' .. (minimum_avg_units or '') .. ' ' .. (maximum_units or '') .. ' ' .. (unit_pollutioncost_multiplier or '') .. ' ' .. (enforce_type or ''))
2022-03-19 23:20:55 +02:00
2021-10-13 10:21:53 +02:00
local memory = Memory.get_crew_memory ( )
2022-03-05 16:09:35 +02:00
local surface = game.surfaces [ Common.current_destination ( ) . surface_name ]
-- local surface = spawner.surface
-- local spawnposition = spawner.position
2023-02-20 20:53:51 +02:00
-- local difficulty = memory.difficulty
2021-10-13 10:21:53 +02:00
local enemy_force_name = memory.enemy_force_name
2022-03-01 23:59:48 +02:00
local evolution = memory.evolution_factor
2021-10-13 10:21:53 +02:00
local units_created_count = 0
local units_created = { }
2023-02-20 20:53:51 +02:00
local temp_floating_pollution = memory.floating_pollution
local budget = fraction_of_floating_pollution * temp_floating_pollution
2022-02-27 18:42:25 +02:00
2023-02-20 20:53:51 +02:00
local initialpollution = memory.floating_pollution
-- local initialbudget = budget
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
local map_pollution_cost_multiplier = 1
2021-10-13 10:21:53 +02:00
local destination = Common.current_destination ( )
2022-03-07 14:57:07 +02:00
if destination.dynamic_data and destination.dynamic_data . initial_spawner_count then
2021-10-13 10:21:53 +02:00
local initial_spawner_count = destination.dynamic_data . initial_spawner_count
2022-03-07 14:57:07 +02:00
if initial_spawner_count > 0 then
local spawnerscount = Common.spawner_count ( surface )
2021-10-13 10:21:53 +02:00
if spawnerscount > 0 then
2022-10-10 21:03:51 +02:00
-- if Common.current_destination().subtype and Common.current_destination().subtype == IslandEnum.enum.RADIOACTIVE then
2021-10-13 10:21:53 +02:00
-- -- 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:
2022-05-01 12:52:43 +02:00
map_pollution_cost_multiplier = initial_spawner_count / spawnerscount
2022-03-19 23:20:55 +02:00
2022-03-07 14:57:07 +02:00
if memory.overworldx == 0 then
2022-05-01 12:52:43 +02:00
map_pollution_cost_multiplier = Math.max ( 1 , map_pollution_cost_multiplier )
2022-03-07 14:57:07 +02:00
end -- The first map not being fully loaded when you get there commonly means it records too few initial spawners, which this helps fix
2021-10-13 10:21:53 +02:00
else
2022-05-01 12:52:43 +02:00
map_pollution_cost_multiplier = 1000000
2021-10-13 10:21:53 +02:00
end
end
end
2022-05-01 12:52:43 +02:00
-- 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
2022-02-24 21:39:03 +02:00
2022-05-01 12:52:43 +02:00
local base_scripted_biters_pollution_cost_multiplier = Balance.scripted_biters_pollution_cost_multiplier ( )
2022-03-19 23:20:55 +02:00
2022-05-01 12:52:43 +02:00
map_pollution_cost_multiplier = map_pollution_cost_multiplier * base_scripted_biters_pollution_cost_multiplier
2021-10-13 10:21:53 +02:00
2023-01-12 18:58:53 +02:00
if destination.subtype == IslandEnum.enum . SWAMP then
2022-05-01 12:52:43 +02:00
map_pollution_cost_multiplier = map_pollution_cost_multiplier * 0.95 --biters 5% more aggressive
2022-03-03 02:19:20 +02:00
end
2023-01-12 18:58:53 +02:00
-- if destination.subtype == IslandEnum.enum.MAZE then
2022-03-14 23:38:44 +02:00
-- base_pollution_cost_multiplier = base_pollution_cost_multiplier * 1.2 --biters 20% less aggressive
-- end
2022-03-05 02:11:11 +02:00
2023-02-20 20:53:51 +02:00
if budget >= minimum_avg_units * Common.averageUnitPollutionCost ( evolution ) * map_pollution_cost_multiplier then
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local function spawn ( name2 )
units_created_count = units_created_count + 1
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
local unit_pollutioncost = CoreData.biterPollutionValues [ name2 ] * map_pollution_cost_multiplier / wave_size_multiplier
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local p = surface.find_non_colliding_position ( name2 , spawnposition , 60 , 1 )
if not p then
2022-03-05 16:09:35 +02:00
p = spawnposition
log ( ' no position found, using spawnposition ' )
2022-03-04 22:31:38 +02:00
end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local biter = surface.create_entity ( { name = name2 , force = enemy_force_name , position = p } )
Common.try_make_biter_elite ( biter )
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
units_created [ # units_created + 1 ] = biter
memory.scripted_biters [ biter.unit_number ] = { entity = biter , created_at = game.tick }
2021-10-13 10:21:53 +02:00
2022-05-01 12:52:43 +02:00
temp_floating_pollution = temp_floating_pollution - unit_pollutioncost
budget = budget - unit_pollutioncost
2022-03-03 02:19:20 +02:00
-- 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:
2022-05-01 12:52:43 +02:00
game.pollution_statistics . on_flow ( name2 , - CoreData.biterPollutionValues [ name2 ] )
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
return biter.unit_number
end
2021-10-13 10:21:53 +02:00
2022-02-27 18:42:25 +02:00
local mixed = ( Math.random ( 3 ) <= 2 )
2021-10-13 10:21:53 +02:00
if mixed then
2022-05-05 10:55:48 +02:00
local whilesafety = 5000
2021-10-13 10:21:53 +02:00
local next_name = enforce_type or Common.get_random_unit_type ( evolution )
2022-05-01 12:52:43 +02:00
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
2021-10-13 10:21:53 +02:00
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 )
2022-05-05 10:55:48 +02:00
local whilesafety = 5000
2022-05-01 12:52:43 +02:00
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
2021-10-13 10:21:53 +02:00
whilesafety = whilesafety - 1
spawn ( name )
end
end
2023-02-20 20:53:51 +02:00
memory.floating_pollution = temp_floating_pollution
2022-05-05 10:55:48 +02:00
else
log ( ' Attacks: ' .. ' Insufficient pollution for wave. ' )
2022-05-01 12:52:43 +02:00
end
2022-02-27 18:42:25 +02:00
if units_created_count > 0 then
2022-05-05 10:55:48 +02:00
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. ' )
2022-02-27 18:42:25 +02:00
end
2022-03-19 23:20:55 +02:00
2023-02-20 20:53:51 +02:00
return units_created
2021-10-13 10:21:53 +02:00
end
--=== Misc Functions
function Public . generate_main_attack_target ( )
2022-07-31 14:08:22 +02:00
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
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 ( )
2021-10-13 10:21:53 +02:00
if rng <= fractioncharged ^ ( 1 / 2 ) then
2022-02-24 21:39:03 +02:00
target = destination.dynamic_data . rocketsilos [ 1 ]
2021-10-13 10:21:53 +02:00
else
target = fake_boat_target ( )
end
2023-02-20 20:53:51 +02:00
return target
2021-10-13 10:21:53 +02:00
end
function Public . generate_side_attack_target ( surface , position )
2023-02-20 20:53:51 +02:00
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 )
2021-10-13 10:21:53 +02:00
end
function Public . nearest_target ( surface , position )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
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 )
2023-02-20 20:53:51 +02:00
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 ( )
2021-10-13 10:21:53 +02:00
return true
end
2023-02-20 20:53:51 +02:00
return false
2021-10-13 10:21:53 +02:00
end
function Public . get_scripted_biter_count ( )
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
-----------commands-----------
function Public . stop ( )
2023-02-20 20:53:51 +02:00
local command = {
type = defines.command . stop ,
distraction = defines.distraction . none
}
return command
2021-10-13 10:21:53 +02:00
end
function Public . move_to ( position )
2023-02-20 20:53:51 +02:00
local command = {
type = defines.command . go_to_location ,
destination = position ,
distraction = defines.distraction . by_anything
}
return command
2021-10-13 10:21:53 +02:00
end
function Public . attack_target_entity ( target )
2023-02-20 20:53:51 +02:00
if not ( target and target.valid ) then return end
local command = {
type = defines.command . attack ,
target = target ,
distraction = defines.distraction . by_anything
}
return command
2021-10-13 10:21:53 +02:00
end
function Public . attack_area ( position , radius )
2023-02-20 20:53:51 +02:00
local command = {
type = defines.command . attack_area ,
destination = position ,
radius = radius or 25 ,
distraction = defines.distraction . by_anything
}
return command
2021-10-13 10:21:53 +02:00
end
function Public . attack_obstacles ( surface , position )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
function Public . wander_around ( ticks_to_wait ) --wander individually inside group radius
2023-02-20 20:53:51 +02:00
local command = {
type = defines.command . wander ,
distraction = defines.distraction . by_anything ,
ticks_to_wait = ticks_to_wait ,
}
return command
2021-10-13 10:21:53 +02:00
end
function Public . group_set_commands ( group , commands )
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
function Public . attack_target ( target )
2023-02-18 18:57:33 +02:00
if not ( target and target.valid ) then return end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local commands
2021-10-13 10:21:53 +02:00
if target.name == ' boatarea ' then
2023-02-20 20:53:51 +02:00
commands = {
Public.attack_area ( target.position , 32 ) ,
Public.attack_area ( target.position , 32 ) ,
}
2021-10-13 10:21:53 +02:00
else
2023-02-20 20:53:51 +02:00
commands = {
Public.attack_area ( target.position , 8 ) ,
Public.attack_target_entity ( target ) ,
}
2021-10-13 10:21:53 +02:00
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)}),
2023-02-20 20:53:51 +02:00
-- attackcommand,
2021-10-13 10:21:53 +02:00
-- }
-- else
-- commands = {attackcommand}
-- end
2023-02-20 20:53:51 +02:00
return commands
2021-10-13 10:21:53 +02:00
end
--- small group of revenge biters ---
2022-02-28 18:36:46 +02:00
function Public . revenge_group ( surface , p , target , type , bonus_evo , amount_multiplier )
amount_multiplier = amount_multiplier or 1
bonus_evo = bonus_evo or 0
2021-10-13 10:21:53 +02:00
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
2022-03-01 23:59:48 +02:00
name = Common.get_random_biter_type ( memory.evolution_factor + bonus_evo )
2021-10-13 10:21:53 +02:00
if name == ' small-biter ' then
2022-03-06 01:42:47 +02:00
count = 5
2021-10-13 10:21:53 +02:00
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
2022-03-01 23:59:48 +02:00
name = Common.get_random_spitter_type ( memory.evolution_factor + bonus_evo )
2021-10-13 10:21:53 +02:00
if name == ' small-spitter ' then
2022-03-06 01:42:47 +02:00
count = 7
2021-10-13 10:21:53 +02:00
elseif name == ' medium-spitter ' then
2022-07-31 11:23:59 +02:00
count = 4
2021-10-13 10:21:53 +02:00
elseif name == ' big-spitter ' then
2022-03-06 01:42:47 +02:00
count = 3
2021-10-13 10:21:53 +02:00
elseif name == ' behemoth-spitter ' then
count = 2
end
end
if ( not ( name and count and count > 0 ) ) then return end
2023-02-20 20:53:51 +02:00
local units = { }
2022-02-28 18:36:46 +02:00
for i = 1 , Math.floor ( count * amount_multiplier ) do
2021-10-13 10:21:53 +02:00
local p2 = surface.find_non_colliding_position ( ' wooden-chest ' , p , 5 , 0.5 )
if p2 then
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
if # units > 0 then
local unit_group = surface.create_unit_group ( { position = p , force = enemy_force_name } )
2023-02-20 20:53:51 +02:00
if not ( unit_group and unit_group.valid ) then return end
2023-02-18 18:57:33 +02:00
2021-10-13 10:21:53 +02:00
for _ , unit in pairs ( units ) do
unit_group.add_member ( unit )
end
2023-02-20 20:53:51 +02:00
Public.group_set_commands ( unit_group , Public.attack_target ( target ) )
2021-10-13 10:21:53 +02:00
unit_group.set_autonomous ( )
end
end
----------- biter raiding parties -----------
2022-02-28 18:36:46 +02:00
function Public . spawn_boat_biters ( boat , max_evo , count , width )
2021-10-13 10:21:53 +02:00
-- max_evolution_bonus = max_evolution_bonus or 0.3
local memory = Memory.get_crew_memory ( )
local surface = game.surfaces [ boat.surface_name ]
2023-02-20 20:53:51 +02:00
-- local difficulty = memory.difficulty
2021-10-13 10:21:53 +02:00
local enemy_force_name = boat.force_name
2022-03-01 23:59:48 +02:00
-- local evolution = memory.evolution_factor
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local p = { boat.position . x - width / 2 , boat.position . y }
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local units = { }
2022-02-28 18:36:46 +02:00
for i = 1 , count do
2023-02-20 20:53:51 +02:00
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)
2021-10-13 10:21:53 +02:00
2022-02-28 18:36:46 +02:00
local p2 = surface.find_non_colliding_position ( ' wooden-chest ' , p , 8 , 0.5 ) --needs to be wooden-chest for collisions to work properly
2021-10-13 10:21:53 +02:00
if p2 then
2023-02-20 20:53:51 +02:00
local biter = surface.create_entity ( { name = name , force = enemy_force_name , position = p2 } )
Common.try_make_biter_elite ( biter )
2022-03-19 23:20:55 +02:00
2023-02-20 20:53:51 +02:00
memory.scripted_biters [ biter.unit_number ] = { entity = biter , created_at = game.tick }
2022-03-19 23:20:55 +02:00
2023-02-20 20:53:51 +02:00
units [ # units + 1 ] = biter
end
end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
end
function Public . update_landing_party_unit_groups ( boat , step_distance )
local memory = Memory.get_crew_memory ( )
2023-02-20 20:53:51 +02:00
-- 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
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local groupref = group.ref
if not ( groupref and groupref.valid ) then return end
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
local p2 = groupref.position
if not p2 then return end
2021-10-13 10:21:53 +02:00
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
2022-03-04 19:57:58 +02:00
-- if boat.spawner and boat.spawner.valid then
-- new_group.set_command(Public.move_to(boat.spawner.position))
-- end
2021-10-13 10:21:53 +02:00
end
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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 ' }
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
function Public . try_boat_biters_attack ( )
local memory = Memory.get_crew_memory ( )
local destination = Common.current_destination ( )
2021-10-13 10:21:53 +02:00
2023-02-20 20:53:51 +02:00
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
2023-02-26 22:22:31 +02:00
Utils.fast_remove ( destination.dynamic_data . enemyboats , i )
2023-02-20 20:53:51 +02:00
end
end
end
2021-10-13 10:21:53 +02:00
-- 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