Phoron Deposit Away Site (#20730)

This site is something of an experiment. It features a special mineral
deposit with lots of phoron. When the deposit is drilled, it'll activate
four mob spawners some distance away that'll send fauna towards it.
There can be a total of 20 active fauna on the map at once (5 per
spawner.) There's no limit on how many mobs can spawn in total (for
now.)

The idea is to make an away site with a clear goal and challenge, which
requires multiple people across different departments to accomplish
safely. Security to defend against the waves of fauna, engineering to
construct and maintain barricades/other defenses (very important),
mining to handle the drill, and of course medical in case something goes
wrong. You can't do it solo, trust me I've tried.

Once the deposit has been fully depleted, (takes about 15 minutes or so)
the team will have to sally out of their barricades and fight their way
back to their shuttle. Cause again, the mobs won't stop coming. I think
it's more interesting that way. But it ends with LOTS of fauna corpses,
of which the performance impact on a populated server might be too
severe to justify? I've no clue.

I also added some atmospheric sound and music which plays once the
deposit is drilled. Hopefully sets the tone without being too
overbearing.

This required a lot of code. I do not know how to code. I've tried to be
thorough, but may still want to review it under some extra scrutiny.

See the changelog for some other small tweaks.

### Asset Licenses
The following assets that **have not** been created by myself are
included in this PR:

| sound/music/phoron_deposit.ogg | Reitanna Seishin & Zander Noriega
|CC0 & CC-BY 3.0 | https://creativecommons.org/publicdomain/zero/1.0/
https://creativecommons.org/licenses/by/3.0/
This commit is contained in:
4000daniel1
2025-07-12 19:21:49 +02:00
committed by GitHub
parent bfb076eea5
commit 08fe54baf7
12 changed files with 67357 additions and 5 deletions
@@ -0,0 +1,107 @@
/obj/effect/fauna_spawner
name = "Mob spawner"
desc = "A mob spawner you're not supposed to see"
icon = 'icons/effects/map_effects.dmi'
icon_state = "ghostspawpoint"
anchored = 1
unacidable = 1
simulated = 0
invisibility = 101
var/first_spawn_done = FALSE // Keeping this false will make it so that it always spawns the first mob on the list first
var/spawning_enabled = FALSE
var/list/active_mobs = list()
var/max_active_mobs = 5
var/list/mob_choices = list() // List of mobs it'll spawn
var/obj/effect/landmark/mob_waypoint/waypoint = null // The spawner automatically detects a waypoint on the same z-level, no need to set this manually
/obj/effect/fauna_spawner/Initialize()
. = ..()
var/obj/effect/landmark/mob_waypoint/W = locate(/obj/effect/landmark/mob_waypoint) in world
RegisterSignal(GLOB, COMSIG_GLOB_MOB_DEATH, PROC_REF(mob_died))
if (W && W.z == src.z)
waypoint = W
if(!islist(GLOB.fauna_spawners))
GLOB.fauna_spawners = list()
GLOB.fauna_spawners |= src
/obj/effect/fauna_spawner/Destroy()
UnregisterSignal(GLOB, COMSIG_GLOB_MOB_DEATH, PROC_REF(mob_died))
if(islist(GLOB.fauna_spawners))
GLOB.fauna_spawners -= src
return ..()
/obj/effect/fauna_spawner/proc/start_spawning()
if (spawning_enabled)
return
spawning_enabled = TRUE
spawn()
while (spawning_enabled && src)
for (var/i = length(active_mobs); i >= 1; i--)
var/mob/living/M = active_mobs[i]
if (!M || QDELETED(M) || M.stat == DEAD)
active_mobs.Cut(i, i+1)
if (length(active_mobs) < max_active_mobs)
spawn_mob()
sleep(rand(5 SECONDS, 10 SECONDS))
/obj/effect/fauna_spawner/proc/spawn_mob()
if (!mob_choices || !length(mob_choices))
return
var/mob_type
var/move_speed = 5
var/choice
if (!first_spawn_done)
choice = mob_choices[1]
first_spawn_done = TRUE
else
choice = pick(mob_choices)
if (islist(choice))
mob_type = choice["type"]
move_speed = choice["speed"]
else
mob_type = choice
var/mob/living/new_mob = new mob_type(src.loc)
if (!new_mob)
return
active_mobs += new_mob
RegisterSignal(new_mob, COMSIG_GLOB_MOB_DEATH, PROC_REF(mob_died))
if (src.waypoint && istype(new_mob, /mob/living/simple_animal/hostile))
var/mob/living/simple_animal/hostile/H = new_mob
H.target_waypoint = src.waypoint
spawn()
if (isturf(src.waypoint.loc))
GLOB.move_manager.move_towards(H, src.waypoint.loc, move_speed, TRUE)
/obj/effect/fauna_spawner/proc/mob_died(var/mob/living/mob_ref, gibbed)
for (var/i = length(active_mobs); i >= 1; i--)
var/mob/living/M = active_mobs[i]
if (!M || QDELETED(M) || M.stat == DEAD)
active_mobs.Cut(i, i+1)
if (mob_ref in active_mobs)
active_mobs -= mob_ref
/obj/effect/fauna_spawner/proc/stop_spawning()
spawning_enabled = FALSE
/proc/activate_fauna_spawners(var/z)
if(!islist(GLOB.fauna_spawners) || !length(GLOB.fauna_spawners))
return
for(var/obj/effect/fauna_spawner/S in GLOB.fauna_spawners)
if(S?.loc?.z == z)
S.start_spawning()
//Make your subtypes here
/obj/effect/fauna_spawner/phoron_deposit
name = "Phoron Deposit Spawner"
mob_choices = list(
list(type = /mob/living/simple_animal/hostile/carp/shark/reaver/eel/phoron_deposit, speed = 5), //Speed refers only to the speed that the mobs will move to the waypoint at. Lower values = faster
list(type = /mob/living/simple_animal/hostile/carp/shark/phoron_deposit, speed = 4),
list(type = /mob/living/simple_animal/hostile/carp/shark/reaver/phoron_deposit, speed = 5),
list(type = /mob/living/simple_animal/hostile/gnat/phoron_deposit, speed = 1),
list(type = /mob/living/simple_animal/hostile/carp, speed = 2)
)
/obj/effect/landmark/mob_waypoint
name = "mob waypoint"