Files
Bubberstation/code/datums/components/spawner.dm
ArcaneMusic 659c925495 Ore vents now have countermeasures against walling them off from all sides. (#83295)
## About The Pull Request

This PR fixes some balance and practicality issues with the spawner
component that has affected vent mining and the associated wave defense.

* The turf_peel() proc now checks to see if it's pulling no turf from
the inside or outside of it's peel, in which case it now has a default
case where it returns it's center turf instead.
* As a consequence of this, the center turf is where mobs will spawn if
an ore vent is unable to find any space where it can spawn any new
hostile mobs.

Upon testing this, it worked fairly well, but ultimately node drones
were capable of tanking enough hits for long enough that typically they
could still survive a small vent's onslaught. As a precaution, I've made
two additional changes.

* Node drones have had their maximum health dropped slightly, from 500
health to 300 health.
* As a secondary precaution, if a spawner using the turf peel method
cannot spawn correctly, it will send a signal, which ore vents are now
registered to. When called on an ore vent, it has new behavior to clear
the offending nearby turfs and create a pathway to allow nearby mobs to
get access to the vent.
* (**This is an explosion**.)

## Why It's Good For The Game

Fixes an unreported on the repo but repeatedly pinged issue regarding
ore vent waves where players could often wall off or blockoff an ore
vent in such a way that it allows vents to be functionally immortal by
quickly walling off the vent using sandstone doors. This should help to
prevent players cheesing the intended gameplay mechanic, as well as keep
up the challenge to arcmining wave defense without some additional
nuance.

I may have gone a little overboard with the health tweaks as well, but
considering that even with the explosions, I was able to survive the
repeated explosions on the vent, I think this should work quite well all
things considered. Still, open to feedback there.

## Changelog

🆑
balance: Ore vents, if blocked off from all four sides while being
defended, now cause a mild gas explosion, resulting in a mild dissuasive
explosion.
fix: NODE drones spawned from ore vent defense have lower maximum
health.
/🆑
2024-05-20 09:23:09 +02:00

124 lines
4.5 KiB
Plaintext

/datum/component/spawner
/// Time to wait between spawns
var/spawn_time
/// Maximum number of atoms we can have active at one time
var/max_spawned
/// Visible message to show when something spawns
var/spawn_text
/// List of atom types to spawn, picked randomly
var/list/spawn_types
/// Faction to grant to mobs (only applies to mobs)
var/list/faction
/// List of weak references to things we have already created
var/list/spawned_things = list()
/// How many mobs can we spawn maximum each time we try to spawn? (1 - max)
var/max_spawn_per_attempt
/// Distance from the spawner to spawn mobs
var/spawn_distance
/// Distance from the spawner to exclude mobs from spawning
var/spawn_distance_exclude
COOLDOWN_DECLARE(spawn_delay)
/datum/component/spawner/Initialize(spawn_types = list(), spawn_time = 30 SECONDS, max_spawned = 5, max_spawn_per_attempt = 1 , faction = list(FACTION_MINING), spawn_text = null, spawn_distance = 1, spawn_distance_exclude = 0)
if (!islist(spawn_types))
CRASH("invalid spawn_types to spawn specified for spawner component!")
src.spawn_time = spawn_time
src.spawn_types = spawn_types
src.faction = faction
src.spawn_text = spawn_text
src.max_spawned = max_spawned
src.max_spawn_per_attempt = max_spawn_per_attempt
src.spawn_distance = spawn_distance
src.spawn_distance_exclude = spawn_distance_exclude
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(stop_spawning))
RegisterSignal(parent, COMSIG_VENT_WAVE_CONCLUDED, PROC_REF(stop_spawning))
START_PROCESSING((spawn_time < 2 SECONDS ? SSfastprocess : SSprocessing), src)
/datum/component/spawner/process()
try_spawn_mob()
/// Stop spawning mobs
/datum/component/spawner/proc/stop_spawning(force)
SIGNAL_HANDLER
STOP_PROCESSING(SSprocessing, src)
spawned_things = list()
/// Try to create a new mob
/datum/component/spawner/proc/try_spawn_mob()
if(!length(spawn_types))
return
if(!COOLDOWN_FINISHED(src, spawn_delay))
return
validate_references()
var/spawned_total = length(spawned_things)
if(spawned_total >= max_spawned)
return
var/atom/spawner = parent
COOLDOWN_START(src, spawn_delay, spawn_time)
var/chosen_mob_type = pick(spawn_types)
var/adjusted_spawn_count = 1
var/max_spawn_this_attempt = min(max_spawn_per_attempt, max_spawned - spawned_total)
if (max_spawn_this_attempt > 1)
adjusted_spawn_count = rand(1, max_spawn_this_attempt)
for(var/i in 1 to adjusted_spawn_count)
var/atom/created
var/turf/picked_spot
if(spawn_distance == 1)
created = new chosen_mob_type(spawner.loc)
else if(spawn_distance >= 1 && spawn_distance_exclude >= 1)
picked_spot = pick(turf_peel(spawn_distance, spawn_distance_exclude, spawner.loc, view_based = TRUE))
if(!picked_spot)
picked_spot = pick(circle_range_turfs(spawner.loc, spawn_distance))
if(picked_spot == spawner.loc)
SEND_SIGNAL(spawner, COMSIG_SPAWNER_SPAWNED_DEFAULT)
created = new chosen_mob_type(picked_spot)
else if (spawn_distance >= 1)
picked_spot = pick(circle_range_turfs(spawner.loc, spawn_distance))
created = new chosen_mob_type(picked_spot)
created.flags_1 |= (spawner.flags_1 & ADMIN_SPAWNED_1)
spawned_things += WEAKREF(created)
if (isliving(created))
var/mob/living/created_mob = created
created_mob.faction = src.faction
RegisterSignal(created, COMSIG_MOB_STATCHANGE, PROC_REF(mob_stat_changed))
SEND_SIGNAL(src, COMSIG_SPAWNER_SPAWNED, created)
RegisterSignal(created, COMSIG_QDELETING, PROC_REF(on_deleted))
if (spawn_text)
spawner.visible_message(span_danger("A creature [spawn_text] [spawner]."))
/// Remove weakrefs to atoms which have been killed or deleted without us picking it up somehow
/datum/component/spawner/proc/validate_references()
for (var/datum/weakref/weak_thing as anything in spawned_things)
var/atom/previously_spawned = weak_thing?.resolve()
if (!previously_spawned)
spawned_things -= weak_thing
continue
if (!isliving(previously_spawned))
continue
var/mob/living/spawned_mob = previously_spawned
if (spawned_mob.stat != DEAD)
continue
spawned_things -= weak_thing
/// Called when an atom we spawned is deleted, remove it from the list
/datum/component/spawner/proc/on_deleted(atom/source)
SIGNAL_HANDLER
spawned_things -= WEAKREF(source)
/// Called when a mob we spawned dies, remove it from the list and unregister signals
/datum/component/spawner/proc/mob_stat_changed(mob/living/source)
if (source.stat != DEAD)
return
spawned_things -= WEAKREF(source)
UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_MOB_STATCHANGE))