mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2024-12-30 23:17:53 +02:00
5620534ad6
Delay the removal of given entity so other functions can check if the given entity was previously tracked.
571 lines
17 KiB
Lua
571 lines
17 KiB
Lua
-- Biters, Spawners and Worms gain additional health / resistance -- mewmew
|
|
-- modified by Gerkiz
|
|
-- Use this.biter_health_boost or this.biter_health_boost_forces to modify their health.
|
|
-- 1 = vanilla health, 2 = 200% vanilla health
|
|
-- do not use values below 1
|
|
|
|
local Event = require 'utils.event'
|
|
local LootDrop = require 'modules.mobs_drop_loot'
|
|
local WD = require 'modules.wave_defense.table'
|
|
local Global = require 'utils.global'
|
|
local Task = require 'utils.task'
|
|
local Token = require 'utils.token'
|
|
|
|
local floor = math.floor
|
|
local insert = table.insert
|
|
local random = math.random
|
|
local sqrt = math.sqrt
|
|
local round = math.round
|
|
local Public = {}
|
|
|
|
local this = {
|
|
biter_health_boost = 1,
|
|
biter_health_boost_forced = false,
|
|
biter_health_boost_forces = {},
|
|
biter_health_boost_units = {},
|
|
biter_health_boost_count = 0,
|
|
make_normal_unit_mini_bosses = false,
|
|
active_surface = 'nauvis',
|
|
active_surfaces = {},
|
|
acid_lines_delay = {},
|
|
acid_nova = false,
|
|
boss_spawns_projectiles = false,
|
|
enable_boss_loot = false,
|
|
randomize_stun_and_slowdown_sticker = false
|
|
}
|
|
|
|
local radius = 6
|
|
local targets = {}
|
|
local acid_splashes = {
|
|
['big-biter'] = 'acid-stream-worm-medium',
|
|
['behemoth-biter'] = 'acid-stream-worm-big'
|
|
}
|
|
local acid_lines = {
|
|
['small-spitter'] = 'acid-stream-spitter-small',
|
|
['medium-spitter'] = 'acid-stream-spitter-medium',
|
|
['big-spitter'] = 'acid-stream-spitter-big',
|
|
['behemoth-spitter'] = 'acid-stream-spitter-big'
|
|
}
|
|
for x = radius * -1, radius, 1 do
|
|
for y = radius * -1, radius, 1 do
|
|
if sqrt(x ^ 2 + y ^ 2) <= radius then
|
|
targets[#targets + 1] = {x = x, y = y}
|
|
end
|
|
end
|
|
end
|
|
|
|
local projectiles = {
|
|
'slowdown-capsule',
|
|
'defender-capsule',
|
|
'destroyer-capsule',
|
|
'laser',
|
|
'distractor-capsule',
|
|
'rocket',
|
|
'explosive-rocket',
|
|
'grenade',
|
|
'rocket',
|
|
'grenade'
|
|
}
|
|
|
|
Global.register(
|
|
this,
|
|
function(t)
|
|
this = t
|
|
end
|
|
)
|
|
|
|
local filters = {
|
|
{filter = 'name', name = 'unit'},
|
|
{filter = 'name', name = 'turret'},
|
|
{filter = 'name', name = 'ammo-turret'},
|
|
{filter = 'name', name = 'electric-turret'},
|
|
{filter = 'name', name = 'unit-spawner'}
|
|
}
|
|
|
|
local entity_types = {
|
|
['unit'] = true,
|
|
['turret'] = true,
|
|
['ammo-turret'] = true,
|
|
['electric-turret'] = true,
|
|
['unit-spawner'] = true
|
|
}
|
|
|
|
local remove_unit_token =
|
|
Token.register(
|
|
function(data)
|
|
local unit_number = data.unit_number
|
|
if not unit_number then
|
|
return
|
|
end
|
|
|
|
this.biter_health_boost_units[unit_number] = nil
|
|
end
|
|
)
|
|
|
|
local function loaded_biters(event)
|
|
local cause = event.cause
|
|
local entity = event.entity
|
|
if not entity or not entity.valid then
|
|
return
|
|
end
|
|
|
|
local position = false
|
|
if cause then
|
|
if cause.valid then
|
|
position = cause.position
|
|
end
|
|
end
|
|
if not position then
|
|
position = {entity.position.x + (-20 + random(0, 40)), entity.position.y + (-20 + random(0, 40))}
|
|
end
|
|
|
|
entity.surface.create_entity(
|
|
{
|
|
name = projectiles[random(1, 10)],
|
|
position = entity.position,
|
|
force = 'enemy',
|
|
source = entity.position,
|
|
target = position,
|
|
max_range = 16,
|
|
speed = 0.01
|
|
}
|
|
)
|
|
end
|
|
|
|
local function acid_nova(event)
|
|
for _ = 1, random(20, 40) do
|
|
local i = random(1, #targets)
|
|
event.entity.surface.create_entity(
|
|
{
|
|
name = acid_splashes[event.entity.name],
|
|
position = event.entity.position,
|
|
force = event.entity.force.name,
|
|
source = event.entity.position,
|
|
target = {x = event.entity.position.x + targets[i].x, y = event.entity.position.y + targets[i].y},
|
|
max_range = radius,
|
|
speed = 0.001
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
local function create_entity_radius(surface, name, source, target)
|
|
local distance = sqrt((source.x - target.x) ^ 2 + (source.y - target.y) ^ 2)
|
|
local modifier = {(target.x - source.x) / distance, (target.y - source.y) / distance}
|
|
|
|
local position = {source.x, source.y}
|
|
|
|
for _ = 1, distance * 1.5, 1 do
|
|
if random(1, 2) ~= 1 then
|
|
surface.create_entity(
|
|
{
|
|
name = name,
|
|
position = source,
|
|
force = 'enemy',
|
|
source = source,
|
|
target = position,
|
|
max_range = 25,
|
|
speed = 1
|
|
}
|
|
)
|
|
end
|
|
position = {position[1] + modifier[1], position[2] + modifier[2]}
|
|
end
|
|
end
|
|
|
|
local function clean_table()
|
|
local units_to_delete = {}
|
|
|
|
--Mark all health boost entries for deletion
|
|
for key, _ in pairs(this.biter_health_boost_units) do
|
|
units_to_delete[key] = true
|
|
end
|
|
|
|
--Remove valid health boost entries from deletion
|
|
local validTypes = {}
|
|
for k, v in pairs(entity_types) do
|
|
if v then
|
|
insert(validTypes, k)
|
|
end
|
|
end
|
|
|
|
for name, enabled in pairs(this.active_surfaces) do
|
|
local surface = game.surfaces[name]
|
|
if surface and surface.valid and enabled then
|
|
for _, unit in pairs(surface.find_entities_filtered({type = validTypes})) do
|
|
units_to_delete[unit.unit_number] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local surface = game.surfaces[this.active_surface]
|
|
if not (surface and surface.valid) then
|
|
return
|
|
end
|
|
|
|
for _, unit in pairs(surface.find_entities_filtered({type = validTypes})) do
|
|
units_to_delete[unit.unit_number] = nil
|
|
end
|
|
|
|
--Remove abandoned health boost entries
|
|
for key, _ in pairs(units_to_delete) do
|
|
this.biter_health_boost_units[key] = nil
|
|
end
|
|
end
|
|
|
|
local function check_clear_table()
|
|
this.biter_health_boost_count = this.biter_health_boost_count + 1
|
|
if this.biter_health_boost_count >= 500 then
|
|
clean_table()
|
|
this.biter_health_boost_count = 0
|
|
end
|
|
end
|
|
|
|
local function create_boss_healthbar(entity, size)
|
|
return rendering.draw_sprite(
|
|
{
|
|
sprite = 'virtual-signal/signal-white',
|
|
tint = {0, 200, 0},
|
|
x_scale = size * 15,
|
|
y_scale = size,
|
|
render_layer = 'light-effect',
|
|
target = entity,
|
|
target_offset = {0, -2.5},
|
|
surface = entity.surface
|
|
}
|
|
)
|
|
end
|
|
|
|
local function set_boss_healthbar(health, max_health, healthbar_id)
|
|
local m = health / max_health
|
|
if m < 0 then
|
|
return
|
|
end
|
|
local x_scale = rendering.get_y_scale(healthbar_id) * 15
|
|
rendering.set_x_scale(healthbar_id, x_scale * m)
|
|
rendering.set_color(healthbar_id, {floor(255 - 255 * m), floor(200 * m), 0})
|
|
end
|
|
|
|
local function extra_projectiles(cause, target)
|
|
if not cause or not cause.valid then
|
|
return
|
|
end
|
|
local biter_health_boost_units = this.biter_health_boost_units
|
|
local cause_unit_number = cause.unit_number
|
|
local cause_health_pool = biter_health_boost_units[cause_unit_number]
|
|
if cause_health_pool and cause_health_pool[3] and cause_health_pool[3].healthbar_id and entity_types[cause.type] then
|
|
if this.acid_nova then
|
|
if acid_lines[cause.name] then
|
|
if not this.acid_lines_delay[cause_unit_number] then
|
|
this.acid_lines_delay[cause_unit_number] = 0
|
|
end
|
|
if this.acid_lines_delay[cause_unit_number] < game.tick then
|
|
create_entity_radius(cause.surface, acid_lines[cause.name], cause.position, target.position)
|
|
this.acid_lines_delay[cause_unit_number] = game.tick + 180
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_entity_damaged(event)
|
|
local biter = event.entity
|
|
if not (biter and biter.valid) then
|
|
return
|
|
end
|
|
local surface = biter.surface
|
|
local cause = event.cause
|
|
|
|
local biter_health_boost_units = this.biter_health_boost_units
|
|
|
|
local unit_number = biter.unit_number
|
|
|
|
local damage = event.final_damage_amount
|
|
|
|
--Create new health pool
|
|
local health_pool = biter_health_boost_units[unit_number]
|
|
extra_projectiles(cause, biter)
|
|
|
|
if not entity_types[biter.type] then
|
|
return
|
|
end
|
|
|
|
if this.randomize_stun_and_slowdown_sticker then
|
|
local damage_type = event.damage_type
|
|
if damage_type and damage_type.name == 'electric' then
|
|
local stickers = biter.stickers
|
|
if stickers and #stickers > 0 then
|
|
for i = 1, #stickers, 1 do
|
|
if random(1, 4) == 1 then -- there's a % that biters can recover from stun and get slowed instead.
|
|
if stickers[i].sticked_to == biter then
|
|
if stickers[i].name == 'stun-sticker' then
|
|
stickers[i].destroy()
|
|
if random(1, 2) == 1 then
|
|
local slow = surface.create_entity {name = 'slowdown-sticker', position = biter.position, target = biter}
|
|
slow.time_to_live = 200
|
|
end
|
|
break
|
|
elseif stickers[i].name == 'slowdown-sticker' then
|
|
stickers[i].destroy()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not health_pool and this.make_normal_unit_mini_bosses then
|
|
if this.biter_health_boost_forces[biter.force.index] then
|
|
Public.add_unit(biter, this.biter_health_boost_forces[biter.force.index])
|
|
else
|
|
Public.add_unit(biter, this.biter_health_boost)
|
|
end
|
|
health_pool = this.biter_health_boost_units[unit_number]
|
|
end
|
|
|
|
if not health_pool then
|
|
return
|
|
end
|
|
|
|
--Process boss unit health bars
|
|
local boss = health_pool[3]
|
|
if boss and boss.healthbar_id then
|
|
set_boss_healthbar(health_pool[1], boss.max_health, boss.healthbar_id)
|
|
end
|
|
|
|
--Reduce health pool
|
|
health_pool[1] = round(health_pool[1] - damage)
|
|
|
|
--Set entity health relative to health pool
|
|
local max_health = health_pool[3].max_health
|
|
local m = health_pool[1] / max_health
|
|
local final_health = round(biter.prototype.max_health * m)
|
|
biter.health = final_health
|
|
|
|
--Proceed to kill entity if health is 0
|
|
if biter.health > 0 and health_pool[1] > 0 then
|
|
return
|
|
end
|
|
|
|
if cause then
|
|
if cause.valid then
|
|
event.entity.die(cause.force, cause)
|
|
return
|
|
end
|
|
end
|
|
biter.die(biter.force)
|
|
end
|
|
|
|
local function on_entity_died(event)
|
|
if not this.check_on_entity_died then
|
|
return
|
|
end
|
|
|
|
local biter = event.entity
|
|
if not (biter and biter.valid) then
|
|
return
|
|
end
|
|
if not entity_types[biter.type] then
|
|
return
|
|
end
|
|
|
|
local biter_health_boost_units = this.biter_health_boost_units
|
|
|
|
local unit_number = biter.unit_number
|
|
local health_pool = biter_health_boost_units[unit_number]
|
|
local wave_count = WD.get_wave()
|
|
|
|
if health_pool then
|
|
Task.set_timeout_in_ticks(30, remove_unit_token, {unit_number = unit_number})
|
|
if health_pool[3] and health_pool[3].healthbar_id then
|
|
if this.enable_boss_loot then
|
|
if random(1, 128) == 1 then
|
|
LootDrop.drop_loot(biter, wave_count)
|
|
end
|
|
end
|
|
if this.boss_spawns_projectiles then
|
|
if random(1, 32) == 1 then
|
|
loaded_biters(event)
|
|
end
|
|
end
|
|
if this.acid_nova then
|
|
if acid_splashes[biter.name] then
|
|
acid_nova(event)
|
|
end
|
|
if this.acid_lines_delay[biter.unit_number] then
|
|
this.acid_lines_delay[biter.unit_number] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Use this function to retrieve a key from the global table.
|
|
---@param key string
|
|
function Public.get(key)
|
|
if key then
|
|
return this[key]
|
|
else
|
|
return this
|
|
end
|
|
end
|
|
|
|
--- Using this function can set a new value to an exist key or create a new key with value
|
|
---@param key string
|
|
---@param value any
|
|
function Public.set(key, value)
|
|
if key and (value or value == false) then
|
|
this[key] = value
|
|
return this[key]
|
|
elseif key then
|
|
return this[key]
|
|
else
|
|
return this
|
|
end
|
|
end
|
|
|
|
--- Use this function to reset the global table to it's init values.
|
|
function Public.reset_table()
|
|
this.biter_health_boost = 1
|
|
this.biter_health_boost_forces = false
|
|
this.biter_health_boost_forces = {}
|
|
this.biter_health_boost_units = {}
|
|
this.biter_health_boost_count = 0
|
|
this.make_normal_unit_mini_bosses = false
|
|
this.active_surface = 'nauvis'
|
|
this.active_surfaces = {}
|
|
this.check_on_entity_died = false
|
|
this.acid_lines_delay = {}
|
|
this.acid_nova = false
|
|
this.boss_spawns_projectiles = false
|
|
this.enable_boss_loot = false
|
|
end
|
|
|
|
--- Use this function to add a new unit that has extra health
|
|
---@param unit userdata
|
|
---@param health_multiplier number
|
|
function Public.add_unit(unit, health_multiplier)
|
|
if not health_multiplier then
|
|
health_multiplier = this.biter_health_boost
|
|
end
|
|
local health = floor(unit.prototype.max_health * health_multiplier)
|
|
local xp_modifier = round(1 / health_multiplier, 5)
|
|
this.biter_health_boost_units[unit.unit_number] = {
|
|
health,
|
|
xp_modifier,
|
|
{max_health = health}
|
|
}
|
|
|
|
check_clear_table()
|
|
end
|
|
|
|
--- Use this function to add a new boss unit (with healthbar)
|
|
---@param unit userdata
|
|
---@param health_multiplier number
|
|
---@param health_bar_size number|nil
|
|
function Public.add_boss_unit(unit, health_multiplier, health_bar_size)
|
|
if not health_multiplier then
|
|
health_multiplier = this.biter_health_boost
|
|
end
|
|
if not health_bar_size then
|
|
health_bar_size = 0.5
|
|
end
|
|
local xp_modifier = round(1 / health_multiplier, 5)
|
|
local health = floor(unit.prototype.max_health * health_multiplier)
|
|
this.biter_health_boost_units[unit.unit_number] = {
|
|
health,
|
|
xp_modifier,
|
|
{max_health = health, healthbar_id = create_boss_healthbar(unit, health_bar_size)}
|
|
}
|
|
|
|
check_clear_table()
|
|
end
|
|
|
|
--- This sets the active surface that we check and have the script active.
|
|
--- This deletes the list of surfaces if we use multiple, so use it only before setting more of them.
|
|
---@param str string
|
|
function Public.set_active_surface(str)
|
|
if str and type(str) == 'string' then
|
|
this.active_surfaces = {}
|
|
this.active_surface = str or 'nauvis'
|
|
end
|
|
return this.active_surface
|
|
end
|
|
|
|
--- This sets if this surface is active, when we using multiple surfaces. The default active surface does not need to be added again
|
|
---@param name string
|
|
---@param value boolean
|
|
function Public.set_surface_activity(name, value)
|
|
if name and type(name) == 'string' and type(value) == 'boolean' then
|
|
this.active_surfaces[name] = value
|
|
end
|
|
return this.active_surfaces
|
|
end
|
|
|
|
--- Enables that biter bosses (units with health bars) spawns acid on death.
|
|
---@param boolean
|
|
function Public.acid_nova(boolean)
|
|
this.acid_nova = boolean or false
|
|
return this.acid_nova
|
|
end
|
|
|
|
--- Enables that we clear units from the global table when a unit dies.
|
|
---@param boolean
|
|
function Public.check_on_entity_died(boolean)
|
|
this.check_on_entity_died = boolean or false
|
|
return this.check_on_entity_died
|
|
end
|
|
|
|
--- Enables that biter bosses (units with health bars) spawns projectiles on death.
|
|
---@param boolean
|
|
function Public.boss_spawns_projectiles(boolean)
|
|
this.boss_spawns_projectiles = boolean or false
|
|
|
|
return this.boss_spawns_projectiles
|
|
end
|
|
|
|
--- Enables that biter bosses (units with health bars) drops loot.
|
|
---@param boolean
|
|
function Public.enable_boss_loot(boolean)
|
|
this.enable_boss_loot = boolean or false
|
|
|
|
return this.enable_boss_loot
|
|
end
|
|
|
|
--- Forces a value of biter_health_boost
|
|
---@param boolean
|
|
function Public.enable_biter_health_boost_forced(boolean)
|
|
this.biter_health_boost_forced = boolean or false
|
|
|
|
return this.biter_health_boost_forced
|
|
end
|
|
|
|
--- Enables that normal units have boosted health.
|
|
---@param boolean
|
|
function Public.enable_make_normal_unit_mini_bosses(boolean)
|
|
this.make_normal_unit_mini_bosses = boolean or false
|
|
|
|
return this.make_normal_unit_mini_bosses
|
|
end
|
|
|
|
--- Enables that enemies can recover from stun randomly.
|
|
---@param boolean
|
|
function Public.enable_randomize_stun_and_slowdown_sticker(boolean)
|
|
this.randomize_stun_and_slowdown_sticker = boolean or false
|
|
|
|
return this.randomize_stun_and_slowdown_sticker
|
|
end
|
|
|
|
Event.on_init(
|
|
function()
|
|
Public.reset_table()
|
|
end
|
|
)
|
|
|
|
Event.add(defines.events.on_entity_damaged, on_entity_damaged, filters)
|
|
Event.on_nth_tick(7200, check_clear_table)
|
|
Event.add(defines.events.on_entity_died, on_entity_died, filters)
|
|
|
|
return Public
|