1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2024-12-30 23:17:53 +02:00
ComfyFactorio/modules/biter_health_booster_v2.lua
Gerkiz 5620534ad6 Health boost module
Delay the removal of given entity so other functions can check if the given entity was previously tracked.
2023-10-04 23:28:32 +02:00

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