Files
Bubberstation/code/datums/components/spawner.dm
Jacquerel 0ae46755b5 Mobs spawners won't spawn more mobs than they are supposed to (#83266)
## About The Pull Request

At some point we added a new parameter to spawners which randomised the
number of mobs they could spawn up to a maximum.
For some reason the default value here was set to "2", meaning that
every mob spawner in the game would be spawning an average of 1.5 mobs
per update instead of 1.
As the place it was _intended_ for (mining vents) already provides a
value in the constructor, I just set the default back to 1.

Additionally, this randomised value did not actually obey the "maximum
mobs" parameter of the component and could spawn mobs above what was
supposed to be the cap of mobs created by the spawner.

This PR fixes those things.

## Changelog

🆑
fix: Mob spawners (such as lavaland tendrils) won't spawn more mobs than
they are supposed to, faster than they should.
/🆑
2024-05-17 16:26:37 -06:00

122 lines
4.4 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))
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))