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 17:21:49 +00:00
committed by GitHub
parent bfb076eea5
commit 08fe54baf7
12 changed files with 67357 additions and 5 deletions
+3
View File
@@ -2181,6 +2181,7 @@
#include "code\modules\effects\map_effects\effect_emitter.dm"
#include "code\modules\effects\map_effects\map_effects.dm"
#include "code\modules\effects\map_effects\map_helpers.dm"
#include "code\modules\effects\map_effects\mob_spawner.dm"
#include "code\modules\effects\map_effects\perma_light.dm"
#include "code\modules\effects\map_effects\portal.dm"
#include "code\modules\effects\map_effects\screen_shaker.dm"
@@ -3923,6 +3924,8 @@
#include "maps\away\away_site\magshield\magshield_areas.dm"
#include "maps\away\away_site\orion\orion_automated_station.dm"
#include "maps\away\away_site\overgrown_mining_station\overgrown_mining_station.dm"
#include "maps\away\away_site\phoron_deposit\phoron_deposit.dm"
#include "maps\away\away_site\phoron_deposit\phoron_deposit_objects.dm"
#include "maps\away\away_site\pirate_base\pirate_base.dm"
#include "maps\away\away_site\pirate_base\pirate_base_areas.dm"
#include "maps\away\away_site\pirate_base\pirate_base_ghostroles.dm"
+3
View File
@@ -111,6 +111,9 @@ GLOBAL_LIST_INIT(headsetlist, list("Nothing", "Headset", "Bowman Headset", "Doub
/// Primary Radio Slot loadout choices.
GLOBAL_LIST_INIT(primary_radio_slot_choice, list("Left Ear", "Right Ear", "Wrist"))
// Used to track fauna spawners on the phoron deposit away site.
GLOBAL_LIST_INIT(fauna_spawners, list())
/// Visual nets.
GLOBAL_LIST_EMPTY_TYPED(visual_nets, /datum/visualnet)
/// Camera visualnet.
@@ -145,7 +145,7 @@
playsound(src, barricade_hitsound, 50, 1)
if(is_wired)
visible_message(SPAN_DANGER("\The [src]'s barbed wire slices into [L]!"))
L.apply_damage(rand(5, 10), DAMAGE_BRUTE, pick(BP_R_HAND, BP_L_HAND), "barbed wire", DAMAGE_FLAG_SHARP|DAMAGE_FLAG_EDGE, 25)
L.apply_damage((5), DAMAGE_BRUTE, pick(BP_R_HAND, BP_L_HAND), "barbed wire", DAMAGE_FLAG_SHARP|DAMAGE_FLAG_EDGE, 25)
L.do_attack_animation(src)
take_damage(damage)
@@ -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"
+9 -1
View File
@@ -146,7 +146,15 @@
return
//Drill through the flooring, if any.
if(istype(get_turf(src), /turf/simulated/floor/exoplanet/asteroid))
if(istype(get_turf(src), /turf/simulated/floor/exoplanet/asteroid/ash/rocky/phoron_deposit))
var/turf/simulated/floor/exoplanet/asteroid/ash/rocky/phoron_deposit/T = get_turf(src)
if(!T.dug)
T.gets_dug()
for(var/obj/item/ore/ore in range(1, src)) // gets_dug causes ore to spawn, this picks that ore up as well
ore.forceMove(src)
if(attached_satchel?.linked_box)
attached_satchel.insert_into_storage(ore)
else if(istype(get_turf(src), /turf/simulated/floor/exoplanet/asteroid))
var/turf/simulated/floor/exoplanet/asteroid/T = get_turf(src)
if(!T.dug)
T.gets_dug()
@@ -30,6 +30,7 @@ ABSTRACT_TYPE(/mob/living/simple_animal/hostile)
hunger_enabled = 0//Until automated eating mechanics are enabled, disable hunger for hostile mobs
var/shuttletarget = null
var/enroute = 0
var/obj/effect/landmark/mob_waypoint/target_waypoint = null // The waypoint mobs that are spawned by mapped in spawners move to
// Vars to help find targets
var/list/targets = list()
@@ -311,6 +312,7 @@ ABSTRACT_TYPE(/mob/living/simple_animal/hostile)
/mob/living/simple_animal/hostile/death()
..()
GLOB.move_manager.stop_looping(src)
LoseTarget() //Ensure we always stop chasing upon death
/mob/living/simple_animal/hostile/think()
..()
@@ -432,8 +434,9 @@ ABSTRACT_TYPE(/mob/living/simple_animal/hostile)
if(prob(break_stuff_probability) || bypass_prob) //bypass_prob is used to make mob destroy things in the way to our target
for(var/card_dir in GLOB.cardinals) // North, South, East, West
var/turf/target_turf = get_step(src, card_dir)
var/obj/found_obj = null
var/obj/found_obj = locate(/obj/effect/energy_field) in target_turf
found_obj = locate(/obj/effect/energy_field) in target_turf
if(found_obj && !found_obj.invisibility && found_obj.density)
var/obj/effect/energy_field/e = found_obj
e.Stress(rand(0.5, 1.5))
@@ -479,7 +482,21 @@ ABSTRACT_TYPE(/mob/living/simple_animal/hostile)
hostile_last_attack = world.time
return TRUE
return FALSE
found_obj = locate(/obj/structure/girder) in target_turf
if(found_obj)
found_obj.attack_generic(src, rand(melee_damage_lower, melee_damage_upper), attacktext, TRUE)
hostile_last_attack = world.time
return TRUE
found_obj = null
for (var/obj/structure/barricade/B in target_turf)
found_obj = B
break
if(found_obj)
found_obj.attack_generic(src, rand(melee_damage_lower, melee_damage_upper), attacktext, TRUE)
hostile_last_attack = world.time
return TRUE
return FALSE
/mob/living/simple_animal/hostile/RangedAttack(atom/A, params) //Player firing
if(ranged)
@@ -0,0 +1,61 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: 4000daniel1
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- rscadd: "Added the phoron deposit away site."
- balance: "Hostile mobs now attack barricades & girders in their way."
- balance: "Barricades with barbed wire now deal slightly less damage to attacking mobs."
- bugfix: "Fixed a bug where hostile mobs rarely continue chasing their target even after dying."
@@ -0,0 +1,26 @@
/datum/map_template/ruin/away_site/phoron_deposit
name = "phoron deposit"
description = "An asteroid with a phoron deposit."
prefix = "away_site/phoron_deposit/"
suffix = "phoron_deposit.dmm"
sectors = list(SECTOR_TAU_CETI, SECTOR_ROMANOVICH, SECTOR_VALLEY_HALE, SECTOR_TABITI)
spawn_weight = 1
spawn_cost = 2
id = "deposit"
unit_test_groups = list(3)
/singleton/submap_archetype/deposit
map = "phoron deposit"
descriptor = "An asteroid with a phoron deposit."
/obj/effect/overmap/visitable/sector/deposit
name = "phoron deposit"
desc = "Sensors have detected a rare high yield subsurface phoron deposit within a canyon on this asteroid. Additional scanning of the area reveals that there's a large underground cavern system surrounding it, with a plethora of lifesigns within, likely being aggressive fauna. Expeditionary personnel are advised to fortify the area before commencing drilling, as the process may attract intense hostile attention from the caverns."
icon_state = "object"
in_space = FALSE
/area/phoron_deposit_shuttle
name = "Einstein Engines Shuttle"
icon_state = "yellow"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,122 @@
// Mob stuff
/mob/living/simple_animal/hostile/carp/shark/phoron_deposit
maxHealth = 60
health = 60
/mob/living/simple_animal/hostile/carp/shark/reaver/phoron_deposit
maxHealth = 60
health = 60
speed = 6
/mob/living/simple_animal/hostile/gnat/phoron_deposit
maxHealth = 15
health = 15
destroy_surroundings = TRUE
/mob/living/simple_animal/hostile/carp/shark/reaver/eel/phoron_deposit
maxHealth = 90
health = 90
speed = 3
var/tmp/wall_breaking_allowed = FALSE // The eel gets to break walls just to make sure the event can't be cheesed by building them
var/tmp/breaking_wall = FALSE
/mob/living/simple_animal/hostile/carp/shark/reaver/eel/phoron_deposit/Move(NewLoc)
if(!wall_breaking_allowed)
return ..()
if(!breaking_wall)
if(istype(NewLoc, /turf/simulated/wall))
breaking_wall = TRUE
var/turf/wall = NewLoc
spawn(0)
sleep(5 SECONDS)
if(wall && istype(wall, /turf/simulated/wall) && wall == get_turf(wall) && get_dist(src, wall) == 1)
visible_message(SPAN_DANGER("With a loud thud, \the [src] breaks down the [wall]!"))
playsound(src.loc, 'sound/effects/meteorimpact.ogg', 50, 1)
wall.ChangeTurf(/turf/simulated/floor/exoplanet/asteroid/ash/rocky)
new /obj/effect/decal/cleanable/floor_damage/broken6(get_turf(wall))
breaking_wall = FALSE
return FALSE
return ..()
// Phoron deposit turf
/turf/simulated/floor/exoplanet/asteroid/ash/rocky/phoron_deposit
name = "phoron deposit"
desc = "A rare deposit, full of crystal phoron. You can drill it to extract it, but you've a feeling you should prepare accordingly first..."
var/mineral_amount = 300
/turf/simulated/floor/exoplanet/asteroid/ash/rocky/phoron_deposit/Initialize()
..()
var/turf/T = get_turf(src)
if (T)
T.has_resources = TRUE
if (!T.resources)
T.resources = list()
T.resources[ORE_PHORON] = mineral_amount
return INITIALIZE_HINT_NORMAL
/turf/simulated/floor/exoplanet/asteroid/ash/rocky/phoron_deposit/Destroy()
var/turf/T = get_turf(src)
if (T && T.resources)
T.resources[ORE_PHORON] = max(0, T.resources[ORE_PHORON] - mineral_amount)
if (T.resources[ORE_PHORON] <= 0)
T.resources -= ORE_PHORON
if (!length(T.resources))
T.has_resources = FALSE
..()
return QDEL_HINT_LETMELIVE
/turf/simulated/floor/exoplanet/asteroid/ash/rocky/phoron_deposit/gets_dug(var/mob/user)
..()
for (var/mob/M in view(null, src))
M.show_message(SPAN_DANGER("You struggle to retain your balance as the ground beneath you violently tremors. This can't be just the work of the drill, something is coming!<br>"), 1)
for (var/mob/L in world)
if (L.client && L.z == src.z)
if (!L.client.prefs || (L.client.prefs.sfx_toggles & ASFX_MUSIC))
sound_to(L, 'sound/music/phoron_deposit.ogg')
sleep(16 SECONDS)
activate_fauna_spawners(src.z)
//Corpse
/obj/effect/landmark/corpse/einstein
name = "Einstein Prospector"
corpseuniform = /obj/item/clothing/under/rank/einstein
corpseshoes = /obj/item/clothing/shoes/jackboots
corpsehelmet = /obj/item/clothing/head/helmet/space/void/einstein
corpsesuit = /obj/item/clothing/suit/space/void/einstein
corpseid = TRUE
corpseidjob = "Prospector (Einstein)"
corpseidicon = "einstein_card"
corpsepocket1 = /obj/item/storage/wallet/random
//Paper
/obj/item/paper/phoron_deposit/briefing_note
name = "printed message"
desc = "A message printed from a computer."
info = "<h4>Einstein Engines Internal Communication</h4><br>\
From: Central Command<br>\
To: EEV Origination<br>\
<br>\
We have received credible intelligence that a large Conglomerate vessel is inbound to the sector, the sensor equipment on this vessel is very likely to detect the deposit. Therefore, awaiting further reinforcements is no longer an option. The deposit must be extracted before our competitors reach it.<br>\
<br>\
We understand your concerns regarding the caverns and the presence of fauna. We've prepared a plan of action to ensure the extraction is conducted safely, despite shortage of personnel.<br>\
<br>\
1. Gather all available resources.<br>\
An abundance of ammunition may prove necessary, bring as much as possible. Steel and plasteel will also be required in order to fortify the area around your drilling equipment. Additionally, you are advised to bring medicinal injectors in the event of an emergency, especially painkillers.<br>\
<br>\
2. Construct a defensive perimeter around the deposit. <br>\
Steel and plasteel barricades with barbed wire are advised. Construct several layers, if there is sufficient material to do so.<br>\
<br>\
3. Begin drilling operations.<br>\
Be prepared to defend yourselves upon activation of drilling equipment. We estimate that extracting the deposit entirely will take approximately 15 minutes.<br>\
<br>\
4. Evacuate the site.<br>\
Once the deposit is empty, abandon the defenses and quickly exfiltrate with the phoron. Should the presence of fauna be as intense as we anticipate, it is vital that the area is evacuated with haste. Do not attempt to hold the position.<br>\
<br>\
Follow these steps precisely, and the likelihood of a swift and safe extraction is high. Report back immediately upon mission success.<br>\
<br>\
Good luck.<br>\
<font size=1>Einstein Engines. Lead by our history, leading our future.</font>"
+8 -1
View File
@@ -85,4 +85,11 @@ Original Title: Velvet Rose
Author: Naelynn
License: CC0 1.0
License Link: https://github.com/Aurorastation/Aurora.3/pull/7729#issuecomment-567239575
Link: https://github.com/Aurorastation/Aurora.3/pull/7729
Link: https://github.com/Aurorastation/Aurora.3/pull/7729
----------
File Name: phoron_deposit.ogg
Original Title: earth rumble & Perpetual Tension
Author: Reitanna & Zander Noriega
License: CC0 & CC-BY 3.0
Link: https://freesound.org/people/Reitanna/sounds/217657/
Link 2: https://opengameart.org/content/perpetual-tension
Binary file not shown.