diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index 7cc18937127..cd0eb754305 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -1,25 +1,27 @@ /datum/component/spawner - var/mob_types = list(/mob/living/basic/carp) - var/spawn_time = 300 //30 seconds default + /// Time to wait between spawns + var/spawn_time + /// Maximum number of mobs we can have active at one time + var/max_mobs + /// Visible message to show when a mob spawns + var/spawn_text + /// List of mob types to spawn, picked randomly + var/list/mob_types + /// Faction to grant to mobs + var/list/faction + /// List of weak references to mobs we have already created var/list/spawned_mobs = list() - var/spawn_delay = 0 - var/max_mobs = 5 - var/spawn_text = "emerges from" - var/list/faction = list("mining") + /// Time until we next spawn + COOLDOWN_DECLARE(spawn_delay) - - -/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs) - if(_spawn_time) - spawn_time=_spawn_time - if(_mob_types) - mob_types=_mob_types - if(_faction) - faction=_faction - if(_spawn_text) - spawn_text=_spawn_text - if(_max_mobs) - max_mobs=_max_mobs +/datum/component/spawner/Initialize(mob_types = list(), spawn_time = 30 SECONDS, max_mobs = 5, faction = list("mining"), spawn_text = "emerges from") + if (!length(mob_types)) + CRASH("No types of mob to spawn specified for spawner component!") + src.spawn_time = spawn_time + src.mob_types = mob_types + src.faction = faction + src.spawn_text = spawn_text + src.max_mobs = max_mobs RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(stop_spawning)) START_PROCESSING(SSprocessing, src) @@ -27,27 +29,49 @@ /datum/component/spawner/process() try_spawn_mob() - +/// Stop spawning mobs /datum/component/spawner/proc/stop_spawning(force) SIGNAL_HANDLER STOP_PROCESSING(SSprocessing, src) - for(var/mob/living/simple_animal/L in spawned_mobs) - if(L.nest == src) - L.nest = null - spawned_mobs = null + spawned_mobs = list() +/// Try to create a new mob /datum/component/spawner/proc/try_spawn_mob() - var/atom/P = parent - if(spawned_mobs.len >= max_mobs) + if(!COOLDOWN_FINISHED(src, spawn_delay)) return - if(spawn_delay > world.time) + validate_references() + if(length(spawned_mobs) >= max_mobs) return - spawn_delay = world.time + spawn_time + var/atom/spawner = parent + COOLDOWN_START(src, spawn_delay, spawn_time) + var/chosen_mob_type = pick(mob_types) - var/mob/living/simple_animal/L = new chosen_mob_type(P.loc) - L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1) - spawned_mobs += L - L.nest = src - L.faction = src.faction - P.visible_message(span_danger("[L] [spawn_text] [P].")) + var/mob/living/created = new chosen_mob_type(spawner.loc) + created.flags_1 |= (spawner.flags_1 & ADMIN_SPAWNED_1) + spawned_mobs += WEAKREF(created) + created.faction = src.faction + spawner.visible_message(span_danger("[created] [spawn_text] [spawner].")) + + RegisterSignal(created, COMSIG_PARENT_QDELETING, PROC_REF(mob_deleted)) + RegisterSignal(created, COMSIG_MOB_STATCHANGE, PROC_REF(mob_stat_changed)) + +/// Remove weakrefs to mobs which have been killed or deleted without us picking it up somehow +/datum/component/spawner/proc/validate_references() + for (var/datum/weakref/weak_mob as anything in spawned_mobs) + var/mob/living/previously_spawned = weak_mob.resolve() + if (previously_spawned && previously_spawned.stat != DEAD) + continue + spawned_mobs -= weak_mob + +/// Called when a mob we spawned is deleted, remove it from the list +/datum/component/spawner/proc/mob_deleted(mob/living/source) + SIGNAL_HANDLER + spawned_mobs -= 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_mobs -= WEAKREF(source) + UnregisterSignal(source, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_STATCHANGE)) diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index a505952666b..64c58d2421f 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -17,7 +17,7 @@ /obj/structure/spawner/Initialize(mapload) . = ..() - AddComponent(spawner_type, mob_types, spawn_time, faction, spawn_text, max_mobs) + AddComponent(spawner_type, mob_types, spawn_time, max_mobs, faction, spawn_text) /obj/structure/spawner/attack_animal(mob/living/simple_animal/user, list/modifiers) if(faction_check(faction, user.faction, FALSE) && !user.client) diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm index 08332d8f529..32ce7717b94 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm @@ -26,8 +26,6 @@ lighting_cutoff_blue = 40 ai_controller = /datum/ai_controller/basic_controller/blankbody - /// Used for mobs that get spawned in a spawner appearently. - var/datum/component/spawner/nest /mob/living/basic/blankbody/Initialize(mapload) . = ..() @@ -61,12 +59,6 @@ melee_damage_lower = 10 melee_damage_upper = 20 -/mob/living/basic/blankbody/Destroy() - if(nest) - nest.spawned_mobs -= src - nest = null - return ..() - /datum/ai_controller/basic_controller/blankbody blackboard = list( BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm index 3e1271c415a..568f92703ea 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm @@ -21,7 +21,7 @@ unsuitable_atmos_damage = 0 unsuitable_cold_damage = 0 unsuitable_heat_damage = 0 - // Green and blue, bit dim cause yaknow morphlike + // Green and blue, bit dim cause yaknow morphlike lighting_cutoff_red = 5 lighting_cutoff_green = 25 lighting_cutoff_blue = 15 @@ -29,8 +29,6 @@ ai_controller = /datum/ai_controller/basic_controller/creature /// Used for checking if the mob is phased or not. var/is_phased = FALSE - /// Used for mobs that get spawned in a spawner appearently. - var/datum/component/spawner/nest /mob/living/basic/creature/Initialize(mapload) . = ..() @@ -123,12 +121,6 @@ holder = new /obj/effect/dummy/phased_mob(owner_turf, owner_mob) owner_mob.is_phased = TRUE -/mob/living/basic/creature/Destroy() - if(nest) - nest.spawned_mobs -= src - nest = null - return ..() - /datum/ai_controller/basic_controller/creature blackboard = list( BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm index ca642350431..651ff8bdada 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm @@ -31,8 +31,6 @@ var/static/list/migo_sounds /// Odds migo will dodge var/dodge_prob = 10 - /// Used for mobs that get spawned in a spawner appearently. - var/datum/component/spawner/nest /mob/living/basic/migo/Initialize(mapload) . = ..() @@ -96,12 +94,6 @@ if(!.)//Can't dodge there so we just carry on . = Move(moving_to, move_direction) -/mob/living/basic/migo/Destroy() - if(nest) - nest.spawned_mobs -= src - nest = null - return ..() - /datum/ai_controller/basic_controller/migo blackboard = list( BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm index c87dc31e3bf..1320c5e4bb9 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm @@ -46,8 +46,6 @@ var/recovery_time = 0 /// If this is a megafauna that is real (has achievements, gps signal) var/true_spawn = TRUE - /// Range the megafauna can move from their nest (if they have one - var/nest_range = 10 /// The chosen attack by the megafauna var/chosen_attack = 1 /// Attack actions, sets chosen_attack to the number in the action @@ -74,13 +72,6 @@ //Safety check if(!loc) return ..() - if(nest && nest.parent && get_dist(nest.parent, src) > nest_range) - var/turf/closest = get_turf(nest.parent) - for(var/i = 1 to nest_range) - closest = get_step(closest, get_dir(closest, src)) - forceMove(closest) // someone teleported out probably and the megafauna kept chasing them - LoseTarget() - return return ..() /mob/living/simple_animal/hostile/megafauna/death(gibbed, list/force_grant) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm index 15741c8d38f..c5d6a0d4486 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm @@ -282,7 +282,14 @@ /mob/living/simple_animal/hostile/big_legion/Initialize(mapload) .=..() - AddComponent(/datum/component/spawner, list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion), 200, faction, "peels itself off from", 3) + AddComponent(\ + /datum/component/spawner,\ + mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion),\ + spawn_time = 20 SECONDS,\ + max_mobs = 3,\ + spawn_text = "peels itself off from",\ + faction = faction,\ + ) // Snow Legion /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 189c15aa617..ad7c164a5fd 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -127,8 +127,6 @@ ///If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood. var/gold_core_spawnable = NO_SPAWN - var/datum/component/spawner/nest - ///Sentience type, for slime potions. var/sentience_type = SENTIENCE_ORGANIC @@ -225,10 +223,6 @@ GLOB.simple_animals[AIStatus] -= src SSnpcpool.currentrun -= src - if(nest) - nest.spawned_mobs -= src - nest = null - var/turf/T = get_turf(src) if (T && AIStatus == AI_Z_OFF) SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src @@ -459,9 +453,6 @@ new i(loc) /mob/living/simple_animal/death(gibbed) - if(nest) - nest.spawned_mobs -= src - nest = null drop_loot() if(dextrous) drop_all_held_items()