2018-07-20 23:01:34 +02:00
local Event = require ' utils.event '
2019-01-04 22:02:55 +02:00
local Task = require ' utils.task '
2018-11-26 03:07:03 +02:00
local Token = require ' utils.token '
2018-07-20 23:01:34 +02:00
local Global = require ' utils.global '
2019-01-11 17:31:32 +02:00
local math = require ' utils.math '
2019-06-21 22:29:35 +02:00
local table = require ' utils.table '
2019-01-10 16:34:51 +02:00
local random = math.random
local set_timeout_in_ticks = Task.set_timeout_in_ticks
2019-01-11 17:31:32 +02:00
local ceil = math.ceil
2019-06-21 22:29:35 +02:00
local draw_arc = rendering.draw_arc
local fast_remove = table.fast_remove
local tau = 2 * math.pi
local start_angle = - tau / 4
local update_rate = 4 -- ticks between updates
local time_to_live = update_rate + 1
local pole_respawn_time = 60 * 60
2018-07-20 23:01:34 +02:00
local no_coin_entity = { }
2020-04-16 13:18:08 +02:00
Global.register (
{ no_coin_entity = no_coin_entity } ,
function ( tbl )
no_coin_entity = tbl.no_coin_entity
end
)
2018-07-20 23:01:34 +02:00
local entity_drop_amount = {
--[[['small-biter'] = {low = -62, high = 1},
[ ' small-spitter ' ] = { low = - 62 , high = 1 } ,
[ ' medium-biter ' ] = { low = - 14 , high = 1 } ,
[ ' medium-spitter ' ] = { low = - 14 , high = 1 } ,
[ ' big-biter ' ] = { low = - 2 , high = 1 } ,
[ ' big-spitter ' ] = { low = - 2 , high = 1 } ,
[ ' behemoth-biter ' ] = { low = 1 , high = 1 } ,
[ ' behemoth-spitter ' ] = { low = 1 , high = 1 } , ] ]
2019-01-11 17:31:32 +02:00
[ ' biter-spawner ' ] = { low = 8 , high = 24 } ,
[ ' spitter-spawner ' ] = { low = 8 , high = 24 } ,
[ ' small-worm-turret ' ] = { low = 3 , high = 10 } ,
[ ' medium-worm-turret ' ] = { low = 8 , high = 24 } ,
2019-03-07 17:55:49 +02:00
[ ' big-worm-turret ' ] = { low = 15 , high = 30 } ,
[ ' behemoth-worm-turret ' ] = { low = 25 , high = 45 }
2018-07-20 23:01:34 +02:00
}
2020-04-16 13:18:08 +02:00
local spill_items =
Token.register (
function ( data )
local stack = { name = ' coin ' , count = data.count }
data.surface . spill_item_stack ( data.position , stack , true )
end
)
2018-07-20 23:01:34 +02:00
local entity_spawn_map = {
2019-11-01 18:04:43 +02:00
[ ' medium-biter ' ] = { name = ' small-worm-turret ' , count = 1 , chance = 0.2 } ,
[ ' big-biter ' ] = { name = ' medium-worm-turret ' , count = 1 , chance = 0.2 } ,
[ ' behemoth-biter ' ] = { name = ' big-worm-turret ' , count = 1 , chance = 0.2 } ,
2019-01-11 17:31:32 +02:00
[ ' medium-spitter ' ] = { name = ' small-worm-turret ' , count = 1 , chance = 0.2 } ,
[ ' big-spitter ' ] = { name = ' medium-worm-turret ' , count = 1 , chance = 0.2 } ,
[ ' behemoth-spitter ' ] = { name = ' big-worm-turret ' , count = 1 , chance = 0.2 } ,
2019-01-10 16:34:51 +02:00
[ ' biter-spawner ' ] = { type = ' biter ' , count = 5 , chance = 1 } ,
[ ' spitter-spawner ' ] = { type = ' spitter ' , count = 5 , chance = 1 } ,
2019-11-01 18:04:43 +02:00
[ ' behemoth-worm-turret ' ] = {
type = ' compound ' ,
spawns = {
{ name = ' behemoth-spitter ' , count = 2 } ,
{ name = ' behemoth-biter ' , count = 2 }
} ,
chance = 1
} ,
2019-01-23 21:15:28 +02:00
[ ' stone-furnace ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' steel-furnace ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' electric-furnace ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' assembling-machine-1 ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' assembling-machine-2 ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' assembling-machine-3 ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' chemical-plant ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' centrifuge ' ] = { type = ' cause ' , count = 6 , chance = 1 } ,
2019-01-26 20:07:09 +02:00
[ ' pumpjack ' ] = { type = ' cause ' , count = 6 , chance = 1 } ,
[ ' storage-tank ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
2019-01-23 21:15:28 +02:00
[ ' oil-refinery ' ] = { type = ' cause ' , count = 8 , chance = 1 } ,
[ ' offshore-pump ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' boiler ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' heat-exchanger ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' steam-engine ' ] = { type = ' cause ' , count = 6 , chance = 1 } ,
[ ' steam-turbine ' ] = { type = ' cause ' , count = 10 , chance = 1 } ,
[ ' nuclear-reactor ' ] = { type = ' cause ' , count = 20 , chance = 1 } ,
[ ' rocket-silo ' ] = { type = ' cause ' , count = 40 , chance = 1 } ,
[ ' train-stop ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' burner-mining-drill ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' electric-mining-drill ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' lab ' ] = { type = ' cause ' , count = 6 , chance = 1 } ,
[ ' solar-panel ' ] = { type = ' cause ' , count = 4 , chance = 1 } ,
[ ' accumulator ' ] = { type = ' cause ' , count = 2 , chance = 1 } ,
[ ' beacon ' ] = { type = ' cause ' , count = 6 , chance = 1 } ,
[ ' radar ' ] = { type = ' cause ' , count = 4 , chance = 1 }
2018-07-20 23:01:34 +02:00
}
2019-01-10 16:34:51 +02:00
local unit_levels = {
2020-04-06 22:30:47 +02:00
biter = { ' small-biter ' , ' medium-biter ' , ' big-biter ' , ' behemoth-biter ' } ,
2019-01-10 16:34:51 +02:00
spitter = {
2020-04-16 13:18:08 +02:00
' small-spitter ' ,
' medium-spitter ' ,
' big-spitter ' ,
' behemoth-spitter '
2019-01-10 16:34:51 +02:00
}
2018-07-20 23:01:34 +02:00
}
2019-01-11 17:31:32 +02:00
local worms = {
[ ' small-worm-turret ' ] = true ,
[ ' medium-worm-turret ' ] = true ,
2019-03-07 17:55:49 +02:00
[ ' big-worm-turret ' ] = true ,
[ ' behemoth-worm-turret ' ] = true
2019-01-11 17:31:32 +02:00
}
2019-01-11 01:10:36 +02:00
local allowed_cause_source = {
[ ' small-biter ' ] = true ,
[ ' medium-biter ' ] = true ,
[ ' big-biter ' ] = true ,
[ ' behemoth-biter ' ] = true ,
[ ' small-spitter ' ] = true ,
[ ' medium-spitter ' ] = true ,
[ ' big-spitter ' ] = true ,
[ ' behemoth-spitter ' ] = true
}
2018-08-02 14:51:42 +02:00
local turret_evolution_factor = {
2019-01-10 16:34:51 +02:00
[ ' gun-turret ' ] = 0.001 ,
[ ' laser-turret ' ] = 0.002 ,
[ ' flamethrower-turret ' ] = 0.0015 ,
[ ' artillery-turret ' ] = 0.004
2018-08-02 14:51:42 +02:00
}
2020-04-16 13:18:08 +02:00
local spawn_worm =
Token.register (
function ( data )
local surface = data.surface
local name = data.name
local position = data.position
2018-07-20 23:01:34 +02:00
2020-04-16 13:18:08 +02:00
local p = surface.find_non_colliding_position ( name , position , 8 , 1 )
2018-07-20 23:01:34 +02:00
2020-04-16 13:18:08 +02:00
if p then
local entity = surface.create_entity ( { name = data.name , position = data.position } )
no_coin_entity [ entity.unit_number ] = true
end
2018-07-20 23:01:34 +02:00
end
2020-04-16 13:18:08 +02:00
)
2018-07-20 23:01:34 +02:00
local function get_level ( )
2018-07-28 14:04:14 +02:00
local ef = game.forces . enemy.evolution_factor
2019-01-11 17:31:32 +02:00
if ef == 0 then
return 1
else
return ceil ( ef * 4 )
end
2018-07-20 23:01:34 +02:00
end
2020-04-16 13:18:08 +02:00
local spawn_units =
Token.register (
function ( data )
local surface = data.surface
local name = data.name
local position = data.position
for _ = 1 , data.count do
local p = surface.find_non_colliding_position ( name , position , 8 , 1 )
if p then
surface.create_entity { name = name , position = p }
end
end
2018-07-20 23:01:34 +02:00
end
2020-04-16 13:18:08 +02:00
)
2018-07-20 23:01:34 +02:00
2020-04-16 13:18:08 +02:00
local spawn_player =
Token.register (
function ( player )
if player and player.valid then
player.ticks_to_respawn = 3600
end
end
)
2019-01-10 16:34:51 +02:00
2019-06-21 22:29:35 +02:00
local function has_valid_turret ( turrets )
for i = # turrets , 1 , - 1 do
local turret = turrets [ i ]
if turret.valid then
return true
else
fast_remove ( turrets , i )
end
end
return false
end
local pole_callback
2020-04-16 13:18:08 +02:00
pole_callback =
Token.register (
function ( data )
if not has_valid_turret ( data.turrets ) then
return
end
2019-06-21 22:29:35 +02:00
2020-04-16 13:18:08 +02:00
local tick = data.tick
local now = game.tick
if now >= tick then
data.surface . create_entity (
{
name = data.name ,
force = ' enemy ' ,
position = data.position
}
)
return
end
2019-06-21 22:29:35 +02:00
2020-04-16 13:18:08 +02:00
local fraction = ( ( now - tick ) / pole_respawn_time ) + 1
draw_arc (
{
color = { 1 - fraction , fraction , 0 } ,
max_radius = 0.5 ,
min_radius = 0.4 ,
start_angle = start_angle ,
angle = fraction * tau ,
target = data.position ,
surface = data.surface ,
time_to_live = time_to_live
}
)
set_timeout_in_ticks ( update_rate , pole_callback , data )
end
)
2019-06-21 22:29:35 +02:00
local filter = { area = nil , name = ' laser-turret ' , force = ' enemy ' }
local function do_pole ( entity )
2020-04-16 13:18:08 +02:00
if entity.type ~= ' electric-pole ' then
return
end
2019-06-21 22:29:35 +02:00
local supply_area_distance = entity.prototype . supply_area_distance
2020-04-16 13:18:08 +02:00
if not supply_area_distance then
return
end
2019-06-21 22:29:35 +02:00
local surface = entity.surface
local position = entity.position
local x , y = position.x , position.y
local d = supply_area_distance / 2
filter.area = { { x - d , y - d } , { x + d , y + d } }
local turrets = surface.find_entities_filtered ( filter )
2020-04-16 13:18:08 +02:00
if # turrets == 0 then
return
end
2019-06-21 22:29:35 +02:00
2020-04-16 13:18:08 +02:00
set_timeout_in_ticks (
update_rate ,
pole_callback ,
{
name = entity.name ,
position = position ,
surface = surface ,
tick = game.tick + pole_respawn_time ,
turrets = turrets
}
)
end
local function do_evolution ( entity_name , entity_force )
local factor = turret_evolution_factor [ entity_name ]
if factor then
local old = entity_force.evolution_factor
local new = old + ( 1 - old ) * factor
entity_force.evolution_factor = math.min ( new , 1 )
end
2019-06-21 22:29:35 +02:00
end
2020-04-06 22:30:47 +02:00
local bot_spawn_whitelist = {
[ ' gun-turret ' ] = true ,
[ ' laser-turret ' ] = true ,
2020-04-26 00:17:12 +02:00
[ ' flamethrower-turret ' ] = true ,
[ ' artillery-turret ' ] = true
}
local bot_cause_whitelist = {
[ ' character ' ] = true ,
[ ' artillery-turret ' ] = true ,
2020-08-18 03:34:24 +02:00
[ ' artillery-wagon ' ] = true ,
[ ' spidertron ' ] = true
2020-04-06 22:30:47 +02:00
}
2018-07-20 23:01:34 +02:00
2021-02-01 18:09:07 +02:00
-- https://en.wikipedia.org/wiki/Euclidean_distance#Two_dimensions
2021-02-01 19:06:43 +02:00
-- math.sqrt is computationally expensive so we compare to the distance squared instead
local function euclidean_distance_squared ( p , q )
local distance = ( p.x - q.x ) ^ 2 + ( p.y - q.y ) ^ 2
2021-02-01 18:09:07 +02:00
return distance
end
2021-01-29 15:05:21 +02:00
local destroyer_callback
destroyer_callback =
Token.register (
function ( data )
local entity = data.entity
local destroyer = data.destroyer
if not destroyer or not destroyer.valid or not entity or not entity.valid then
return
end
2021-02-01 19:06:43 +02:00
local distance = 10
if euclidean_distance_squared ( destroyer.position , entity.position ) < distance ^ 2 then
2021-02-01 18:09:07 +02:00
entity.surface . create_entity { name = " laser " , position = destroyer.position , target = entity , speed = 1 }
end
set_timeout_in_ticks ( 30 , destroyer_callback , data )
2021-01-29 15:05:21 +02:00
end
)
2020-04-16 13:18:08 +02:00
local function do_bot_spawn ( entity_name , entity , event )
2020-12-13 16:49:49 +02:00
-- Return if the entity killed is not on the white list
2020-12-08 10:18:31 +02:00
if not bot_spawn_whitelist [ entity_name ] then
return
end
2020-12-13 16:49:49 +02:00
-- Return if the evolution is too low
local entity_force = entity.force
local ef = entity_force.evolution_factor
2020-11-29 23:26:01 +02:00
if ef <= 0.2 then
2020-07-20 21:26:15 +02:00
return
end
2020-11-29 23:52:41 +02:00
2020-12-13 16:49:49 +02:00
local cause = event.cause
local create_entity = entity.surface . create_entity
2020-11-29 23:26:01 +02:00
local spawn_entity = {
position = entity.position ,
target = cause ,
force = entity_force
}
2020-07-20 21:26:15 +02:00
2020-12-13 16:49:49 +02:00
-- Cbeck if there is a cause for the entity's death
-- If there is no cause then the player probably picked up an artillery turret before the projectile hit the entity.
-- This causes no bots to spawn because there is no cause. Punish the player for the behaviour by sending some bots to spawn instead of their location
2020-11-29 23:26:01 +02:00
if not cause then
2020-07-20 21:26:15 +02:00
return
end
2020-12-13 16:49:49 +02:00
-- Now we have checked for no cause, check for if the cause was on the cause whitelist (players, artillery, spidertrons)
2020-11-29 23:26:01 +02:00
if not bot_cause_whitelist [ cause.name ] then
2020-04-16 13:18:08 +02:00
return
end
2019-06-21 22:29:35 +02:00
2020-04-16 13:18:08 +02:00
local repeat_cycle = 1 -- The number of times a squad of robots are spawned default must be 1
if ef > .95 then
repeat_cycle = 2
end
2019-01-10 16:34:51 +02:00
2020-04-26 01:46:04 +02:00
if cause.name ~= ' character ' then
2020-12-13 17:53:58 +02:00
if ( entity_name == ' artillery-turret ' ) then
2021-02-06 11:05:40 +02:00
repeat_cycle = 8
2020-04-26 01:46:04 +02:00
else
repeat_cycle = 4
end
2020-04-16 13:18:08 +02:00
for i = 1 , repeat_cycle do
2021-01-29 15:05:21 +02:00
if ( cause.name == ' artillery-turret ' ) then
2020-11-29 23:26:01 +02:00
spawn_entity.target = cause.position -- Overwrite target. Artillery turrets/wagons don't move so send them to entity position. Stops players from picking up the arty and the bots stopping dead.
2020-11-29 23:52:41 +02:00
spawn_entity.speed = 0.2
2021-02-02 09:33:22 +02:00
spawn_entity.max_range = 10000 -- Entities of type projectile have a default max range that's lower than late game artillery range.
2020-12-08 10:18:31 +02:00
-- This is particularly risky for players to do because defender-capsule quantities are not limited by the player force's follower robot count.
2020-11-29 23:26:01 +02:00
spawn_entity.name = ' defender-capsule ' -- use 'defender-capsule' (projectile) not 'defender' (entity) since a projectile can target a position but a capsule entity must have another entity as target
create_entity ( spawn_entity )
create_entity ( spawn_entity )
spawn_entity.name = ' destroyer-capsule '
create_entity ( spawn_entity )
2020-11-29 23:52:41 +02:00
create_entity ( spawn_entity )
2021-01-29 15:05:21 +02:00
elseif ( cause.name == ' artillery-wagon ' ) then
spawn_entity.name = ' defender '
create_entity ( spawn_entity )
create_entity ( spawn_entity )
spawn_entity.name = ' destroyer '
local destroyer = create_entity ( spawn_entity )
2021-02-01 18:09:07 +02:00
set_timeout_in_ticks ( random ( 30 , 60 ) , destroyer_callback , { destroyer = destroyer , entity = cause } )
2021-01-29 15:07:30 +02:00
destroyer = create_entity ( spawn_entity )
2021-02-01 18:09:07 +02:00
set_timeout_in_ticks ( random ( 30 , 60 ) , destroyer_callback , { destroyer = destroyer , entity = cause } )
2020-11-29 23:26:01 +02:00
else
2020-12-13 18:05:23 +02:00
spawn_entity.name = ' defender '
2020-11-29 23:26:01 +02:00
create_entity ( spawn_entity )
create_entity ( spawn_entity )
spawn_entity.name = ' destroyer '
create_entity ( spawn_entity )
2020-11-29 23:52:41 +02:00
create_entity ( spawn_entity )
end
2019-11-01 18:04:43 +02:00
end
2020-04-26 01:46:04 +02:00
elseif entity_name == ' gun-turret ' then
2020-04-16 13:18:08 +02:00
for i = 1 , repeat_cycle do
spawn_entity.name = ' defender '
create_entity ( spawn_entity )
2020-04-26 01:46:04 +02:00
create_entity ( spawn_entity )
2020-04-16 13:18:08 +02:00
spawn_entity.name = ' destroyer '
create_entity ( spawn_entity )
end
2020-04-26 01:46:04 +02:00
elseif entity_name == ' laser-turret ' then
2020-04-16 13:18:08 +02:00
for i = 1 , repeat_cycle do
2020-04-26 00:17:12 +02:00
spawn_entity.name = ' defender '
create_entity ( spawn_entity )
spawn_entity.name = ' destroyer '
create_entity ( spawn_entity )
create_entity ( spawn_entity )
end
2020-04-26 01:46:04 +02:00
else
for i = 1 , repeat_cycle do
spawn_entity.name = ' distractor-capsule '
spawn_entity.speed = 0
create_entity ( spawn_entity )
end
end
2020-04-16 13:18:08 +02:00
end
2019-11-01 18:04:43 +02:00
2021-02-08 15:32:05 +02:00
local function do_coin_drop ( entity_name , entity , cause )
2020-04-16 13:18:08 +02:00
local position = entity.position
2020-04-06 22:30:47 +02:00
local bounds = entity_drop_amount [ entity_name ]
2020-04-16 13:18:08 +02:00
if not bounds then
return
2019-01-10 16:34:51 +02:00
end
2018-08-02 14:51:42 +02:00
2020-04-16 13:18:08 +02:00
local unit_number = entity.unit_number
if no_coin_entity [ unit_number ] then
no_coin_entity [ unit_number ] = nil
return
end
local count = random ( bounds.low , bounds.high )
2021-02-09 00:53:59 +02:00
if count <= 0 then
return
end
if cause and cause.name == ' character ' then
local player = cause.player
if player and player.valid then
local coins = { name = " coin " , count = count }
if player.can_insert ( coins ) then
2021-02-13 20:55:57 +02:00
player.insert ( coins )
2021-02-09 00:53:59 +02:00
entity.surface . create_entity { name = " flying-text " , position = { position.x - 1 , position.y } , text = " + " .. count .. " [img=item.coin] " , color = { 1 , 0.8 , 0 , 0.5 } , render_player_index = player.index }
return
end
2021-02-08 15:32:05 +02:00
end
2020-04-16 13:18:08 +02:00
end
2021-02-09 00:53:59 +02:00
-- spill them on the floor
set_timeout_in_ticks (
1 ,
spill_items ,
{
count = count ,
surface = entity.surface ,
position = position
}
)
2020-04-16 13:18:08 +02:00
end
2020-04-29 05:06:41 +02:00
local function do_spawn_entity ( entity_name , entity , event )
2020-04-06 22:30:47 +02:00
local spawn = entity_spawn_map [ entity_name ]
2020-04-16 13:18:08 +02:00
if not spawn then
return
end
2020-04-06 22:30:47 +02:00
local chance = spawn.chance
2020-04-16 13:18:08 +02:00
if chance ~= 1 and random ( ) > chance then
return
end
2020-04-06 22:30:47 +02:00
local name = spawn.name
if name == nil then
local type = spawn.type
if type == ' cause ' then
local cause = event.cause
2020-04-16 13:18:08 +02:00
if not cause then
return
end
2020-04-06 22:30:47 +02:00
name = cause.name
2020-04-16 13:18:08 +02:00
if not allowed_cause_source [ cause.name ] then
return
end
2020-04-06 22:30:47 +02:00
elseif type == ' compound ' then
local spawns = spawn.spawns
spawn = spawns [ random ( # spawns ) ]
name = spawn.name
else
name = unit_levels [ type ] [ get_level ( ) ]
end
2018-07-20 23:01:34 +02:00
end
2020-04-06 22:30:47 +02:00
2020-04-16 13:18:08 +02:00
local position = entity.position
2020-04-06 22:30:47 +02:00
if worms [ name ] then
2020-04-16 13:18:08 +02:00
set_timeout_in_ticks (
5 ,
spawn_worm ,
{
surface = entity.surface ,
name = name ,
position = position
}
)
2020-04-06 22:30:47 +02:00
else
2020-04-16 13:18:08 +02:00
set_timeout_in_ticks (
5 ,
spawn_units ,
{
surface = entity.surface ,
name = name ,
position = position ,
count = spawn.count
}
)
2020-04-06 22:30:47 +02:00
end
2020-04-16 13:18:08 +02:00
end
Event.add (
defines.events . on_entity_died ,
function ( event )
local entity = event.entity
if not entity or not entity.valid then
return
end
local entity_force = entity.force
local entity_name = entity.name
2021-02-08 15:32:05 +02:00
local cause = event.cause
2020-04-06 22:30:47 +02:00
2020-04-16 13:18:08 +02:00
if entity_force.name == ' enemy ' then
do_pole ( entity )
do_evolution ( entity_name , entity_force )
2021-02-08 15:32:05 +02:00
do_coin_drop ( entity_name , entity , cause )
2020-04-16 13:18:08 +02:00
do_bot_spawn ( entity_name , entity , event )
end
2020-04-29 05:06:41 +02:00
do_spawn_entity ( entity_name , entity , event )
2020-04-16 13:18:08 +02:00
end
)
2020-04-06 22:30:47 +02:00
2020-04-16 13:18:08 +02:00
Event.add (
defines.events . on_player_died ,
function ( event )
local player = game.get_player ( event.player_index )
set_timeout_in_ticks ( 1 , spawn_player , player )
end
2021-01-29 15:05:21 +02:00
)