diff --git a/byond-extools.dll b/byond-extools.dll
index 4910fad01b..bd6b34c48e 100644
Binary files a/byond-extools.dll and b/byond-extools.dll differ
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index bdee4cdcd5..65994dda5a 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -312,6 +312,7 @@
#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value)
+#define COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH "obj_default_unfasten_wrench" //called exclusively in plumbing, for now
#define COMSIG_OBJ_ATTACK_GENERIC "obj_attack_generic" //from base of atom/animal_attack(): (/mob/user)
#define COMPONENT_STOP_GENERIC_ATTACK 1
diff --git a/code/__DEFINES/plumbing.dm b/code/__DEFINES/plumbing.dm
new file mode 100644
index 0000000000..5ea7c46b42
--- /dev/null
+++ b/code/__DEFINES/plumbing.dm
@@ -0,0 +1,9 @@
+#define FIRST_DUCT_LAYER 1
+#define SECOND_DUCT_LAYER 2
+#define THIRD_DUCT_LAYER 4
+#define FOURTH_DUCT_LAYER 8
+#define FIFTH_DUCT_LAYER 16
+
+#define DUCT_LAYER_DEFAULT THIRD_DUCT_LAYER
+
+#define MACHINE_REAGENT_TRANSFER 10
diff --git a/code/__DEFINES/pool.dm b/code/__DEFINES/pool.dm
index 64bbd20b19..1bf39dcb1d 100644
--- a/code/__DEFINES/pool.dm
+++ b/code/__DEFINES/pool.dm
@@ -8,5 +8,5 @@
GLOBAL_LIST_INIT(blacklisted_pool_reagents, list(
/datum/reagent/toxin/plasma, /datum/reagent/oxygen, /datum/reagent/nitrous_oxide, /datum/reagent/nitrogen, //gases
/datum/reagent/fermi, //blanket fermichem ban sorry. this also covers mkultra, genital enlargers, etc etc.
- /datum/reagent/consumable/femcum, /datum/reagent/consumable/semen //NO.
+ /datum/reagent/consumable/semen //NO.
))
diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index 4296e3c2e9..63259774fa 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -6,10 +6,17 @@
#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
-#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm.
- //if TESTING is enabled, qdel will call this object's find_references() verb.
-#define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails.
-//defines for the gc_destroyed var
+
+#ifdef LEGACY_REFERENCE_TRACKING
+/** If LEGACY_REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
+ *
+ * Functionally identical to QDEL_HINT_QUEUE if GC_FAILURE_HARD_LOOKUP is not enabled in _compiler_options.dm.
+*/
+#define QDEL_HINT_FINDREFERENCE 5
+/// Behavior as QDEL_HINT_FINDREFERENCE, but only if the GC fails and a hard delete is forced.
+#define QDEL_HINT_IFFAIL_FINDREFERENCE 6
+#endif
+
#define GC_QUEUE_CHECK 1
#define GC_QUEUE_HARDDELETE 2
diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm
index f4beef7ee8..44e97ef345 100644
--- a/code/__DEFINES/reagents.dm
+++ b/code/__DEFINES/reagents.dm
@@ -57,6 +57,10 @@
#define ADD_REAGENT 2 // reagent added
#define REM_REAGENT 3 // reagent removed (may still exist)
+
+#define PILL_STYLE_COUNT 22 //Update this if you add more pill icons or you die (literally, we'll toss a nuke at whever your ip turns up)
+#define RANDOM_PILL_STYLE 22 //Dont change this one though
+
#define THRESHOLD_UNHUSK 50 // health threshold for synthflesh/rezadone to unhusk someone
//reagent bitflags, used for altering how they works
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 101330cc8b..fe46fdc710 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -75,6 +75,7 @@
#define VV_HK_MARK "mark"
#define VV_HK_ADDCOMPONENT "addcomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
+#define VV_HK_VIEW_REFERENCES "viewreferences"
// /datum/gas_mixture
#define VV_HK_SET_MOLES "set_moles"
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index d66e83d651..1119ef44ed 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -37,10 +37,10 @@
//CIT CHANGES START HERE, ADDS SNOWFLAKE BODYPARTS AND MORE
//mammal bodyparts (fucking furries)
init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails_animated, GLOB.mam_tails_animated_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/mam_tails, GLOB.mam_tails_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/ears/mam_ears, GLOB.mam_ears_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts/mam_snouts, GLOB.mam_snouts_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/mam_tails_animated, GLOB.mam_tails_animated_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/taur, GLOB.taur_list)
//xeno parts (hiss?)
init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_head, GLOB.xeno_head_list)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index c4964022e5..52e26b67e7 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -93,16 +93,16 @@
if(!GLOB.mam_body_markings_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list)
if(!GLOB.mam_tails_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/mam_tails, GLOB.mam_tails_list)
if(!GLOB.mam_ears_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/ears/mam_ears, GLOB.mam_ears_list)
if(!GLOB.mam_snouts_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts/mam_snouts, GLOB.mam_snouts_list)
//snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns
var/list/snowflake_mam_tails_list = list()
for(var/mtpath in GLOB.mam_tails_list)
- var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath]
+ var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[mtpath]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
@@ -120,7 +120,7 @@
snowflake_markings_list[S.name] = mmpath
var/list/snowflake_ears_list = list()
for(var/mepath in GLOB.mam_ears_list)
- var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath]
+ var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[mepath]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
@@ -129,7 +129,7 @@
snowflake_ears_list[S.name] = mepath
var/list/snowflake_mam_snouts_list = list()
for(var/mspath in GLOB.mam_snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[mspath]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
@@ -138,7 +138,7 @@
snowflake_mam_snouts_list[S.name] = mspath
var/list/snowflake_ipc_antenna_list = list()
for(var/mspath in GLOB.ipc_antennas_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.ipc_antennas_list[mspath]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.ipc_antennas_list[mspath]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 9a36c626ba..0e0bd4ffaa 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -11,15 +11,28 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
-//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing.
- //implies FIND_REF_NO_CHECK_TICK
+/*
+* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
+*
+* * Requires TESTING to be defined to work.
+*/
+//#define REFERENCE_TRACKING
-//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping
+///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
+//#define LEGACY_REFERENCE_TRACKING
+#ifdef LEGACY_REFERENCE_TRACKING
+///Use the legacy reference on things hard deleting by default.
+//#define GC_FAILURE_HARD_LOOKUP
+#ifdef GC_FAILURE_HARD_LOOKUP
+#define FIND_REF_NO_CHECK_TICK
+#endif //ifdef GC_FAILURE_HARD_LOOKUP
+
+#endif //ifdef LEGACY_REFERENCE_TRACKING
//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
-#endif
+#endif //ifdef TESTING
//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
#ifndef PRELOAD_RSC //set to:
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index 7c2ebc5f0c..8225725ffd 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -40,6 +40,46 @@ GLOBAL_LIST_EMPTY(insect_fluffs_list)
GLOBAL_LIST_EMPTY(insect_markings_list)
GLOBAL_LIST_EMPTY(caps_list)
+//a way to index the right bodypart list given the type of bodypart
+GLOBAL_LIST_INIT(mutant_reference_list, list(
+ "tail_lizard" = GLOB.tails_list_lizard,
+ "waggingtail_lizard" = GLOB.animated_tails_list_lizard,
+ "tail_human" = GLOB.tails_list_human,
+ "waggingtail_human" = GLOB.animated_tails_list_human,
+ "spines" = GLOB.spines_list,
+ "waggingspines" = GLOB.animated_spines_list,
+ "snout" = GLOB.snouts_list,
+ "frills" = GLOB.frills_list,
+ "horns" = GLOB.horns_list,
+ "ears" = GLOB.ears_list,
+ "body_markings" = GLOB.body_markings_list,
+ "wings" = GLOB.wings_list,
+ "wingsopen" = GLOB.wings_open_list,
+ "deco_wings" = GLOB.deco_wings_list,
+ "legs" = GLOB.legs_list,
+ "insect_wings" = GLOB.insect_wings_list,
+ "insect_fluff" = GLOB.insect_fluffs_list,
+ "insect_markings" = GLOB.insect_markings_list,
+ "caps" = GLOB.caps_list,
+ "ipc_screen" = GLOB.ipc_screens_list,
+ "ipc_antenna" = GLOB.ipc_antennas_list,
+ "mam_tail" = GLOB.mam_tails_list,
+ "mam_waggingtail" = GLOB.mam_tails_animated_list,
+ "mam_body_markings" = GLOB.mam_body_markings_list,
+ "mam_ears" = GLOB.mam_ears_list,
+ "mam_snouts" = GLOB.mam_snouts_list,
+ "taur" = GLOB.taur_list,
+ "xenodorsal" = GLOB.xeno_dorsal_list,
+ "xenohead" = GLOB.xeno_head_list,
+ "xenotail" = GLOB.xeno_tail_list))
+
+//references wag types to regular types, wings open to wings, etc
+GLOBAL_LIST_INIT(mutant_transform_list, list("wingsopen" = "wings",
+ "waggingtail_human" = "tail_human",
+ "waggingtail_lizard" = "tail_lizard",
+ "waggingspines" = "spines",
+ "mam_waggingtail" = "mam_tail"))
+
GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list("ghost")) //stores the ghost forms that support directional sprites
GLOBAL_LIST_INIT(ghost_forms_with_accessories_list, list("ghost")) //stores the ghost forms that support hair and other such things
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index cc9c067b5d..2e3cb1ea46 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -534,4 +534,4 @@
config_entry_value = 6
/datum/config_entry/number/max_shuttle_size
- config_entry_value = 250
+ config_entry_value = 500
diff --git a/code/controllers/subsystem/fluid.dm b/code/controllers/subsystem/fluid.dm
new file mode 100644
index 0000000000..c4fa13d693
--- /dev/null
+++ b/code/controllers/subsystem/fluid.dm
@@ -0,0 +1,5 @@
+PROCESSING_SUBSYSTEM_DEF(fluids)
+ name = "Fluids"
+ wait = 20
+ stat_tag = "FD" //its actually Fluid Ducts
+ flags = SS_NO_INIT | SS_TICKER
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 8a1c08bc35..b46e22c1fa 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -25,7 +25,7 @@ SUBSYSTEM_DEF(garbage)
//Queue
var/list/queues
- #ifdef TESTING
+ #ifdef LEGACY_REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
var/list/reference_find_on_fail_types = list()
#endif
@@ -134,7 +134,7 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
- #ifdef TESTING
+ #ifdef LEGACY_REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
@@ -145,7 +145,9 @@ SUBSYSTEM_DEF(garbage)
fail_counts[level]++
switch (level)
if (GC_QUEUE_CHECK)
- #ifdef TESTING
+ #ifdef REFERENCE_TRACKING
+ D.find_references()
+ #elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
D.find_references()
#ifdef GC_FAILURE_HARD_LOOKUP
@@ -156,7 +158,19 @@ SUBSYSTEM_DEF(garbage)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
+ #ifdef TESTING
+ log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ var/client/admin = c
+ if(!check_rights_for(admin, R_ADMIN))
+ continue
+ to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
+ #endif
+ #ifdef REFERENCE_TRACKING
+ GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
+ continue
+ #endif
I.failures++
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
@@ -181,7 +195,7 @@ SUBSYSTEM_DEF(garbage)
var/gctime = world.time
var/refid = "\ref[D]"
-#ifdef TESTING
+#ifdef LEGACY_REFERENCE_TRACKING
if(reference_find_on_fail_types[D.type])
reference_find_on_fail["\ref[D]"] = TRUE
#endif
@@ -193,7 +207,7 @@ SUBSYSTEM_DEF(garbage)
queue[refid] = gctime
-#ifdef TESTING
+#ifdef LEGACY_REFERENCE_TRACKING
/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
if(!ispath(type))
return "NOT A VAILD PATH"
@@ -260,12 +274,6 @@ SUBSYSTEM_DEF(garbage)
/datum/qdel_item/New(mytype)
name = "[mytype]"
-#ifdef TESTING
-/proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE)
- SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
- qdel(D, force)
-#endif
-
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/D, force=FALSE, ...)
@@ -319,16 +327,13 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
- if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
+ #ifdef LEGACY_REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
- #ifdef TESTING
- D.find_references()
- #endif
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
- #ifdef TESTING
SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
- #endif
+ #endif
else
#ifdef TESTING
if(!I.no_hint)
@@ -339,119 +344,6 @@ SUBSYSTEM_DEF(garbage)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
-#ifdef TESTING
-
-/datum/verb/find_refs()
- set category = "Debug"
- set name = "Find References"
- set src in world
-
- find_references(FALSE)
-
-/datum/proc/find_references(skip_alert)
- running_find_references = type
- if(usr && usr.client)
- if(usr.client.running_find_references)
- testing("CANCELLED search for references to a [usr.client.running_find_references].")
- usr.client.running_find_references = null
- running_find_references = null
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
- return
-
- if(!skip_alert)
- if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
- running_find_references = null
- return
-
- //this keeps the garbage collector from failing to collect objects being searched for in here
- SSgarbage.can_fire = 0
-
- if(usr && usr.client)
- usr.client.running_find_references = type
-
- testing("Beginning search for references to a [type].")
- last_find_references = world.time
-
- DoSearchVar(GLOB) //globals
- for(var/datum/thing in world) //atoms (don't beleive it's lies)
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/datum/thing) //datums
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/client/thing) //clients
- DoSearchVar(thing, "World -> [thing]")
-
- testing("Completed search for references to a [type].")
- if(usr && usr.client)
- usr.client.running_find_references = null
- running_find_references = null
-
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
-
-/datum/verb/qdel_then_find_references()
- set category = "Debug"
- set name = "qdel() then Find References"
- set src in world
-
- qdel(src, TRUE) //Force.
- if(!running_find_references)
- find_references(TRUE)
-
-/datum/verb/qdel_then_if_fail_find_references()
- set category = "Debug"
- set name = "qdel() then Find References if GC failure"
- set src in world
-
- qdel_and_find_ref_if_fail(src, TRUE)
-
-/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
- if(usr && usr.client && !usr.client.running_find_references)
- return
- if (!recursive_limit)
- return
-
- if(istype(X, /datum))
- var/datum/D = X
- if(D.last_find_references == last_find_references)
- return
-
- D.last_find_references = last_find_references
- var/list/L = D.vars
-
- for(var/varname in L)
- if (varname == "vars")
- continue
- var/variable = L[varname]
-
- if(variable == src)
- testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
-
- else if(islist(variable))
- DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
-
- else if(islist(X))
- var/normal = IS_NORMAL_LIST(X)
- for(var/I in X)
- if (I == src)
- testing("Found [src.type] \ref[src] in list [Xname].")
-
- else if (I && !isnum(I) && normal && X[I] == src)
- testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
-
- else if (islist(I))
- DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
-
-#ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
-#endif
-
-#endif
-
#ifdef TESTING
/proc/writeDatumCount()
var/list/datums = list()
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index 2bfb187018..f113be728d 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -2,6 +2,15 @@
//Large Objects//
/////////////////
+/datum/crafting_recipe/plunger
+ name = "Plunger"
+ result = /obj/item/plunger
+ time = 1
+ reqs = list(/obj/item/stack/sheet/plastic = 1,
+ /obj/item/stack/sheet/mineral/wood = 1)
+ category = CAT_MISC
+ subcategory = CAT_TOOL
+
/datum/crafting_recipe/showercurtain
name = "Shower Curtains"
reqs = list(/obj/item/stack/sheet/cloth = 2,
@@ -337,7 +346,7 @@
result = /obj/item/toy/sword/cx
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
-
+
/datum/crafting_recipe/catgirlplushie
name = "Catgirl Plushie"
reqs = list(/obj/item/toy/plush/hairball = 3)
diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm
new file mode 100644
index 0000000000..6592e41103
--- /dev/null
+++ b/code/datums/components/plumbing/_plumbing.dm
@@ -0,0 +1,215 @@
+/datum/component/plumbing
+ ///Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
+ var/list/datum/ductnet/ducts = list()
+ ///shortcut to our parents' reagent holder
+ var/datum/reagents/reagents
+ ///TRUE if we wanna add proper pipe outless under our parent object. this is pretty good if i may so so myself
+ var/use_overlays = TRUE
+ ///We can't just cut all of the parents' overlays, so we'll track them here
+ var/list/image/ducterlays
+ ///directions in wich we act as a supplier
+ var/supply_connects
+ ///direction in wich we act as a demander
+ var/demand_connects
+ ///FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
+ var/active = FALSE
+ ///if TRUE connects will spin with the parent object visually and codually, so you can have it work in any direction. FALSE if you want it to be static
+ var/turn_connects = TRUE
+
+/datum/component/plumbing/Initialize(start=TRUE, _turn_connects=TRUE) //turn_connects for wheter or not we spin with the object to change our pipes
+ if(parent && !istype(parent, /atom/movable))
+ return COMPONENT_INCOMPATIBLE
+ var/atom/movable/AM = parent
+ if(!AM.reagents)
+ return COMPONENT_INCOMPATIBLE
+ reagents = AM.reagents
+ turn_connects = _turn_connects
+
+ RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PARENT_PREQDELETED), .proc/disable)
+ RegisterSignal(parent, list(COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH), .proc/toggle_active)
+
+ if(start)
+ enable()
+
+ if(use_overlays)
+ create_overlays()
+
+/datum/component/plumbing/process()
+ if(!demand_connects || !reagents)
+ STOP_PROCESSING(SSfluids, src)
+ return
+ if(reagents.total_volume < reagents.maximum_volume)
+ for(var/D in GLOB.cardinals)
+ if(D & demand_connects)
+ send_request(D)
+///Can we be added to the ductnet?
+/datum/component/plumbing/proc/can_add(datum/ductnet/D, dir)
+ if(!active)
+ return
+ if(!dir || !D)
+ return FALSE
+ if(num2text(dir) in ducts)
+ return FALSE
+
+ return TRUE
+///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
+/datum/component/plumbing/proc/send_request(dir)
+ process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = null, dir = dir)
+///check who can give us what we want, and how many each of them will give us
+/datum/component/plumbing/proc/process_request(amount, reagent, dir)
+ var/list/valid_suppliers = list()
+ var/datum/ductnet/net
+ if(!ducts.Find(num2text(dir)))
+ return
+ net = ducts[num2text(dir)]
+ for(var/A in net.suppliers)
+ var/datum/component/plumbing/supplier = A
+ if(supplier.can_give(amount, reagent, net))
+ valid_suppliers += supplier
+ for(var/A in valid_suppliers)
+ var/datum/component/plumbing/give = A
+ give.transfer_to(src, amount / valid_suppliers.len, reagent, net)
+///returns TRUE when they can give the specified amount and reagent. called by process request
+/datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net)
+ if(amount <= 0)
+ return
+
+ if(reagent) //only asked for one type of reagent
+ for(var/A in reagents.reagent_list)
+ var/datum/reagent/R = A
+ if(R.type == reagent)
+ return TRUE
+ else if(reagents.total_volume > 0) //take whatever
+ return TRUE
+///this is where the reagent is actually transferred and is thus the finish point of our process()
+/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
+ if(!reagents || !target || !target.reagents)
+ return FALSE
+ if(reagent)
+ reagents.trans_id_to(target.parent, reagent, amount)
+ else
+ reagents.trans_to(target.parent, amount)
+///We create our luxurious piping overlays/underlays, to indicate where we do what. only called once if use_overlays = TRUE in Initialize()
+/datum/component/plumbing/proc/create_overlays()
+ var/atom/movable/AM = parent
+ for(var/image/I in ducterlays)
+ AM.overlays.Remove(I)
+ qdel(I)
+ ducterlays = list()
+ for(var/D in GLOB.cardinals)
+ var/color
+ var/direction
+ if(D & demand_connects)
+ color = "red" //red because red is mean and it takes
+ else if(D & supply_connects)
+ color = "blue" //blue is nice and gives
+ else
+ continue
+ var/image/I
+ if(turn_connects)
+ switch(D)
+ if(NORTH)
+ direction = "north"
+ if(SOUTH)
+ direction = "south"
+ if(EAST)
+ direction = "east"
+ if(WEST)
+ direction = "west"
+ I = image('icons/obj/plumbing/plumbers.dmi', "[direction]-[color]", layer = AM.layer - 1)
+ else
+ I = image('icons/obj/plumbing/plumbers.dmi', color, layer = AM.layer - 1) //color is not color as in the var, it's just the name
+ I.dir = D
+ AM.add_overlay(I)
+ ducterlays += I
+///we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
+/datum/component/plumbing/proc/disable()
+ if(!active)
+ return
+ STOP_PROCESSING(SSfluids, src)
+ for(var/A in ducts)
+ var/datum/ductnet/D = ducts[A]
+ D.remove_plumber(src)
+ active = FALSE
+ for(var/D in GLOB.cardinals)
+ if(D & (demand_connects | supply_connects))
+ for(var/obj/machinery/duct/duct in get_step(parent, D))
+ duct.attempt_connect()
+
+///settle wherever we are, and start behaving like a piece of plumbing
+/datum/component/plumbing/proc/enable()
+ if(active)
+ return
+ update_dir()
+ active = TRUE
+ var/atom/movable/AM = parent
+ for(var/obj/machinery/duct/D in AM.loc) //Destroy any ducts under us. Ducts also self destruct if placed under a plumbing machine. machines disable when they get moved
+ if(D.anchored) //that should cover everything
+ D.disconnect_duct()
+
+ if(demand_connects)
+ START_PROCESSING(SSfluids, src)
+
+ for(var/D in GLOB.cardinals)
+ if(D & (demand_connects | supply_connects))
+ for(var/atom/movable/A in get_step(parent, D))
+ if(istype(A, /obj/machinery/duct))
+ var/obj/machinery/duct/duct = A
+ duct.attempt_connect()
+ else
+ var/datum/component/plumbing/P = A.GetComponent(/datum/component/plumbing)
+ if(P)
+ direct_connect(P, D)
+
+/// Toggle our machinery on or off. This is called by a hook from default_unfasten_wrench with anchored as only param, so we dont have to copypaste this on every object that can move
+/datum/component/plumbing/proc/toggle_active(obj/O, new_state)
+ if(new_state)
+ enable()
+ else
+ disable()
+/** We update our connects only when we settle down by taking our current and original direction to find our new connects
+* If someone wants it to fucking spin while connected to something go actually knock yourself out
+*/
+/datum/component/plumbing/proc/update_dir()
+ if(!turn_connects)
+ return
+ var/atom/movable/AM = parent
+ var/new_demand_connects
+ var/new_supply_connects
+ var/new_dir = AM.dir
+ var/angle = 180 - dir2angle(new_dir)
+ if(new_dir == SOUTH)
+ demand_connects = initial(demand_connects)
+ supply_connects = initial(supply_connects)
+ else
+ for(var/D in GLOB.cardinals)
+ if(D & initial(demand_connects))
+ new_demand_connects += turn(D, angle)
+ if(D & initial(supply_connects))
+ new_supply_connects += turn(D, angle)
+ demand_connects = new_demand_connects
+ supply_connects = new_supply_connects
+///Give the direction of a pipe, and it'll return wich direction it originally was when it's object pointed SOUTH
+/datum/component/plumbing/proc/get_original_direction(dir)
+ var/atom/movable/AM = parent
+ return turn(dir, dir2angle(AM.dir) - 180)
+//special case in-case we want to connect directly with another machine without a duct
+/datum/component/plumbing/proc/direct_connect(datum/component/plumbing/P, dir)
+ if(!P.active)
+ return
+ var/opposite_dir = turn(dir, 180)
+ if(P.demand_connects & opposite_dir && supply_connects & dir || P.supply_connects & opposite_dir && demand_connects & dir) //make sure we arent connecting two supplies or demands
+ var/datum/ductnet/net = new()
+ net.add_plumber(src, dir)
+ net.add_plumber(P, opposite_dir)
+
+///has one pipe input that only takes, example is manual output pipe
+/datum/component/plumbing/simple_demand
+ demand_connects = NORTH
+///has one pipe output that only supplies. example is liquid pump and manual input pipe
+/datum/component/plumbing/simple_supply
+ supply_connects = NORTH
+///input and output, like a holding tank
+/datum/component/plumbing/tank
+ demand_connects = WEST
+ supply_connects = EAST
diff --git a/code/datums/components/plumbing/chemical_acclimator.dm b/code/datums/components/plumbing/chemical_acclimator.dm
new file mode 100644
index 0000000000..1cbe6ff017
--- /dev/null
+++ b/code/datums/components/plumbing/chemical_acclimator.dm
@@ -0,0 +1,21 @@
+/datum/component/plumbing/acclimator
+ demand_connects = WEST
+ supply_connects = EAST
+ var/obj/machinery/plumbing/acclimator/AC
+
+/datum/component/plumbing/acclimator/Initialize(start=TRUE, _turn_connects=TRUE)
+ . = ..()
+ if(!istype(parent, /obj/machinery/plumbing/acclimator))
+ return COMPONENT_INCOMPATIBLE
+ AC = parent
+
+/datum/component/plumbing/acclimator/can_give(amount, reagent)
+ . = ..()
+ if(. && AC.emptying)
+ return TRUE
+ return FALSE
+///We're overriding process and not send_request, because all process does is do the requests, so we might aswell cut out the middle man and save some code from running
+/datum/component/plumbing/acclimator/process()
+ if(AC.emptying)
+ return
+ . = ..()
diff --git a/code/datums/components/plumbing/filter.dm b/code/datums/components/plumbing/filter.dm
new file mode 100644
index 0000000000..76b76323c5
--- /dev/null
+++ b/code/datums/components/plumbing/filter.dm
@@ -0,0 +1,59 @@
+///The magical plumbing component used by the chemical filters. The different supply connects behave differently depending on the filters set on the chemical filter
+/datum/component/plumbing/filter
+ demand_connects = NORTH
+ supply_connects = SOUTH | EAST | WEST //SOUTH is straight, EAST is left and WEST is right. We look from the perspective of the insert
+
+/datum/component/plumbing/filter/Initialize()
+ . = ..()
+ if(!istype(parent, /obj/machinery/plumbing/filter))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/plumbing/filter/can_give(amount, reagent, datum/ductnet/net)
+ . = ..()
+ if(.)
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A)) //we need it relative to the direction, so filters don't change when we turn the filter
+ break
+ if(!direction)
+ return FALSE
+ if(reagent)
+ if(!can_give_in_direction(direction, reagent))
+ return FALSE
+
+/datum/component/plumbing/filter/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
+ if(!reagents || !target || !target.reagents)
+ return FALSE
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A))
+ break
+ if(reagent)
+ reagents.trans_id_to(target.parent, reagent, amount)
+ else
+ for(var/A in reagents.reagent_list)
+ var/datum/reagent/R = A
+ if(!can_give_in_direction(direction, R.type))
+ continue
+ var/new_amount
+ if(R.volume < amount)
+ new_amount = amount - R.volume
+ reagents.trans_id_to(target.parent, R.type, amount)
+ amount = new_amount
+ if(amount <= 0)
+ break
+///We check if the direction and reagent are valid to give. Needed for filters since different outputs have different behaviours
+/datum/component/plumbing/filter/proc/can_give_in_direction(dir, reagent)
+ var/obj/machinery/plumbing/filter/F = parent
+ switch(dir)
+ if(SOUTH) //straight
+ if(!F.left.Find(reagent) && !F.right.Find(reagent))
+ return TRUE
+ if(WEST) //right
+ if(F.right.Find(reagent))
+ return TRUE
+ if(EAST) //left
+ if(F.left.Find(reagent))
+ return TRUE
diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm
new file mode 100644
index 0000000000..90f4e621da
--- /dev/null
+++ b/code/datums/components/plumbing/reaction_chamber.dm
@@ -0,0 +1,38 @@
+/datum/component/plumbing/reaction_chamber
+ demand_connects = WEST
+ supply_connects = EAST
+
+/datum/component/plumbing/reaction_chamber/Initialize(start=TRUE, _turn_connects=TRUE)
+ . = ..()
+ if(!istype(parent, /obj/machinery/plumbing/reaction_chamber))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net)
+ . = ..()
+ var/obj/machinery/plumbing/reaction_chamber/RC = parent
+ if(!. || !RC.emptying)
+ return FALSE
+
+/datum/component/plumbing/reaction_chamber/send_request(dir)
+ var/obj/machinery/plumbing/reaction_chamber/RC = parent
+ if(RC.emptying || !LAZYLEN(RC.required_reagents))
+ return
+ for(var/RT in RC.required_reagents)
+ var/has_reagent = FALSE
+ for(var/A in reagents.reagent_list)
+ var/datum/reagent/RD = A
+ if(RT == RD.type)
+ has_reagent = TRUE
+ if(RD.volume < RC.required_reagents[RT])
+ process_request(min(RC.required_reagents[RT] - RD.volume, MACHINE_REAGENT_TRANSFER) , RT, dir)
+ return
+ if(!has_reagent)
+ process_request(min(RC.required_reagents[RT], MACHINE_REAGENT_TRANSFER), RT, dir)
+ return
+
+ RC.reagent_flags &= ~NO_REACT
+ reagents.handle_reactions()
+
+ RC.emptying = TRUE //If we move this up, it'll instantly get turned off since any reaction always sets the reagent_total to zero. Other option is make the reaction update
+ //everything for every chemical removed, wich isn't a good option either.
+ RC.on_reagent_change() //We need to check it now, because some reactions leave nothing left.
diff --git a/code/datums/components/plumbing/splitter.dm b/code/datums/components/plumbing/splitter.dm
new file mode 100644
index 0000000000..7194e8803b
--- /dev/null
+++ b/code/datums/components/plumbing/splitter.dm
@@ -0,0 +1,45 @@
+/datum/component/plumbing/splitter
+ demand_connects = NORTH
+ supply_connects = SOUTH | EAST
+
+/datum/component/plumbing/splitter/Initialize()
+ . = ..()
+ if(. && !istype(parent, /obj/machinery/plumbing/splitter))
+ return FALSE
+
+/datum/component/plumbing/splitter/can_give(amount, reagent, datum/ductnet/net)
+ . = ..()
+ if(!.)
+ return
+ . = FALSE
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A))
+ break
+ var/obj/machinery/plumbing/splitter/S = parent
+ switch(direction)
+ if(SOUTH)
+ if(S.turn_straight && S.transfer_straight <= amount)
+ S.turn_straight = FALSE
+ return TRUE
+ if(EAST)
+ if(!S.turn_straight && S.transfer_side <= amount)
+ S.turn_straight = TRUE
+ return TRUE
+
+/datum/component/plumbing/splitter/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A))
+ break
+ var/obj/machinery/plumbing/splitter/S = parent
+ switch(direction)
+ if(SOUTH)
+ if(amount >= S.transfer_straight)
+ amount = S.transfer_straight
+ if(EAST)
+ if(amount >= S.transfer_side)
+ amount = S.transfer_side
+ . = ..()
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 348f9e6778..a91549ab4c 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -31,6 +31,9 @@
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
+ #ifdef REFERENCE_TRACKING
+ VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References")
+ #endif
//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
diff --git a/code/datums/diseases/advance/symptoms/species.dm b/code/datums/diseases/advance/symptoms/species.dm
index 49a3cf8d07..2d5255842b 100644
--- a/code/datums/diseases/advance/symptoms/species.dm
+++ b/code/datums/diseases/advance/symptoms/species.dm
@@ -31,4 +31,3 @@
/datum/symptom/inorganic_adaptation/OnRemove(datum/disease/advance/A)
A.infectable_biotypes &= ~MOB_MINERAL
-
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index de5c1ece91..9486029fdd 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -137,10 +137,10 @@
L[DNA_COLOR_TWO_BLOCK] = sanitize_hexcolor(features["mcolor2"], 6)
L[DNA_COLOR_THREE_BLOCK] = sanitize_hexcolor(features["mcolor3"], 6)
if(!GLOB.mam_tails_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/mam_tails, GLOB.mam_tails_list)
L[DNA_MUTANTTAIL_BLOCK] = construct_block(GLOB.mam_tails_list.Find(features["mam_tail"]), GLOB.mam_tails_list.len)
if(!GLOB.mam_ears_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/ears/mam_ears, GLOB.mam_ears_list)
L[DNA_MUTANTEAR_BLOCK] = construct_block(GLOB.mam_ears_list.Find(features["mam_ears"]), GLOB.mam_ears_list.len)
if(!GLOB.mam_body_markings_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list)
diff --git a/code/datums/ductnet.dm b/code/datums/ductnet.dm
new file mode 100644
index 0000000000..14a74a67c4
--- /dev/null
+++ b/code/datums/ductnet.dm
@@ -0,0 +1,65 @@
+///We handle the unity part of plumbing. We track who is connected to who.
+/datum/ductnet
+ var/list/suppliers = list()
+ var/list/demanders = list()
+ var/list/obj/machinery/duct/ducts = list()
+
+ var/capacity
+///Add a duct to our network
+/datum/ductnet/proc/add_duct(obj/machinery/duct/D)
+ if(!D || (D in ducts))
+ return
+ ducts += D
+ D.duct = src
+///Remove a duct from our network and commit suicide, because this is probably easier than to check who that duct was connected to and what part of us was lost
+/datum/ductnet/proc/remove_duct(obj/machinery/duct/ducting)
+ destroy_network(FALSE)
+ for(var/obj/machinery/duct/D in ducting.neighbours)
+ addtimer(CALLBACK(D, /obj/machinery/duct/proc/reconnect), 0) //all needs to happen after the original duct that was destroyed finishes destroying itself
+ addtimer(CALLBACK(D, /obj/machinery/duct/proc/generate_connects), 0)
+ qdel(src)
+///add a plumbing object to either demanders or suppliers
+/datum/ductnet/proc/add_plumber(datum/component/plumbing/P, dir)
+ if(!P.can_add(src, dir))
+ return FALSE
+ P.ducts[num2text(dir)] = src
+ if(dir & P.supply_connects)
+ suppliers += P
+ else if(dir & P.demand_connects)
+ demanders += P
+ return TRUE
+///remove a plumber. we dont delete ourselves because ductnets dont persist through plumbing objects
+/datum/ductnet/proc/remove_plumber(datum/component/plumbing/P)
+ suppliers.Remove(P) //we're probably only in one of these, but Remove() is inherently sane so this is fine
+ demanders.Remove(P)
+
+ for(var/dir in P.ducts)
+ if(P.ducts[dir] == src)
+ P.ducts -= dir
+ if(!ducts.len) //there were no ducts, so it was a direct connection. we destroy ourselves since a ductnet with only one plumber and no ducts is worthless
+ destroy_network()
+///we combine ductnets. this occurs when someone connects to seperate sets of fluid ducts
+/datum/ductnet/proc/assimilate(datum/ductnet/D)
+ ducts.Add(D.ducts)
+ suppliers.Add(D.suppliers)
+ demanders.Add(D.demanders)
+ for(var/A in D.suppliers + D.demanders)
+ var/datum/component/plumbing/P = A
+ for(var/s in P.ducts)
+ if(P.ducts[s] != D)
+ continue
+ P.ducts[s] = src //all your ducts are belong to us
+ for(var/A in D.ducts)
+ var/obj/machinery/duct/M = A
+ M.duct = src //forget your old master
+
+ destroy_network()
+///destroy the network and tell all our ducts and plumbers we are gone
+/datum/ductnet/proc/destroy_network(delete=TRUE)
+ for(var/A in suppliers + demanders)
+ remove_plumber(A)
+ for(var/A in ducts)
+ var/obj/machinery/duct/D = A
+ D.duct = null
+ if(delete) //I don't want code to run with qdeleted objects because that can never be good, so keep this in-case the ductnet has some business left to attend to before commiting suicide
+ qdel(src)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 0238529195..0a6c2b9eca 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -646,3 +646,10 @@
animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
sleep(1)
animate(I, alpha = 0, transform = matrix(), time = 1)
+
+/atom/movable/proc/set_anchored(anchorvalue) //literally only for plumbing ran
+ SHOULD_CALL_PARENT(TRUE)
+ if(anchored == anchorvalue)
+ return
+ . = anchored
+ anchored = anchorvalue
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index 406e142a25..be8dee5bf8 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -224,8 +224,7 @@ Credit where due:
qdel(S)
if(S && !QDELETED(S))
to_chat(L, "[slot] is a clockwork slab, a multipurpose tool used to construct machines and invoke ancient words of power. If this is your first time \
- as a servant, you can find a concise tutorial in the Recollection category of its interface.")
- to_chat(L, "If you want more information, you can read the wiki page to learn more.")
+ as a servant, you can read the wiki page to learn more.")
return TRUE
return FALSE
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index bd4ac5f2d1..2b7411d03f 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -117,6 +117,7 @@ Class Procs:
var/new_occupant_dir = SOUTH //The direction the occupant will be set to look at when entering the machine.
var/speed_process = FALSE // Process as fast as possible?
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
+ var/wire_compatible = FALSE
// For storing and overriding ui id and dimensions
var/tgui_id // ID of TGUI interface
var/ui_style // ID of custom TGUI style (optional)
@@ -433,6 +434,7 @@ Class Procs:
to_chat(user, "You [anchored ? "un" : ""]secure [src].")
setAnchored(!anchored)
playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
+ SEND_SIGNAL(src, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, anchored)
return SUCCESSFUL_UNFASTEN
return FAILED_UNFASTEN
return CANT_UNFASTEN
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index b4eca10ed0..07ffe8896e 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -135,6 +135,14 @@ RLD
flick("[icon_state]_empty", src) //somewhat hacky thing to make RCDs with ammo counters actually have a blinking yellow light
return .
+
+/obj/item/construction/proc/check_menu(mob/living/user)
+ if(!istype(user))
+ return FALSE
+ if(user.incapacitated() || !user.Adjacent(src))
+ return FALSE
+ return TRUE
+
/obj/item/construction/proc/range_check(atom/A, mob/user)
if(!(A in range(custom_range, get_turf(user))))
to_chat(user, "The \'Out of Range\' light on [src] blinks red.")
@@ -275,13 +283,6 @@ RLD
//Not scaling these down to button size because they look horrible then, instead just bumping up radius.
return MA
-/obj/item/construction/rcd/proc/check_menu(mob/living/user)
- if(!istype(user))
- return FALSE
- if(user.incapacitated() || !user.Adjacent(src))
- return FALSE
- return TRUE
-
/obj/item/construction/rcd/proc/change_computer_dir(mob/user)
if(!user)
return
@@ -855,6 +856,82 @@ RLD
desc = "It contains the design for firelock, air alarm, fire alarm, apc circuits and crap power cells."
upgrade = RCD_UPGRADE_SIMPLE_CIRCUITS
+/obj/item/construction/plumbing
+ name = "Plumbing Constructor"
+ desc = "An expertly modified RCD outfitted to construct plumbing machinery. Reload with compressed matter cartridges."
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "arcd"
+ item_state = "oldrcd"
+ has_ammobar = FALSE
+ matter = 200
+ max_matter = 200
+
+ ///type of the plumbing machine
+ var/blueprint = null
+ ///index, used in the attack self to get the type. stored here since it doesnt change
+ var/list/choices = list()
+ ///index, used in the attack self to get the type. stored here since it doesnt change
+ var/list/name_to_type = list()
+ ///
+ var/list/machinery_data = list("cost" = list(), "delay" = list())
+
+/obj/item/construction/plumbing/attack_self(mob/user)
+ ..()
+ if(!choices.len)
+ for(var/A in subtypesof(/obj/machinery/plumbing))
+ var/obj/machinery/plumbing/M = A
+ if(initial(M.rcd_constructable))
+ choices += list(initial(M.name) = image(icon = initial(M.icon), icon_state = initial(M.icon_state)))
+ name_to_type[initial(M.name)] = M
+ machinery_data["cost"][A] = initial(M.rcd_cost)
+ machinery_data["delay"][A] = initial(M.rcd_delay)
+
+ var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
+ if(!check_menu(user))
+ return
+
+ blueprint = name_to_type[choice]
+ playsound(src, 'sound/effects/pop.ogg', 50, FALSE)
+ to_chat(user, "You change [name]s blueprint to '[choice]'.")
+
+///pretty much rcd_create, but named differently to make myself feel less bad for copypasting from a sibling-type
+/obj/item/construction/plumbing/proc/create_machine(atom/A, mob/user)
+ if(!machinery_data || !isopenturf(A))
+ return FALSE
+
+ if(checkResource(machinery_data["cost"][blueprint], user) && blueprint)
+ if(do_after(user, machinery_data["delay"][blueprint], target = A))
+ if(checkResource(machinery_data["cost"][blueprint], user) && canPlace(A))
+ useResource(machinery_data["cost"][blueprint], user)
+ activate()
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ new blueprint (A, FALSE, FALSE)
+ return TRUE
+
+/obj/item/construction/plumbing/proc/canPlace(turf/T)
+ if(!isopenturf(T))
+ return FALSE
+ . = TRUE
+ for(var/obj/O in T.contents)
+ if(O.density) //let's not built ontop of dense stuff, like big machines and other obstacles, it kills my immershion
+ return FALSE
+
+/obj/item/construction/plumbing/afterattack(atom/A, mob/user)
+ . = ..()
+ if(!range_check(A, user))
+ return
+ if(istype(A, /obj/machinery/plumbing))
+ var/obj/machinery/plumbing/P = A
+ if(P.anchored)
+ to_chat(user, "The [P.name] needs to be unanchored!")
+ return
+ if(do_after(user, 20, target = P))
+ P.deconstruct() //Let's not substract matter
+ playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE) //this is just such a great sound effect
+ else
+ create_machine(A, user)
+
+
#undef GLOW_MODE
#undef LIGHT_MODE
#undef REMOVE_MODE
diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm
index b3d5ed431a..70e4441c66 100644
--- a/code/game/objects/items/RPD.dm
+++ b/code/game/objects/items/RPD.dm
@@ -6,6 +6,7 @@ RPD
#define ATMOS_CATEGORY 0
#define DISPOSALS_CATEGORY 1
#define TRANSIT_CATEGORY 2
+#define PLUMBING_CATEGORY 3
#define BUILD_MODE (1<<0)
#define WRENCH_MODE (1<<1)
@@ -75,6 +76,13 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
)
))
+GLOBAL_LIST_INIT(fluid_duct_recipes, list(
+ "Fluid Ducts" = list(
+ new /datum/pipe_info/plumbing("Duct", /obj/machinery/duct, PIPE_ONEDIR),
+ new /datum/pipe_info/plumbing/multilayer("Duct Layer-Manifold",/obj/machinery/duct/multilayered, PIPE_STRAIGHT)
+ )
+))
+
/datum/pipe_info
var/name
var/icon_state
@@ -175,6 +183,15 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if(dt == PIPE_UNARY_FLIPPABLE)
icon_state = "[icon_state]_preview"
+/datum/pipe_info/plumbing/New(label, obj/path, dt=PIPE_UNARY)
+ name = label
+ id = path
+ icon_state = initial(path.icon_state)
+ dirtype = dt
+
+/datum/pipe_info/plumbing/multilayer //exists as identifier so we can see the difference between multi_layer and just ducts properly later on
+
+
/obj/item/pipe_dispenser
name = "Rapid Piping Device (RPD)"
desc = "A device used to rapidly pipe things."
@@ -200,15 +217,19 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
var/atmos_build_speed = 5 //deciseconds (500ms)
var/disposal_build_speed = 5
var/transit_build_speed = 5
+ var/plumbing_build_speed = 5
var/destroy_speed = 5
var/paint_speed = 5
var/category = ATMOS_CATEGORY
var/piping_layer = PIPING_LAYER_DEFAULT
+ var/ducting_layer = DUCT_LAYER_DEFAULT
var/datum/pipe_info/recipe
var/static/datum/pipe_info/first_atmos
var/static/datum/pipe_info/first_disposal
var/static/datum/pipe_info/first_transit
var/mode = BUILD_MODE | DESTROY_MODE | WRENCH_MODE
+ var/static/datum/pipe_info/first_plumbing
+ var/locked = FALSE //wheter we can change categories. Useful for the plumber
/obj/item/pipe_dispenser/New()
. = ..()
@@ -253,12 +274,15 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
var/list/data = list(
"category" = category,
"piping_layer" = piping_layer,
- // "ducting_layer" = ducting_layer, //uhh is this for chem thing?
+
+ "ducting_layer" = ducting_layer,
+
"preview_rows" = recipe.get_preview(p_dir),
"categories" = list(),
"selected_color" = paint_color,
"paint_colors" = GLOB.pipe_paint_colors,
- "mode" = mode
+ "mode" = mode,
+ "locked" = locked
)
var/list/recipes
@@ -269,6 +293,8 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
recipes = GLOB.disposal_pipe_recipes
if(TRANSIT_CATEGORY)
recipes = GLOB.transit_tube_recipes
+ if(PLUMBING_CATEGORY)
+ recipes = GLOB.fluid_duct_recipes
for(var/c in recipes)
var/list/cat = recipes[c]
var/list/r = list()
@@ -297,6 +323,8 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
recipe = first_atmos
if(TRANSIT_CATEGORY)
recipe = first_transit
+ if(PLUMBING_CATEGORY)
+ recipe = first_plumbing
p_dir = NORTH
playeffect = FALSE
if("piping_layer")
@@ -468,16 +496,56 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if(mode & WRENCH_MODE)
tube.wrench_act(user, src)
return
-
+ if(PLUMBING_CATEGORY) //Plumbing.
+ if(!can_make_pipe)
+ return ..()
+ A = get_turf(A)
+ if(isclosedturf(A))
+ to_chat(user, "[src]'s error light flickers; there's something in the way!")
+ return
+ to_chat(user, "You start building a fluid duct...")
+ playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
+ if(do_after(user, plumbing_build_speed, target = A))
+ var/obj/machinery/duct/D
+ if(recipe.type == /datum/pipe_info/plumbing/multilayer)
+ var/temp_connects = NORTH + SOUTH
+ if(queued_p_dir == EAST)
+ temp_connects = EAST + WEST
+ D = new queued_p_type (A, TRUE, GLOB.pipe_paint_colors[paint_color], ducting_layer, temp_connects)
+ else
+ D = new queued_p_type (A, TRUE, GLOB.pipe_paint_colors[paint_color], ducting_layer)
+ D.add_fingerprint(usr)
+ if(mode & WRENCH_MODE)
+ D.wrench_act(user, src)
else
return ..()
/obj/item/pipe_dispenser/proc/activate()
playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1)
+/* unneeded, you can craft ducts from plastic
+/obj/item/pipe_dispenser/plumbing
+ name = "Plumberinator"
+ desc = "A crude device to rapidly plumb things."
+ icon_state = "plumberer"
+ category = PLUMBING_CATEGORY
+ locked = TRUE
+
+/obj/item/pipe_dispenser/plumbing/Initialize()
+ . = ..()
+ spark_system = new
+ spark_system.set_up(5, 0, src)
+ spark_system.attach(src)
+ if(!first_plumbing)
+ first_plumbing = GLOB.fluid_duct_recipes[GLOB.fluid_duct_recipes[1]][1]
+
+ recipe = first_plumbing
+
+*/
#undef ATMOS_CATEGORY
#undef DISPOSALS_CATEGORY
#undef TRANSIT_CATEGORY
+#undef PLUMBING_CATEGORY
#undef BUILD_MODE
#undef DESTROY_MODE
diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm
index 56eb25f953..37d19783b5 100644
--- a/code/game/objects/items/circuitboards/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -48,6 +48,13 @@
/obj/item/stack/cable_coil = 5,
/obj/item/stack/sheet/glass = 1)
+/obj/item/circuitboard/machine/medipen_refiller
+ name = "Medipen Refiller (Machine Board)"
+ icon_state = "medical"
+ build_path = /obj/machinery/medipen_refiller
+ req_components = list(
+ /obj/item/stock_parts/matter_bin = 1)
+
/obj/item/circuitboard/machine/clonepod
name = "Clone Pod (Machine Board)"
build_path = /obj/machinery/clonepod
@@ -512,6 +519,10 @@
/obj/item/stack/sheet/glass = 1)
needs_anchored = FALSE
+/obj/item/circuitboard/machine/hydroponics/automagic
+ name = "Automatic Hydroponics Tray (Machine Board)"
+ build_path = /obj/machinery/hydroponics/constructable/automagic
+
/obj/item/circuitboard/machine/seed_extractor
name = "Seed Extractor (Machine Board)"
build_path = /obj/machinery/seed_extractor
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 9ea8c9c689..7ba2196184 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -128,6 +128,7 @@
absorption_capacity = 5
splint_factor = 0.35
custom_price = PRICE_REALLY_CHEAP
+ grind_results = list(/datum/reagent/cellulose = 2)
// gauze is only relevant for wounds, which are handled in the wounds themselves
/obj/item/stack/medical/gauze/try_heal(mob/living/M, mob/user, silent)
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 305c2ab276..645051b7c2 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -412,6 +412,7 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
force = 0
throwforce = 0
merge_type = /obj/item/stack/sheet/cloth
+ grind_results = list(/datum/reagent/cellulose = 2)
/obj/item/stack/sheet/cloth/get_main_recipes()
. = ..()
@@ -773,6 +774,7 @@ GLOBAL_LIST_INIT(plastic_recipes, list(
new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/glass/beaker/waterbottle/empty), \
new /datum/stack_recipe("large water bottle", /obj/item/reagent_containers/glass/beaker/waterbottle/large/empty,3), \
new /datum/stack_recipe("shower curtain", /obj/structure/curtain, 10, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("duct", /obj/item/stack/ducts,1), \
new /datum/stack_recipe("laser pointer case", /obj/item/glasswork/glass_base/laserpointer_shell, 30), \
new /datum/stack_recipe("wet floor sign", /obj/item/caution, 2)))
@@ -841,6 +843,7 @@ new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperfra
merge_type = /obj/item/stack/sheet/cotton
var/pull_effort = 30
var/loom_result = /obj/item/stack/sheet/cloth
+ grind_results = list(/datum/reagent/cellulose = 5)
/obj/item/stack/sheet/cotton/ten
amount = 10
@@ -856,6 +859,7 @@ new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperfra
merge_type = /obj/item/stack/sheet/cotton/durathread
pull_effort = 70
loom_result = /obj/item/stack/sheet/durathread
+ grind_results = list(/datum/reagent/cellulose = 10)
/obj/item/stack/sheet/meat
name = "meat sheets"
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 3c554aa58e..0001494fdd 100755
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -478,7 +478,7 @@
/obj/item/assembly/signaler,
/obj/item/lightreplacer,
/obj/item/rcd_ammo,
- /obj/item/construction/rcd,
+ /obj/item/construction,
/obj/item/pipe_dispenser,
/obj/item/stack/rods,
/obj/item/stack/tile/plasteel,
@@ -492,7 +492,7 @@
icon_state = "grenadebeltnew"
item_state = "security"
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
-
+
/obj/item/storage/belt/grenade/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 79dd13b0ae..1927adb477 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -329,3 +329,6 @@
. = ..()
if(. && ricochet_damage_mod)
take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet
+
+/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ return
diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm
index 4f6256e9c4..263b47aab2 100644
--- a/code/game/objects/structures/lavaland/geyser.dm
+++ b/code/game/objects/structures/lavaland/geyser.dm
@@ -9,7 +9,7 @@
var/erupting_state = null //set to null to get it greyscaled from "[icon_state]_soup". Not very usable with the whole random thing, but more types can be added if you change the spawn prob
var/activated = FALSE //whether we are active and generating chems
- var/reagent_id = /datum/reagent/fuel/oil
+ var/reagent_id = /datum/reagent/oil
var/potency = 2 //how much reagents we add every process (2 seconds)
var/max_volume = 500
var/start_volume = 50
@@ -77,10 +77,10 @@
/obj/item/plunger/reinforced
name = "reinforced plunger"
- desc = "It's an M. 7 Reinforced Plunger© for heavy duty plunging."
+ desc = "It's an M. 7 Reinforced Plunger� for heavy duty plunging."
icon_state = "reinforced_plunger"
reinforced = TRUE
plunge_mod = 0.8
- custom_premium_price = 1200
+ custom_premium_price = 600
diff --git a/code/game/turfs/simulated/floor/plating/asteroid.dm b/code/game/turfs/simulated/floor/plating/asteroid.dm
index 1fbeee7523..6e7dbaf904 100644
--- a/code/game/turfs/simulated/floor/plating/asteroid.dm
+++ b/code/game/turfs/simulated/floor/plating/asteroid.dm
@@ -149,6 +149,8 @@
var/list/megafauna_spawn_list
/// Flora that can spawn in the tunnel, weighted list
var/list/flora_spawn_list
+ //terrain to spawn weighted list
+ var/list/terrain_spawn_list
/// Turf type to choose when spawning in tunnel at 1% chance, weighted list
var/list/choose_turf_type
/// if the tunnel should keep being created
@@ -230,7 +232,8 @@
megafauna_spawn_list = list(/mob/living/simple_animal/hostile/megafauna/dragon = 4, /mob/living/simple_animal/hostile/megafauna/colossus = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = SPAWN_BUBBLEGUM)
if (!flora_spawn_list)
flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2)
-
+ if(!terrain_spawn_list)
+ terrain_spawn_list = list(/obj/structure/geyser/random = 1)
. = ..()
if(!has_data)
produce_tunnel_from_data()
@@ -334,8 +337,19 @@
spawned_flora = SpawnFlora(T)
if(!spawned_flora) // no rocks beneath mob spawners / mobs.
SpawnMonster(T)
+ SpawnTerrain(T)
T.ChangeTurf(turf_type, null, CHANGETURF_IGNORE_AIR)
+/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnTerrain(turf/T)
+ if(prob(1))
+ if(istype(loc, /area/mine/explored) || istype(loc, /area/lavaland/surface/outdoors/explored))
+ return
+ var/randumb = pickweight(terrain_spawn_list)
+ for(var/obj/structure/geyser/F in range(7, T))
+ if(istype(F, randumb))
+ return
+ new randumb(T)
+
/// Spawns a random mob or megafauna in the tunnel
/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnMonster(turf/T)
if(!isarea(loc))
diff --git a/code/game/world.dm b/code/game/world.dm
index 55333fb3e6..a342200b3d 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -12,6 +12,9 @@ GLOBAL_LIST(topic_status_cache)
if (fexists(EXTOOLS))
call(EXTOOLS, "maptick_initialize")()
enable_debugger()
+#ifdef REFERENCE_TRACKING
+ enable_reference_tracking()
+#endif
world.Profile(PROFILE_START)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 4b6e89ef25..0411a38398 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -171,7 +171,11 @@ GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
/client/proc/cmd_display_overlay_log,
/client/proc/reload_configuration,
/datum/admins/proc/create_or_modify_area,
- /client/proc/generate_wikichem_list //DO NOT PRESS UNLESS YOU WANT SUPERLAG
+#ifdef REFERENCE_TRACKING
+ /datum/admins/proc/view_refs,
+ /datum/admins/proc/view_del_failures,
+#endif
+ /client/proc/generate_wikichem_list, //DO NOT PRESS UNLESS YOU WANT SUPERLAG
)
GLOBAL_PROTECT(admin_verbs_debug)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index b93c8dabf1..edf95dc47e 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -254,6 +254,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//message from the initiator without a target, all admins will see this
//won't bug irc
/datum/admin_help/proc/MessageNoRecipient(msg)
+ msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN))
var/ref_src = "[REF(src)]"
//Message to be sent to all admins
var/admin_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [keywords_lookup(msg)]"
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index 68b9a8e341..b7f05740a3 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -55,7 +55,7 @@
if(AH)
message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.")
- var/msg = stripped_multiline_input(src,"Message:", "Private message to [key_name(C, 0, 0)]")
+ var/msg = input(src,"Message:", "Private message to [C.holder?.fakekey ? "an Administrator" : key_name(C, 0, 0)].") as message|null
if (!msg)
message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.")
return
@@ -90,7 +90,7 @@
if(!ircreplyamount) //to prevent people from spamming irc/discord
return
if(!msg)
- msg = stripped_multiline_input(src,"Message:", "Private message to Administrator")
+ msg = input(src,"Message:", "Private message to Administrator") as message|null
if(!msg)
return
@@ -112,7 +112,7 @@
//get message text, limit it's length.and clean/escape html
if(!msg)
- msg = stripped_multiline_input(src,"Message:", "Private message to [key_name(recipient, 0, 0)]")
+ msg = input(src,"Message:", "Private message to [recipient.holder?.fakekey ? "an Administrator" : key_name(recipient, 0, 0)].") as message|null
msg = trim(msg)
if(!msg)
return
@@ -133,7 +133,7 @@
//clean the message if it's not sent by a high-rank admin
if(!check_rights(R_SERVER|R_DEBUG,0)||irc)//no sending html to the poor bots
- msg = trim(sanitize(msg), MAX_MESSAGE_LEN)
+ msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN))
if(!msg)
return
@@ -188,10 +188,10 @@
//AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn
if(CONFIG_GET(flag/popup_admin_pm))
- spawn() //so we don't hold the caller proc up
+ spawn() //so we don't hold the caller proc up. Please functionalize this
var/sender = src
var/sendername = key
- var/reply = stripped_multiline_input(recipient, msg,"Admin PM from-[sendername]", "") //show message and await a reply
+ var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply
if(recipient && reply)
if(sender)
recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
new file mode 100644
index 0000000000..70aac2f107
--- /dev/null
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -0,0 +1,225 @@
+#ifdef REFERENCE_TRACKING
+
+GLOBAL_LIST_EMPTY(deletion_failures)
+
+/world/proc/enable_reference_tracking()
+ if (fexists(EXTOOLS))
+ call(EXTOOLS, "ref_tracking_initialize")()
+
+/proc/get_back_references(datum/D)
+ CRASH("/proc/get_back_references not hooked by extools, reference tracking will not function!")
+
+/proc/get_forward_references(datum/D)
+ CRASH("/proc/get_forward_references not hooked by extools, reference tracking will not function!")
+
+/proc/clear_references(datum/D)
+ return
+
+/datum/admins/proc/view_refs(atom/D in world) //it actually supports datums as well but byond no likey
+ set category = "Debug"
+ set name = "View References"
+
+ if(!check_rights(R_DEBUG) || !D)
+ return
+
+ var/list/backrefs = get_back_references(D)
+ if(isnull(backrefs))
+ var/datum/browser/popup = new(usr, "ref_view", "
"
-
+
if(!length(GLOB.loadout_categories[gear_category]))
dat += "No subcategories detected. Something is horribly wrong!"
else
@@ -1636,7 +1636,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("mam_tail")
var/list/snowflake_tails_list = list()
for(var/path in GLOB.mam_tails_list)
- var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path]
+ var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -1661,7 +1661,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("snout")
var/list/snowflake_snouts_list = list()
for(var/path in GLOB.snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.snouts_list[path]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -1678,7 +1678,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("mam_snouts")
var/list/snowflake_mam_snouts_list = list()
for(var/path in GLOB.mam_snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -1827,7 +1827,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("mam_ears")
var/list/snowflake_ears_list = list()
for(var/path in GLOB.mam_ears_list)
- var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path]
+ var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 64c5e775b0..a8183e4940 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 34
+#define SAVEFILE_VERSION_MAX 35
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -200,6 +200,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
features["silicon_flavor_text"] = html_encode(features["silicon_flavor_text"])
features["ooc_notes"] = html_encode(features["ooc_notes"])
+ if(current_version < 35)
+ if(S["species"] == "lizard")
+ features["mam_snouts"] = features["snout"]
+
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index af44b54ed0..9ab5e8d517 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -25,7 +25,7 @@
ship_name = pick(strings(PIRATE_NAMES_FILE, "ship_names"))
/datum/round_event/pirates/announce(fake)
- priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport") // CITADEL EDIT metabreak
+ priority_announce("A business proposition has been downloaded and printed out at all communication consoles.", "Incoming Business Proposition", "commandreport")
if(fake)
return
threat_message = new
@@ -49,6 +49,7 @@
else
priority_announce("Trying to cheat us? You'll regret this!",sender_override = ship_name)
if(!shuttle_spawned)
+ priority_announce("You won't listen to reason? Then we'll take what's yours or die trying!",sender_override = ship_name)
spawn_shuttle()
/datum/round_event/pirates/start()
@@ -83,8 +84,7 @@
announce_to_ghosts(M)
else
announce_to_ghosts(spawner)
-
- priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport") //CITADEL EDIT also metabreak here too
+ priority_announce("Unidentified ship detected near the station.")
//Shuttle equipment
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index 06179d1087..a208f2de3c 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -30,7 +30,7 @@
var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood.
var/self_sufficiency_progress = 0
var/self_sustaining = FALSE //If the tray generates nutrients and water on its own
-
+ var/canirrigate = TRUE //tin
/obj/machinery/hydroponics/constructable
name = "hydroponics tray"
@@ -847,12 +847,13 @@
if (!anchored)
to_chat(user, "Anchor the tray first!")
return
- using_irrigation = !using_irrigation
- O.play_tool_sound(src)
- user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \
- "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.")
- for(var/obj/machinery/hydroponics/h in range(1,src))
- h.update_icon()
+ if(canirrigate)
+ using_irrigation = !using_irrigation
+ O.play_tool_sound(src)
+ user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \
+ "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.")
+ for(var/obj/machinery/hydroponics/h in range(1,src))
+ h.update_icon()
else if(istype(O, /obj/item/shovel/spade))
if(!myseed && !weedlevel)
@@ -910,11 +911,14 @@
harvest = 0
lastproduce = age
if(istype(myseed, /obj/item/seeds/replicapod))
- to_chat(user, "You harvest from the [myseed.plantname].")
+ if(user)//runtimes
+ to_chat(user, "You harvest from the [myseed.plantname].")
else if(myseed.getYield() <= 0)
- to_chat(user, "You fail to harvest anything useful!")
+ if(user)
+ to_chat(user, "You fail to harvest anything useful!")
else
- to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].")
+ if(user)
+ to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].")
if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
qdel(myseed)
myseed = null
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 5e49a32a23..c7314bf180 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -190,6 +190,31 @@ obj/item/seeds/proc/is_gene_forbidden(typepath)
parent.update_tray(user)
return result
+/obj/item/seeds/proc/harvest_userless()
+ var/obj/machinery/hydroponics/parent = loc //for ease of access
+ var/t_amount = 0
+ var/list/result = list()
+ var/output_loc = parent.loc
+ var/product_name
+ while(t_amount < getYield())
+ var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src)
+ if(parent.myseed.plantname != initial(parent.myseed.plantname))
+ t_prod.name = lowertext(parent.myseed.plantname)
+ if(productdesc)
+ t_prod.desc = productdesc
+ t_prod.seed.name = parent.myseed.name
+ t_prod.seed.desc = parent.myseed.desc
+ t_prod.seed.plantname = parent.myseed.plantname
+ result.Add(t_prod) // User gets a consumable
+ if(!t_prod)
+ return
+ t_amount++
+ product_name = parent.myseed.plantname
+ if(getYield() >= 1)
+ SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name)
+ parent.investigate_log("autmoatic harvest of [getYield()] of [src], with seed traits [english_list(genes)] and reagents_add [english_list(reagents_add)] and potency [potency].", INVESTIGATE_BOTANY)
+ parent.update_tray()
+ return result
/obj/item/seeds/proc/prepare_result(var/obj/item/reagent_containers/food/snacks/grown/T)
if(!T.reagents)
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index bd36218211..1238b37919 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -46,6 +46,7 @@
var/minimal_player_age = 0
var/outfit = null
+ var/plasma_outfit = null //the outfit given to plasmamen
var/exp_requirements = 0
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index 1962b8e8a9..bff56d1d16 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/atmos
+ plasma_outfit = /datum/outfit/plasmaman/atmospherics
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index e5cd015460..8290adbbd7 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -11,6 +11,7 @@
exp_type_department = EXP_TYPE_SERVICE // This is so the jobs menu can work properly
outfit = /datum/outfit/job/bartender
+ plasma_outfit = /datum/outfit/plasmaman/bar
access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_BAR, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
index 65f3e7ca48..4c91f87791 100644
--- a/code/modules/jobs/job_types/botanist.dm
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -10,6 +10,7 @@
selection_color = "#bbe291"
outfit = /datum/outfit/job/botanist
+ plasma_outfit = /datum/outfit/plasmaman/botany
access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 3733658c33..047a07062d 100644
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -17,6 +17,7 @@
outfit = /datum/outfit/job/captain
+ plasma_outfit = /datum/outfit/plasmaman/captain
access = list() //See get_access()
minimal_access = list() //See get_access()
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
index 840af56a0e..1f87a5265d 100644
--- a/code/modules/jobs/job_types/cargo_technician.dm
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -10,6 +10,7 @@
selection_color = "#ca8f55"
outfit = /datum/outfit/job/cargo_tech
+ plasma_outfit = /datum/outfit/plasmaman/cargo
access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MINING,
ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
index 5f66519365..a9e891a303 100644
--- a/code/modules/jobs/job_types/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/chaplain
+ plasma_outfit = /datum/outfit/plasmaman/chaplain
access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
index b2699071e3..6d4204d041 100644
--- a/code/modules/jobs/job_types/chemist.dm
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/chemist
+ plasma_outfit = /datum/outfit/plasmaman/chemist
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index d8df767a9f..18be8c9835 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_ENGINEERING
outfit = /datum/outfit/job/ce
+ plasma_outfit = /datum/outfit/plasmaman/ce
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index 9cbc15ca28..627a7a2ca1 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_MEDICAL
outfit = /datum/outfit/job/cmo
+ plasma_outfit = /datum/outfit/plasmaman/cmo
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index 7ad7148614..dc2f60434c 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/clown
+ plasma_outfit = /datum/outfit/plasmaman/clown
access = list(ACCESS_THEATRE)
minimal_access = list(ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
index 5a5916cb7e..666ee8f036 100644
--- a/code/modules/jobs/job_types/cook.dm
+++ b/code/modules/jobs/job_types/cook.dm
@@ -11,6 +11,7 @@
var/cooks = 0 //Counts cooks amount
outfit = /datum/outfit/job/cook
+ plasma_outfit = /datum/outfit/plasmaman/chef
access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
index 47bfd4914a..254fc15bd4 100644
--- a/code/modules/jobs/job_types/curator.dm
+++ b/code/modules/jobs/job_types/curator.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/curator
+ plasma_outfit = /datum/outfit/plasmaman/curator
access = list(ACCESS_LIBRARY)
minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION)
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index 463113f14c..65724765e1 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/detective
+ plasma_outfit = /datum/outfit/plasmaman/detective
access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
index a40ca0fca3..5ff1bebfbf 100644
--- a/code/modules/jobs/job_types/geneticist.dm
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/geneticist
+ plasma_outfit = /datum/outfit/plasmaman/genetics
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index 8015c19c36..41fb4b99da 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_SERVICE
outfit = /datum/outfit/job/hop
+ plasma_outfit = /datum/outfit/plasmaman/hop
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index 3d7d07d894..cfd8d7f6c0 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -17,6 +17,8 @@
exp_type_department = EXP_TYPE_SECURITY
outfit = /datum/outfit/job/hos
+ plasma_outfit = /datum/outfit/plasmaman/hos
+
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP,
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
index 2f6d6f0e32..c62c2e5b26 100644
--- a/code/modules/jobs/job_types/janitor.dm
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -10,6 +10,7 @@
selection_color = "#bbe291"
outfit = /datum/outfit/job/janitor
+ plasma_outfit = /datum/outfit/plasmaman/janitor
access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
index 1a7499800b..17c376a5de 100644
--- a/code/modules/jobs/job_types/lawyer.dm
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -11,6 +11,7 @@
var/lawyers = 0 //Counts lawyer amount
outfit = /datum/outfit/job/lawyer
+ plasma_outfit = /datum/outfit/plasmaman/bar //yes, this is correct, there's no 'lawyer' plasmeme outfit
access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
index 5ec4b83b2f..d6a763acfe 100644
--- a/code/modules/jobs/job_types/medical_doctor.dm
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -10,6 +10,7 @@
selection_color = "#74b5e0"
outfit = /datum/outfit/job/doctor
+ plasma_outfit = /datum/outfit/plasmaman/medical
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
index 4ba2489ab2..e00b3a1e29 100644
--- a/code/modules/jobs/job_types/mime.dm
+++ b/code/modules/jobs/job_types/mime.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/mime
+ plasma_outfit = /datum/outfit/plasmaman/mime
access = list(ACCESS_THEATRE)
minimal_access = list(ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index 7128fbe2c7..33f7df8260 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -17,6 +17,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/rd
+ plasma_outfit = /datum/outfit/plasmaman/rd
access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index f5ae93bb6a..aa52b353df 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/roboticist
+ plasma_outfit = /datum/outfit/plasmaman/robotics
access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
index 476f740b9d..a851b333fe 100644
--- a/code/modules/jobs/job_types/scientist.dm
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/scientist
+ plasma_outfit = /datum/outfit/plasmaman/science
access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
index 7e71b2d1f4..bc83eb752d 100644
--- a/code/modules/jobs/job_types/security_officer.dm
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/security
+ plasma_outfit = /datum/outfit/plasmaman/security
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_MINERAL_STOREROOM) // See /datum/job/officer/get_access()
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index ebf7ba0f1f..04d3fb53b8 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -12,6 +12,7 @@
outfit = /datum/outfit/job/miner
+ plasma_outfit = /datum/outfit/plasmaman/mining
access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MINING,
ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index d3f5db7dbb..2396728ad8 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/engineer
+ plasma_outfit = /datum/outfit/plasmaman/engineering
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
index 790e828931..3e9b3ba06c 100644
--- a/code/modules/jobs/job_types/virologist.dm
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/virologist
+ plasma_outfit = /datum/outfit/plasmaman/viro
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
index 22e3f9b0f1..c909342d6f 100644
--- a/code/modules/jobs/job_types/warden.dm
+++ b/code/modules/jobs/job_types/warden.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/warden
+ plasma_outfit = /datum/outfit/plasmaman/warden
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_MINERAL_STOREROOM) // See /datum/job/warden/get_access()
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
index 020776a75f..3c2c850e3c 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
@@ -1,9 +1,9 @@
-/datum/sprite_accessory/mam_tails/shark/datashark
+/datum/sprite_accessory/tails/mam_tails/shark/datashark
name = "DataShark"
icon_state = "datashark"
ckeys_allowed = list("rubyflamewing")
-/datum/sprite_accessory/mam_tails_animated/shark/datashark
+/datum/sprite_accessory/tails_animated/mam_tails_animated/shark/datashark
name = "DataShark"
icon_state = "datashark"
ckeys_allowed = list("rubyflamewing")
@@ -14,19 +14,19 @@
ckeys_allowed = list("rubyflamewing")
//Sabresune
-/datum/sprite_accessory/mam_ears/sabresune
+/datum/sprite_accessory/ears/mam_ears/sabresune
name = "Sabresune"
icon_state = "sabresune"
ckeys_allowed = list("poojawa")
extra = TRUE
extra_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_tails/sabresune
+/datum/sprite_accessory/tails/mam_tails/sabresune
name = "Sabresune"
icon_state = "sabresune"
ckeys_allowed = list("poojawa")
-/datum/sprite_accessory/mam_tails_animated/sabresune
+/datum/sprite_accessory/tails_animated/mam_tails_animated/sabresune
name = "Sabresune"
icon_state = "sabresune"
ckeys_allowed = list("poojawa")
@@ -37,17 +37,17 @@
ckeys_allowed = list("poojawa")
//Lunasune
-/datum/sprite_accessory/mam_ears/lunasune
+/datum/sprite_accessory/ears/mam_ears/lunasune
name = "lunasune"
icon_state = "lunasune"
ckeys_allowed = list("invader4352")
-/datum/sprite_accessory/mam_tails/lunasune
+/datum/sprite_accessory/tails/mam_tails/lunasune
name = "lunasune"
icon_state = "lunasune"
ckeys_allowed = list("invader4352")
-/datum/sprite_accessory/mam_tails_animated/lunasune
+/datum/sprite_accessory/tails_animated/mam_tails_animated/lunasune
name = "lunasune"
icon_state = "lunasune"
ckeys_allowed = list("invader4352")
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
index 6514cb4f80..4cb8d080ff 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
@@ -76,6 +76,9 @@
//For soft-restricting markings to species IDs
var/list/recommended_species
+/datum/sprite_accessory/proc/is_not_visible(var/mob/living/carbon/human/H, var/tauric) //return if the accessory shouldn't be shown
+ return FALSE
+
/datum/sprite_accessory/underwear
icon = 'icons/mob/clothing/underwear.dmi'
var/has_color = FALSE
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
index c8e7aca26d..899bf91081 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
@@ -7,6 +7,9 @@
mutant_part_string = "xenodorsal"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/xeno_dorsal/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["xenodorsal"] || H.dna.features["xenodorsal"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT)))
+
/datum/sprite_accessory/xeno_dorsal/standard
name = "Standard"
icon_state = "standard"
@@ -27,6 +30,9 @@
mutant_part_string = "tail"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/xeno_tail/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["xenotail"] || H.dna.features["xenotail"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+
/datum/sprite_accessory/xeno_tail/none
name = "None"
relevant_layers = null
@@ -43,6 +49,10 @@
mutant_part_string = "xhead"
relevant_layers = list(BODY_ADJ_LAYER)
+/datum/sprite_accessory/xeno_head/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["xenohead"] || H.dna.features["xenohead"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/xeno_head/standard
name = "Standard"
icon_state = "standard"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
index 9950f0d76a..a04838a360 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
@@ -224,6 +224,9 @@
color_src = 0
relevant_layers = list(BODY_FRONT_LAYER)
+/datum/sprite_accessory/insect_fluff/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["insect_fluff"] || H.dna.features["insect_fluff"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+
/datum/sprite_accessory/insect_fluff/none
name = "None"
icon_state = "none"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
index bc269ccf62..1e6c797364 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
@@ -3,6 +3,10 @@
mutant_part_string = "ears"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/ears/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["ears"] || H.dna.features["ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/ears/none
name = "None"
icon_state = "none"
@@ -175,50 +179,54 @@
*************** Furry Ears ****************
*******************************************/
-/datum/sprite_accessory/mam_ears
+/datum/sprite_accessory/ears/mam_ears
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
color_src = MATRIXED
mutant_part_string = "ears"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_ears/none
+/datum/sprite_accessory/ears/mam_ears/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["mam_ears"] || H.dna.features["mam_ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
+
+/datum/sprite_accessory/ears/mam_ears/none
name = "None"
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/mam_ears/axolotl
+/datum/sprite_accessory/ears/mam_ears/axolotl
name = "Axolotl"
icon_state = "axolotl"
-/datum/sprite_accessory/mam_ears/bat
+/datum/sprite_accessory/ears/mam_ears/bat
name = "Bat"
icon_state = "bat"
-/datum/sprite_accessory/mam_ears/bear
+/datum/sprite_accessory/ears/mam_ears/bear
name = "Bear"
icon_state = "bear"
-/datum/sprite_accessory/mam_ears/bigwolf
+/datum/sprite_accessory/ears/mam_ears/bigwolf
name = "Big Wolf"
icon_state = "bigwolf"
-/datum/sprite_accessory/mam_ears/bigwolfinner
+/datum/sprite_accessory/ears/mam_ears/bigwolfinner
name = "Big Wolf (ALT)"
icon_state = "bigwolfinner"
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/mam_ears/bigwolfdark
+/datum/sprite_accessory/ears/mam_ears/bigwolfdark
name = "Dark Big Wolf"
icon_state = "bigwolfdark"
-/datum/sprite_accessory/mam_ears/bigwolfinnerdark
+/datum/sprite_accessory/ears/mam_ears/bigwolfinnerdark
name = "Dark Big Wolf (ALT)"
icon_state = "bigwolfinnerdark"
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/mam_ears/cat
+/datum/sprite_accessory/ears/mam_ears/cat
name = "Cat"
icon_state = "cat"
icon = 'icons/mob/mutant_bodyparts.dmi'
@@ -226,100 +234,100 @@
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/mam_ears/catbig
+/datum/sprite_accessory/ears/mam_ears/catbig
name = "Cat, Big"
icon_state = "catbig"
-/datum/sprite_accessory/mam_ears/cow
+/datum/sprite_accessory/ears/mam_ears/cow
name = "Cow"
icon_state = "cow"
-/datum/sprite_accessory/mam_ears/curled
+/datum/sprite_accessory/ears/mam_ears/curled
name = "Curled Horn"
icon_state = "horn1"
color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_ears/deer
+/datum/sprite_accessory/ears/mam_ears/deer
name = "Deer"
icon_state = "deer"
color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_ears/eevee
+/datum/sprite_accessory/ears/mam_ears/eevee
name = "Eevee"
icon_state = "eevee"
-/datum/sprite_accessory/mam_ears/elf
+/datum/sprite_accessory/ears/mam_ears/elf
name = "Elf"
icon_state = "elf"
color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_ears/elephant
+/datum/sprite_accessory/ears/mam_ears/elephant
name = "Elephant"
icon_state = "elephant"
-/datum/sprite_accessory/mam_ears/fennec
+/datum/sprite_accessory/ears/mam_ears/fennec
name = "Fennec"
icon_state = "fennec"
-/datum/sprite_accessory/mam_ears/fish
+/datum/sprite_accessory/ears/mam_ears/fish
name = "Fish"
icon_state = "fish"
-/datum/sprite_accessory/mam_ears/fox
+/datum/sprite_accessory/ears/mam_ears/fox
name = "Fox"
icon_state = "fox"
-/datum/sprite_accessory/mam_ears/husky
+/datum/sprite_accessory/ears/mam_ears/husky
name = "Husky"
icon_state = "wolf"
-/datum/sprite_accessory/mam_ears/kangaroo
+/datum/sprite_accessory/ears/mam_ears/kangaroo
name = "kangaroo"
icon_state = "kangaroo"
-/datum/sprite_accessory/mam_ears/jellyfish
+/datum/sprite_accessory/ears/mam_ears/jellyfish
name = "Jellyfish"
icon_state = "jellyfish"
color_src = HAIR
-/datum/sprite_accessory/mam_ears/lab
+/datum/sprite_accessory/ears/mam_ears/lab
name = "Dog, Long"
icon_state = "lab"
-/datum/sprite_accessory/mam_ears/murid
+/datum/sprite_accessory/ears/mam_ears/murid
name = "Murid"
icon_state = "murid"
-/datum/sprite_accessory/mam_ears/otie
+/datum/sprite_accessory/ears/mam_ears/otie
name = "Otusian"
icon_state = "otie"
-/datum/sprite_accessory/mam_ears/squirrel
+/datum/sprite_accessory/ears/mam_ears/squirrel
name = "Squirrel"
icon_state = "squirrel"
-/datum/sprite_accessory/mam_ears/pede
+/datum/sprite_accessory/ears/mam_ears/pede
name = "Scolipede"
icon_state = "pede"
-/datum/sprite_accessory/mam_ears/rabbit
+/datum/sprite_accessory/ears/mam_ears/rabbit
name = "Rabbit"
icon_state = "rabbit"
-/datum/sprite_accessory/mam_ears/sergal
+/datum/sprite_accessory/ears/mam_ears/sergal
name = "Sergal"
icon_state = "sergal"
-/datum/sprite_accessory/mam_ears/skunk
+/datum/sprite_accessory/ears/mam_ears/skunk
name = "skunk"
icon_state = "skunk"
-/datum/sprite_accessory/mam_ears/wolf
+/datum/sprite_accessory/ears/mam_ears/wolf
name = "Wolf"
icon_state = "wolf"
-/datum/sprite_accessory/mam_ears/bunny
+/datum/sprite_accessory/ears/mam_ears/bunny
name = "Bunny"
icon_state = "bunny"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/frills.dm b/code/modules/mob/dead/new_player/sprite_accessories/frills.dm
index 0aaec309a4..18a76df116 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/frills.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/frills.dm
@@ -2,6 +2,10 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
relevant_layers = list(BODY_ADJ_LAYER)
+/datum/sprite_accessory/frills/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["frills"] || H.dna.features["frills"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/frills/none
name = "None"
icon_state = "none"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
index b39f48f858..3a65eaebfa 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
@@ -3,6 +3,10 @@
color_src = HORNCOLOR
relevant_layers = list(HORNS_LAYER)
+/datum/sprite_accessory/horns/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["horns"] || H.dna.features["horns"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/horns/none
name = "None"
icon_state = "none"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
index 2ec6da2da8..d41f5ace22 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
@@ -30,6 +30,9 @@
var/alt_taur_mode = NONE //Same as above.
var/hide_legs = USE_QUADRUPED_CLIP_MASK
+/datum/sprite_accessory/taur/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!tauric || (H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)))
+
/datum/sprite_accessory/taur/New()
switch(hide_legs)
if(USE_QUADRUPED_CLIP_MASK)
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
index 2c0db56ce4..7319652ebe 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
@@ -3,6 +3,10 @@
mutant_part_string = "snout"
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/snouts/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return ((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/snouts/sharp
name = "Sharp"
icon_state = "sharp"
@@ -150,136 +154,140 @@
************** Mammal Snouts **************
*******************************************/
-/datum/sprite_accessory/mam_snouts
+/datum/sprite_accessory/snouts/mam_snouts
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
recommended_species = list("mammal", "slimeperson", "insect", "podweak")
mutant_part_string = "snout"
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_snouts/none
+/datum/sprite_accessory/snouts/mam_snouts/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return ((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
+
+/datum/sprite_accessory/snouts/mam_snouts/none
name = "None"
icon_state = "none"
recommended_species = null
relevant_layers = null
-/datum/sprite_accessory/mam_snouts/bird
+/datum/sprite_accessory/snouts/mam_snouts/bird
name = "Beak"
icon_state = "bird"
-/datum/sprite_accessory/mam_snouts/bigbeak
+/datum/sprite_accessory/snouts/mam_snouts/bigbeak
name = "Big Beak"
icon_state = "bigbeak"
-/datum/sprite_accessory/mam_snouts/bug
+/datum/sprite_accessory/snouts/mam_snouts/bug
name = "Bug"
icon_state = "bug"
color_src = MUTCOLORS
extra2 = TRUE
extra2_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/elephant
+/datum/sprite_accessory/snouts/mam_snouts/elephant
name = "Elephant"
icon_state = "elephant"
extra = TRUE
extra_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/skulldog
+/datum/sprite_accessory/snouts/mam_snouts/skulldog
name = "Skulldog"
icon_state = "skulldog"
extra = TRUE
extra_color_src = MATRIXED
-/datum/sprite_accessory/mam_snouts/lcanid
+/datum/sprite_accessory/snouts/mam_snouts/lcanid
name = "Mammal, Long"
icon_state = "lcanid"
-/datum/sprite_accessory/mam_snouts/lcanidalt
+/datum/sprite_accessory/snouts/mam_snouts/lcanidalt
name = "Mammal, Long ALT"
icon_state = "lcanidalt"
-/datum/sprite_accessory/mam_snouts/scanid
+/datum/sprite_accessory/snouts/mam_snouts/scanid
name = "Mammal, Short"
icon_state = "scanid"
-/datum/sprite_accessory/mam_snouts/scanidalt
+/datum/sprite_accessory/snouts/mam_snouts/scanidalt
name = "Mammal, Short ALT"
icon_state = "scanidalt"
-/datum/sprite_accessory/mam_snouts/scanidalt2
+/datum/sprite_accessory/snouts/mam_snouts/scanidalt2
name = "Mammal, Short ALT 2"
icon_state = "scanidalt2"
-/datum/sprite_accessory/mam_snouts/wolf
+/datum/sprite_accessory/snouts/mam_snouts/wolf
name = "Mammal, Thick"
icon_state = "wolf"
-/datum/sprite_accessory/mam_snouts/wolfalt
+/datum/sprite_accessory/snouts/mam_snouts/wolfalt
name = "Mammal, Thick ALT"
icon_state = "wolfalt"
-/datum/sprite_accessory/mam_snouts/redpanda
+/datum/sprite_accessory/snouts/mam_snouts/redpanda
name = "WahCoon"
icon_state = "wah"
-/datum/sprite_accessory/mam_snouts/redpandaalt
+/datum/sprite_accessory/snouts/mam_snouts/redpandaalt
name = "WahCoon ALT"
icon_state = "wahalt"
-/datum/sprite_accessory/mam_snouts/rhino
+/datum/sprite_accessory/snouts/mam_snouts/rhino
name = "Horn"
icon_state = "rhino"
extra = TRUE
extra = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/rodent
+/datum/sprite_accessory/snouts/mam_snouts/rodent
name = "Rodent"
icon_state = "rodent"
-/datum/sprite_accessory/mam_snouts/husky
+/datum/sprite_accessory/snouts/mam_snouts/husky
name = "Husky"
icon_state = "husky"
-/datum/sprite_accessory/mam_snouts/otie
+/datum/sprite_accessory/snouts/mam_snouts/otie
name = "Otie"
icon_state = "otie"
-/datum/sprite_accessory/mam_snouts/pede
+/datum/sprite_accessory/snouts/mam_snouts/pede
name = "Scolipede"
icon_state = "pede"
-/datum/sprite_accessory/mam_snouts/sergal
+/datum/sprite_accessory/snouts/mam_snouts/sergal
name = "Sergal"
icon_state = "sergal"
-/datum/sprite_accessory/mam_snouts/shark
+/datum/sprite_accessory/snouts/mam_snouts/shark
name = "Shark"
icon_state = "shark"
-/datum/sprite_accessory/mam_snouts/hshark
+/datum/sprite_accessory/snouts/mam_snouts/hshark
name = "hShark"
icon_state = "hshark"
-/datum/sprite_accessory/mam_snouts/toucan
+/datum/sprite_accessory/snouts/mam_snouts/toucan
name = "Toucan"
icon_state = "toucan"
-/datum/sprite_accessory/mam_snouts/sharp
+/datum/sprite_accessory/snouts/mam_snouts/sharp
name = "Sharp"
icon_state = "sharp"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/round
+/datum/sprite_accessory/snouts/mam_snouts/round
name = "Round"
icon_state = "round"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/sharplight
+/datum/sprite_accessory/snouts/mam_snouts/sharplight
name = "Sharp + Light"
icon_state = "sharplight"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/roundlight
+/datum/sprite_accessory/snouts/mam_snouts/roundlight
name = "Round + Light"
icon_state = "roundlight"
color_src = MUTCOLORS
@@ -289,109 +297,109 @@
**************** Snouts *******************
*************but higher up*****************/
-/datum/sprite_accessory/mam_snouts/fbird
+/datum/sprite_accessory/snouts/mam_snouts/fbird
name = "Beak (Top)"
icon_state = "fbird"
-/datum/sprite_accessory/mam_snouts/fbigbeak
+/datum/sprite_accessory/snouts/mam_snouts/fbigbeak
name = "Big Beak (Top)"
icon_state = "fbigbeak"
-/datum/sprite_accessory/mam_snouts/fbug
+/datum/sprite_accessory/snouts/mam_snouts/fbug
name = "Bug (Top)"
icon_state = "fbug"
color_src = MUTCOLORS
extra2 = TRUE
extra2_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/felephant
+/datum/sprite_accessory/snouts/mam_snouts/felephant
name = "Elephant (Top)"
icon_state = "felephant"
extra = TRUE
extra_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/flcanid
+/datum/sprite_accessory/snouts/mam_snouts/flcanid
name = "Mammal, Long (Top)"
icon_state = "flcanid"
-/datum/sprite_accessory/mam_snouts/flcanidalt
+/datum/sprite_accessory/snouts/mam_snouts/flcanidalt
name = "Mammal, Long ALT (Top)"
icon_state = "flcanidalt"
-/datum/sprite_accessory/mam_snouts/fscanid
+/datum/sprite_accessory/snouts/mam_snouts/fscanid
name = "Mammal, Short (Top)"
icon_state = "fscanid"
-/datum/sprite_accessory/mam_snouts/fscanidalt
+/datum/sprite_accessory/snouts/mam_snouts/fscanidalt
name = "Mammal, Short ALT (Top)"
icon_state = "fscanidalt"
-/datum/sprite_accessory/mam_snouts/fscanidalt2
+/datum/sprite_accessory/snouts/mam_snouts/fscanidalt2
name = "Mammal, Short ALT 2 (Top)"
icon_state = "fscanidalt2"
-/datum/sprite_accessory/mam_snouts/fwolf
+/datum/sprite_accessory/snouts/mam_snouts/fwolf
name = "Mammal, Thick (Top)"
icon_state = "fwolf"
-/datum/sprite_accessory/mam_snouts/fwolfalt
+/datum/sprite_accessory/snouts/mam_snouts/fwolfalt
name = "Mammal, Thick ALT (Top)"
icon_state = "fwolfalt"
-/datum/sprite_accessory/mam_snouts/fredpanda
+/datum/sprite_accessory/snouts/mam_snouts/fredpanda
name = "WahCoon (Top)"
icon_state = "fwah"
-/datum/sprite_accessory/mam_snouts/frhino
+/datum/sprite_accessory/snouts/mam_snouts/frhino
name = "Horn (Top)"
icon_state = "frhino"
extra = TRUE
extra = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/frodent
+/datum/sprite_accessory/snouts/mam_snouts/frodent
name = "Rodent (Top)"
icon_state = "frodent"
-/datum/sprite_accessory/mam_snouts/fhusky
+/datum/sprite_accessory/snouts/mam_snouts/fhusky
name = "Husky (Top)"
icon_state = "fhusky"
-/datum/sprite_accessory/mam_snouts/fotie
+/datum/sprite_accessory/snouts/mam_snouts/fotie
name = "Otie (Top)"
icon_state = "fotie"
-/datum/sprite_accessory/mam_snouts/fpede
+/datum/sprite_accessory/snouts/mam_snouts/fpede
name = "Scolipede (Top)"
icon_state = "fpede"
-/datum/sprite_accessory/mam_snouts/fsergal
+/datum/sprite_accessory/snouts/mam_snouts/fsergal
name = "Sergal (Top)"
icon_state = "fsergal"
-/datum/sprite_accessory/mam_snouts/fshark
+/datum/sprite_accessory/snouts/mam_snouts/fshark
name = "Shark (Top)"
icon_state = "fshark"
-/datum/sprite_accessory/mam_snouts/ftoucan
+/datum/sprite_accessory/snouts/mam_snouts/ftoucan
name = "Toucan (Top)"
icon_state = "ftoucan"
-/datum/sprite_accessory/mam_snouts/fsharp
+/datum/sprite_accessory/snouts/mam_snouts/fsharp
name = "Sharp (Top)"
icon_state = "fsharp"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/fround
+/datum/sprite_accessory/snouts/mam_snouts/fround
name = "Round (Top)"
icon_state = "fround"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/fsharplight
+/datum/sprite_accessory/snouts/mam_snouts/fsharplight
name = "Sharp + Light (Top)"
icon_state = "fsharplight"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/froundlight
+/datum/sprite_accessory/snouts/mam_snouts/froundlight
name = "Round + Light (Top)"
icon_state = "froundlight"
color_src = MUTCOLORS
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/spines.dm b/code/modules/mob/dead/new_player/sprite_accessories/spines.dm
index 54749d5ea9..83415ed375 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/spines.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/spines.dm
@@ -2,10 +2,16 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER)
+/datum/sprite_accessory/spines/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR))
+
/datum/sprite_accessory/spines_animated
icon = 'icons/mob/mutant_bodyparts.dmi'
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER)
+/datum/sprite_accessory/spines_animated/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return ((!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || H.dna.species.mutant_bodyparts["tail"])
+
/datum/sprite_accessory/spines/none
name = "None"
icon_state = "none"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
index a2884ab944..16531d8f06 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
@@ -1,24 +1,24 @@
//Synth snouts (This is the most important part)
-/datum/sprite_accessory/mam_snouts/synthliz
+/datum/sprite_accessory/snouts/mam_snouts/synthliz
recommended_species = list("synthliz")
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MUTCOLORS
name = "Synthetic Lizard - Snout"
icon_state = "synthliz_basic"
-/datum/sprite_accessory/mam_snouts/synthliz/synthliz_under
+/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_under
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MATRIXED
name = "Synthetic Lizard - Snout Under"
icon_state = "synthliz_under"
-/datum/sprite_accessory/mam_snouts/synthliz/synthliz_tert
+/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_tert
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MATRIXED
name = "Synthetic Lizard - Snout Tertiary"
icon_state = "synthliz_tert"
-/datum/sprite_accessory/mam_snouts/synthliz/synthliz_tertunder
+/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_tertunder
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MATRIXED
name = "Synthetic Lizard - Snout Tertiary Under"
@@ -42,14 +42,14 @@
icon_state = "synthlizpecslight"
//Synth tails
-/datum/sprite_accessory/mam_tails/synthliz
+/datum/sprite_accessory/tails/mam_tails/synthliz
recommended_species = list("synthliz")
icon = 'modular_citadel/icons/mob/synthliz_tails.dmi'
color_src = MUTCOLORS
name = "Synthetic Lizard"
icon_state = "synthliz"
-/datum/sprite_accessory/mam_tails_animated/synthliz
+/datum/sprite_accessory/tails_animated/mam_tails_animated/synthliz
recommended_species = list("synthliz")
icon = 'modular_citadel/icons/mob/synthliz_tails.dmi'
color_src = MUTCOLORS
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
index 33dbd7059f..812e0c052c 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
@@ -3,6 +3,9 @@
mutant_part_string = "tail"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/tails/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return ((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
+
/datum/sprite_accessory/tails_animated
icon = 'icons/mob/mutant_bodyparts.dmi'
mutant_part_string = "tailwag"
@@ -12,6 +15,9 @@
************* Lizard Tails ****************
*******************************************/
+/datum/sprite_accessory/tails_animated/lizard/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric) || H.dna.species.mutant_bodyparts["tail_lizard"])
+
/datum/sprite_accessory/tails/lizard/smooth
name = "Smooth"
icon_state = "smooth"
@@ -98,6 +104,9 @@
icon_state = "none"
relevant_layers = null
+/datum/sprite_accessory/tails_animated/human/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)|| H.dna.species.mutant_bodyparts["tail_human"])
+
/datum/sprite_accessory/tails/human/ailurus
name = "Red Panda"
icon_state = "wah"
@@ -122,22 +131,22 @@
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = MATRIXED
-/datum/sprite_accessory/mam_tails/batl
+/datum/sprite_accessory/tails/mam_tails/batl
name = "Bat (Long)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "batl"
-/datum/sprite_accessory/mam_tails_animated/batl
+/datum/sprite_accessory/tails_animated/mam_tails_animated/batl
name = "Bat (Long)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "batl"
-/datum/sprite_accessory/mam_tails/bats
+/datum/sprite_accessory/tails/mam_tails/bats
name = "Bat (Short)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "bats"
-/datum/sprite_accessory/mam_tails_animated/bats
+/datum/sprite_accessory/tails_animated/mam_tails_animated/bats
name = "Bat (Short)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "bats"
@@ -518,368 +527,371 @@
************** Furry Tails ****************
*******************************************/
-/datum/sprite_accessory/mam_tails
+/datum/sprite_accessory/tails/mam_tails
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
recommended_species = list("mammal", "slimeperson", "podweak", "felinid", "insect")
mutant_part_string = "tail"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_tails/none
+/datum/sprite_accessory/tails/mam_tails/none
name = "None"
icon_state = "none"
recommended_species = null
relevant_layers = null
-/datum/sprite_accessory/mam_tails_animated
+/datum/sprite_accessory/tails_animated/mam_tails_animated
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
mutant_part_string = "tailwag"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_tails_animated/none
+/datum/sprite_accessory/tails_animated/mam_tails_animated/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric) || H.dna.species.mutant_bodyparts["mam_tail"])
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/none
name = "None"
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/mam_tails/ailurus
+/datum/sprite_accessory/tails/mam_tails/ailurus
name = "Red Panda"
icon_state = "wah"
extra = TRUE
-/datum/sprite_accessory/mam_tails_animated/ailurus
+/datum/sprite_accessory/tails_animated/mam_tails_animated/ailurus
name = "Red Panda"
icon_state = "wah"
extra = TRUE
-/datum/sprite_accessory/mam_tails/axolotl
+/datum/sprite_accessory/tails/mam_tails/axolotl
name = "Axolotl"
icon_state = "axolotl"
-/datum/sprite_accessory/mam_tails_animated/axolotl
+/datum/sprite_accessory/tails_animated/mam_tails_animated/axolotl
name = "Axolotl"
icon_state = "axolotl"
-/datum/sprite_accessory/mam_tails/batl
+/datum/sprite_accessory/tails/mam_tails/batl
name = "Bat (Long)"
icon_state = "batl"
-/datum/sprite_accessory/mam_tails_animated/batl
+/datum/sprite_accessory/tails_animated/mam_tails_animated/batl
name = "Bat (Long)"
icon_state = "batl"
-/datum/sprite_accessory/mam_tails/bats
+/datum/sprite_accessory/tails/mam_tails/bats
name = "Bat (Short)"
icon_state = "bats"
-/datum/sprite_accessory/mam_tails_animated/bats
+/datum/sprite_accessory/tails_animated/mam_tails_animated/bats
name = "Bat (Short)"
icon_state = "bats"
-/datum/sprite_accessory/mam_tails/bee
+/datum/sprite_accessory/tails/mam_tails/bee
name = "Bee"
icon_state = "bee"
-/datum/sprite_accessory/mam_tails_animated/bee
+/datum/sprite_accessory/tails_animated/mam_tails_animated/bee
name = "Bee"
icon_state = "bee"
-/datum/sprite_accessory/mam_tails/cat
+/datum/sprite_accessory/tails/mam_tails/cat
name = "Cat"
icon_state = "cat"
color_src = HAIR
-/datum/sprite_accessory/mam_tails_animated/cat
+/datum/sprite_accessory/tails_animated/mam_tails_animated/cat
name = "Cat"
icon_state = "cat"
color_src = HAIR
-/datum/sprite_accessory/mam_tails/catbig
+/datum/sprite_accessory/tails/mam_tails/catbig
name = "Cat, Big"
icon_state = "catbig"
-/datum/sprite_accessory/mam_tails_animated/catbig
+/datum/sprite_accessory/tails_animated/mam_tails_animated/catbig
name = "Cat, Big"
icon_state = "catbig"
-/datum/sprite_accessory/mam_tails/twocat
+/datum/sprite_accessory/tails/mam_tails/twocat
name = "Cat, Double"
icon_state = "twocat"
-/datum/sprite_accessory/mam_tails_animated/twocat
+/datum/sprite_accessory/tails_animated/mam_tails_animated/twocat
name = "Cat, Double"
icon_state = "twocat"
-/datum/sprite_accessory/mam_tails/corvid
+/datum/sprite_accessory/tails/mam_tails/corvid
name = "Corvid"
icon_state = "crow"
-/datum/sprite_accessory/mam_tails_animated/corvid
+/datum/sprite_accessory/tails_animated/mam_tails_animated/corvid
name = "Corvid"
icon_state = "crow"
-/datum/sprite_accessory/mam_tail/cow
+/datum/sprite_accessory/tails/mam_tail/cow
name = "Cow"
icon_state = "cow"
-/datum/sprite_accessory/mam_tails_animated/cow
+/datum/sprite_accessory/tails_animated/mam_tails_animated/cow
name = "Cow"
icon_state = "cow"
-/datum/sprite_accessory/mam_tails/eevee
+/datum/sprite_accessory/tails/mam_tails/eevee
name = "Eevee"
icon_state = "eevee"
-/datum/sprite_accessory/mam_tails_animated/eevee
+/datum/sprite_accessory/tails_animated/mam_tails_animated/eevee
name = "Eevee"
icon_state = "eevee"
-/datum/sprite_accessory/mam_tails/fennec
+/datum/sprite_accessory/tails/mam_tails/fennec
name = "Fennec"
icon_state = "fennec"
-/datum/sprite_accessory/mam_tails_animated/fennec
+/datum/sprite_accessory/tails_animated/mam_tails_animated/fennec
name = "Fennec"
icon_state = "fennec"
-/datum/sprite_accessory/mam_tails/human/fish
+/datum/sprite_accessory/tails/mam_tails/human/fish
name = "Fish"
icon_state = "fish"
-/datum/sprite_accessory/mam_tails_animated/human/fish
+/datum/sprite_accessory/tails_animated/mam_tails_animated/human/fish
name = "Fish"
icon_state = "fish"
-/datum/sprite_accessory/mam_tails/fox
+/datum/sprite_accessory/tails/mam_tails/fox
name = "Fox"
icon_state = "fox"
-/datum/sprite_accessory/mam_tails_animated/fox
+/datum/sprite_accessory/tails_animated/mam_tails_animated/fox
name = "Fox"
icon_state = "fox"
-/datum/sprite_accessory/mam_tails/hawk
+/datum/sprite_accessory/tails/mam_tails/hawk
name = "Hawk"
icon_state = "hawk"
-/datum/sprite_accessory/mam_tails_animated/hawk
+/datum/sprite_accessory/tails_animated/mam_tails_animated/hawk
name = "Hawk"
icon_state = "hawk"
-/datum/sprite_accessory/mam_tails/horse
+/datum/sprite_accessory/tails/mam_tails/horse
name = "Horse"
icon_state = "horse"
color_src = HAIR
-/datum/sprite_accessory/mam_tails_animated/horse
+/datum/sprite_accessory/tails_animated/mam_tails_animated/horse
name = "Horse"
icon_state = "horse"
color_src = HAIR
-/datum/sprite_accessory/mam_tails/husky
+/datum/sprite_accessory/tails/mam_tails/husky
name = "Husky"
icon_state = "husky"
-/datum/sprite_accessory/mam_tails_animated/husky
+/datum/sprite_accessory/tails_animated/mam_tails_animated/husky
name = "Husky"
icon_state = "husky"
-datum/sprite_accessory/mam_tails/insect
+datum/sprite_accessory/tails/mam_tails/insect
name = "Insect"
icon_state = "insect"
-/datum/sprite_accessory/mam_tails_animated/insect
+/datum/sprite_accessory/tails_animated/mam_tails_animated/insect
name = "Insect"
icon_state = "insect"
-/datum/sprite_accessory/mam_tails/kangaroo
+/datum/sprite_accessory/tails/mam_tails/kangaroo
name = "kangaroo"
icon_state = "kangaroo"
-/datum/sprite_accessory/mam_tails_animated/kangaroo
+/datum/sprite_accessory/tails_animated/mam_tails_animated/kangaroo
name = "kangaroo"
icon_state = "kangaroo"
-/datum/sprite_accessory/mam_tails/kitsune
+/datum/sprite_accessory/tails/mam_tails/kitsune
name = "Kitsune"
icon_state = "kitsune"
-/datum/sprite_accessory/mam_tails_animated/kitsune
+/datum/sprite_accessory/tails_animated/mam_tails_animated/kitsune
name = "Kitsune"
icon_state = "kitsune"
-/datum/sprite_accessory/mam_tails/lab
+/datum/sprite_accessory/tails/mam_tails/lab
name = "Lab"
icon_state = "lab"
-/datum/sprite_accessory/mam_tails_animated/lab
+/datum/sprite_accessory/tails_animated/mam_tails_animated/lab
name = "Lab"
icon_state = "lab"
-/datum/sprite_accessory/mam_tails/murid
+/datum/sprite_accessory/tails/mam_tails/murid
name = "Murid"
icon_state = "murid"
-/datum/sprite_accessory/mam_tails_animated/murid
+/datum/sprite_accessory/tails_animated/mam_tails_animated/murid
name = "Murid"
icon_state = "murid"
-/datum/sprite_accessory/mam_tails/otie
+/datum/sprite_accessory/tails/mam_tails/otie
name = "Otusian"
icon_state = "otie"
-/datum/sprite_accessory/mam_tails_animated/otie
+/datum/sprite_accessory/tails_animated/mam_tails_animated/otie
name = "Otusian"
icon_state = "otie"
-/datum/sprite_accessory/mam_tails/orca
+/datum/sprite_accessory/tails/mam_tails/orca
name = "Orca"
icon_state = "orca"
-/datum/sprite_accessory/mam_tails_animated/orca
+/datum/sprite_accessory/tails_animated/mam_tails_animated/orca
name = "Orca"
icon_state = "orca"
-/datum/sprite_accessory/mam_tails/pede
+/datum/sprite_accessory/tails/mam_tails/pede
name = "Scolipede"
icon_state = "pede"
-/datum/sprite_accessory/mam_tails_animated/pede
+/datum/sprite_accessory/tails_animated/mam_tails_animated/pede
name = "Scolipede"
icon_state = "pede"
-/datum/sprite_accessory/mam_tails/rabbit
+/datum/sprite_accessory/tails/mam_tails/rabbit
name = "Rabbit"
icon_state = "rabbit"
-/datum/sprite_accessory/mam_tails_animated/rabbit
+/datum/sprite_accessory/tails_animated/mam_tails_animated/rabbit
name = "Rabbit"
icon_state = "rabbit"
-/datum/sprite_accessory/mam_tails/sergal
+/datum/sprite_accessory/tails/mam_tails/sergal
name = "Sergal"
icon_state = "sergal"
-/datum/sprite_accessory/mam_tails_animated/sergal
+/datum/sprite_accessory/tails_animated/mam_tails_animated/sergal
name = "Sergal"
icon_state = "sergal"
-/datum/sprite_accessory/mam_tails/skunk
+/datum/sprite_accessory/tails/mam_tails/skunk
name = "Skunk"
icon_state = "skunk"
-/datum/sprite_accessory/mam_tails_animated/skunk
+/datum/sprite_accessory/tails_animated/mam_tails_animated/skunk
name = "Skunk"
icon_state = "skunk"
-/datum/sprite_accessory/mam_tails/smooth
+/datum/sprite_accessory/tails/mam_tails/smooth
name = "Smooth"
icon_state = "smooth"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails_animated/smooth
+/datum/sprite_accessory/tails_animated/mam_tails_animated/smooth
name = "Smooth"
icon_state = "smooth"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails_animated/spikes
+/datum/sprite_accessory/tails_animated/mam_tails_animated/spikes
name = "Spikes"
icon_state = "spikes"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails/spikes
+/datum/sprite_accessory/tails/mam_tails/spikes
name = "Spikes"
icon_state = "spikes"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails/shark
+/datum/sprite_accessory/tails/mam_tails/shark
name = "Shark"
icon_state = "shark"
-/datum/sprite_accessory/mam_tails_animated/shark
+/datum/sprite_accessory/tails_animated/mam_tails_animated/shark
name = "Shark"
icon_state = "shark"
-/datum/sprite_accessory/mam_tails/shepherd
+/datum/sprite_accessory/tails/mam_tails/shepherd
name = "Shepherd"
icon_state = "shepherd"
-/datum/sprite_accessory/mam_tails_animated/shepherd
+/datum/sprite_accessory/tails_animated/mam_tails_animated/shepherd
name = "Shepherd"
icon_state = "shepherd"
-/datum/sprite_accessory/mam_tails/straighttail
+/datum/sprite_accessory/tails/mam_tails/straighttail
name = "Straight Tail"
icon_state = "straighttail"
-/datum/sprite_accessory/mam_tails_animated/straighttail
+/datum/sprite_accessory/tails_animated/mam_tails_animated/straighttail
name = "Straight Tail"
icon_state = "straighttail"
-/datum/sprite_accessory/mam_tails/squirrel
+/datum/sprite_accessory/tails/mam_tails/squirrel
name = "Squirrel"
icon_state = "squirrel"
-/datum/sprite_accessory/mam_tails_animated/squirrel
+/datum/sprite_accessory/tails_animated/mam_tails_animated/squirrel
name = "Squirrel"
icon_state = "squirrel"
-/datum/sprite_accessory/mam_tails/tamamo_kitsune
+/datum/sprite_accessory/tails/mam_tails/tamamo_kitsune
name = "Tamamo Kitsune Tails"
icon_state = "9sune"
-/datum/sprite_accessory/mam_tails_animated/tamamo_kitsune
+/datum/sprite_accessory/tails_animated/mam_tails_animated/tamamo_kitsune
name = "Tamamo Kitsune Tails"
icon_state = "9sune"
-/datum/sprite_accessory/mam_tails/tentacle
+/datum/sprite_accessory/tails/mam_tails/tentacle
name = "Tentacle"
icon_state = "tentacle"
-/datum/sprite_accessory/mam_tails_animated/tentacle
+/datum/sprite_accessory/tails_animated/mam_tails_animated/tentacle
name = "Tentacle"
icon_state = "tentacle"
-/datum/sprite_accessory/mam_tails/tiger
+/datum/sprite_accessory/tails/mam_tails/tiger
name = "Tiger"
icon_state = "tiger"
-/datum/sprite_accessory/mam_tails_animated/tiger
+/datum/sprite_accessory/tails_animated/mam_tails_animated/tiger
name = "Tiger"
icon_state = "tiger"
-/datum/sprite_accessory/mam_tails/dtiger
+/datum/sprite_accessory/tails/mam_tails/dtiger
name = "Dark Tiger"
icon_state = "dtiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails_animated/dtiger
+/datum/sprite_accessory/tails_animated/mam_tails_animated/dtiger
name = "Dark Tiger"
icon_state = "dtiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails/ltiger
+/datum/sprite_accessory/tails/mam_tails/ltiger
name = "Light Tiger"
icon_state = "ltiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails_animated/ltiger
+/datum/sprite_accessory/tails_animated/mam_tails_animated/ltiger
name = "Light Tiger"
icon_state = "ltiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails/wolf
+/datum/sprite_accessory/tails/mam_tails/wolf
name = "Wolf"
icon_state = "wolf"
-/datum/sprite_accessory/mam_tails_animated/wolf
+/datum/sprite_accessory/tails_animated/mam_tails_animated/wolf
name = "Wolf"
icon_state = "wolf"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
index 34767a10f1..fb03df88a1 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
@@ -5,10 +5,16 @@
icon_state = "none"
relevant_layers = null
+/datum/sprite_accessory/wings/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["wings"] || H.dna.features["wings"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception))))
+
/datum/sprite_accessory/wings_open
icon = 'icons/mob/wings.dmi'
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/wings_open/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)) || H.dna.species.mutant_bodyparts["wings"])
+
/datum/sprite_accessory/wings_open/angel
name = "Angel"
icon_state = "angel"
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index df049f672d..230a634e0e 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -642,106 +642,19 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(!mutant_bodyparts)
return
- var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
var/tauric = mutant_bodyparts["taur"] && H.dna.features["taur"] && H.dna.features["taur"] != "None"
- if(mutant_bodyparts["tail_lizard"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "tail_lizard"
-
- if(mutant_bodyparts["waggingtail_lizard"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "waggingtail_lizard"
- else if (mutant_bodyparts["tail_lizard"])
- bodyparts_to_add -= "waggingtail_lizard"
-
- if(mutant_bodyparts["tail_human"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "tail_human"
-
- if(mutant_bodyparts["waggingtail_human"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "waggingtail_human"
- else if (mutant_bodyparts["tail_human"])
- bodyparts_to_add -= "waggingtail_human"
-
- if(mutant_bodyparts["spines"])
- if(!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR))
- bodyparts_to_add -= "spines"
-
- if(mutant_bodyparts["waggingspines"])
- if(!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR))
- bodyparts_to_add -= "waggingspines"
- else if (mutant_bodyparts["tail"])
- bodyparts_to_add -= "waggingspines"
-
- if(mutant_bodyparts["snout"]) //Take a closer look at that snout!
- if((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "snout"
-
- if(mutant_bodyparts["frills"])
- if(!H.dna.features["frills"] || H.dna.features["frills"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "frills"
-
- if(mutant_bodyparts["horns"])
- if(!H.dna.features["horns"] || H.dna.features["horns"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "horns"
-
- if(mutant_bodyparts["ears"])
- if(!H.dna.features["ears"] || H.dna.features["ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "ears"
-
- if(mutant_bodyparts["wings"])
- if(!H.dna.features["wings"] || H.dna.features["wings"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception))))
- bodyparts_to_add -= "wings"
-
- if(mutant_bodyparts["wings_open"])
- if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)))
- bodyparts_to_add -= "wings_open"
- else if (mutant_bodyparts["wings"])
- bodyparts_to_add -= "wings_open"
-
- if(mutant_bodyparts["insect_fluff"])
- if(!H.dna.features["insect_fluff"] || H.dna.features["insect_fluff"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
- bodyparts_to_add -= "insect_fluff"
-
-//CITADEL EDIT
- //Race specific bodyparts:
- //Xenos
- if(mutant_bodyparts["xenodorsal"])
- if(!H.dna.features["xenodorsal"] || H.dna.features["xenodorsal"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT)))
- bodyparts_to_add -= "xenodorsal"
- if(mutant_bodyparts["xenohead"])//This is an overlay for different castes using different head crests
- if(!H.dna.features["xenohead"] || H.dna.features["xenohead"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "xenohead"
- if(mutant_bodyparts["xenotail"])
- if(!H.dna.features["xenotail"] || H.dna.features["xenotail"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
- bodyparts_to_add -= "xenotail"
-
- //Other Races
- if(mutant_bodyparts["mam_tail"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "mam_tail"
-
- if(mutant_bodyparts["mam_waggingtail"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "mam_waggingtail"
- else if (mutant_bodyparts["mam_tail"])
- bodyparts_to_add -= "mam_waggingtail"
-
- if(mutant_bodyparts["mam_ears"])
- if(!H.dna.features["mam_ears"] || H.dna.features["mam_ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "mam_ears"
-
- if(mutant_bodyparts["mam_snouts"]) //Take a closer look at that snout!
- if((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "mam_snouts"
-
- if(mutant_bodyparts["taur"])
- if(!tauric || (H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)))
- bodyparts_to_add -= "taur"
-
-//END EDIT
+ for(var/mutant_part in mutant_bodyparts)
+ var/reference_list = GLOB.mutant_reference_list[mutant_part]
+ if(reference_list)
+ var/datum/sprite_accessory/S
+ var/transformed_part = GLOB.mutant_transform_list[mutant_part]
+ if(transformed_part)
+ S = reference_list[H.dna.features[transformed_part]]
+ else
+ S = reference_list[H.dna.features[mutant_part]]
+ if(!S || S.is_not_visible(H, tauric))
+ bodyparts_to_add -= mutant_part
//Digitigrade legs are stuck in the phantom zone between true limbs and mutant bodyparts. Mainly it just needs more agressive updating than most limbs.
var/update_needed = FALSE
@@ -778,76 +691,22 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/list/dna_feature_as_text_string = list()
for(var/bodypart in bodyparts_to_add)
- var/datum/sprite_accessory/S
- switch(bodypart)
- if("tail_lizard")
- S = GLOB.tails_list_lizard[H.dna.features["tail_lizard"]]
- if("waggingtail_lizard")
- S = GLOB.animated_tails_list_lizard[H.dna.features["tail_lizard"]]
- if("tail_human")
- S = GLOB.tails_list_human[H.dna.features["tail_human"]]
- if("waggingtail_human")
- S = GLOB.animated_tails_list_human[H.dna.features["tail_human"]]
- if("spines")
- S = GLOB.spines_list[H.dna.features["spines"]]
- if("waggingspines")
- S = GLOB.animated_spines_list[H.dna.features["spines"]]
- if("snout")
- S = GLOB.snouts_list[H.dna.features["snout"]]
- if("frills")
- S = GLOB.frills_list[H.dna.features["frills"]]
- if("horns")
- S = GLOB.horns_list[H.dna.features["horns"]]
- if("ears")
- S = GLOB.ears_list[H.dna.features["ears"]]
- if("body_markings")
- S = GLOB.body_markings_list[H.dna.features["body_markings"]]
- if("wings")
- S = GLOB.wings_list[H.dna.features["wings"]]
- if("wingsopen")
- S = GLOB.wings_open_list[H.dna.features["wings"]]
- if("deco_wings")
- S = GLOB.deco_wings_list[H.dna.features["deco_wings"]]
- if("legs")
- S = GLOB.legs_list[H.dna.features["legs"]]
- if("insect_wings")
- S = GLOB.insect_wings_list[H.dna.features["insect_wings"]]
- if("insect_fluff")
- S = GLOB.insect_fluffs_list[H.dna.features["insect_fluff"]]
- if("insect_markings")
- S = GLOB.insect_markings_list[H.dna.features["insect_markings"]]
- if("caps")
- S = GLOB.caps_list[H.dna.features["caps"]]
- if("ipc_screen")
- S = GLOB.ipc_screens_list[H.dna.features["ipc_screen"]]
- if("ipc_antenna")
- S = GLOB.ipc_antennas_list[H.dna.features["ipc_antenna"]]
- if("mam_tail")
- S = GLOB.mam_tails_list[H.dna.features["mam_tail"]]
- if("mam_waggingtail")
- S = GLOB.mam_tails_animated_list[H.dna.features["mam_tail"]]
- if("mam_body_markings")
- S = GLOB.mam_body_markings_list[H.dna.features["mam_body_markings"]]
- if("mam_ears")
- S = GLOB.mam_ears_list[H.dna.features["mam_ears"]]
- if("mam_snouts")
- S = GLOB.mam_snouts_list[H.dna.features["mam_snouts"]]
- if("taur")
- S = GLOB.taur_list[H.dna.features["taur"]]
- if("xenodorsal")
- S = GLOB.xeno_dorsal_list[H.dna.features["xenodorsal"]]
- if("xenohead")
- S = GLOB.xeno_head_list[H.dna.features["xenohead"]]
- if("xenotail")
- S = GLOB.xeno_tail_list[H.dna.features["xenotail"]]
+ var/reference_list = GLOB.mutant_reference_list[bodypart]
+ if(reference_list)
+ var/datum/sprite_accessory/S
+ var/transformed_part = GLOB.mutant_transform_list[bodypart]
+ if(transformed_part)
+ S = reference_list[H.dna.features[transformed_part]]
+ else
+ S = reference_list[H.dna.features[bodypart]]
- if(!S || S.icon_state == "none")
- continue
+ if(!S || S.icon_state == "none")
+ continue
- for(var/L in S.relevant_layers)
- LAZYADD(relevant_layers["[L]"], S)
- if(!S.mutant_part_string)
- dna_feature_as_text_string[S] = bodypart
+ for(var/L in S.relevant_layers)
+ LAZYADD(relevant_layers["[L]"], S)
+ if(!S.mutant_part_string)
+ dna_feature_as_text_string[S] = bodypart
var/static/list/layer_text = list(
"[BODY_BEHIND_LAYER]" = "BEHIND",
@@ -2046,17 +1905,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
return
/**
-
-
-
* The human species version of [/mob/living/carbon/proc/get_biological_state]. Depends on the HAS_FLESH and HAS_BONE species traits, having bones lets you have bone wounds, having flesh lets you have burn, slash, and piercing wounds
-
-
-
*/
-
-
-
/datum/species/proc/get_biological_state(mob/living/carbon/human/H)
. = BIO_INORGANIC
if(HAS_FLESH in species_traits)
@@ -2072,7 +1922,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(HAS_TRAIT(H, TRAIT_NOBREATH))
return TRUE
-
/datum/species/proc/handle_environment(datum/gas_mixture/environment, mob/living/carbon/human/H)
if(!environment)
return
@@ -2264,7 +2113,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
/datum/species/proc/spec_stun(mob/living/carbon/human/H,amount)
if(H)
stop_wagging_tail(H)
-
. = stunmod * H.physiology.stun_mod * amount
//////////////
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index cda6faded0..cd9b3b80c7 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -27,7 +27,7 @@
// Cold temperatures hurt faster as it is harder to move with out the heat energy
bodytemp_cold_damage_limit = (T20C - 10) // about 10c
*/
- hair_color = "fixedmutcolor"
+ hair_color = "mutcolor"
hair_alpha = 140
var/current_color
var/EMPeffect = FALSE
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index c762a52ffc..49c55f70b3 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -504,7 +504,7 @@
else if (select_alteration == "Ears")
var/list/snowflake_ears_list = list("Normal" = null)
for(var/path in GLOB.mam_ears_list)
- var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path]
+ var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
@@ -518,7 +518,7 @@
else if (select_alteration == "Snout")
var/list/snowflake_snouts_list = list("Normal" = null)
for(var/path in GLOB.mam_snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
@@ -549,7 +549,7 @@
else if (select_alteration == "Tail")
var/list/snowflake_tails_list = list("Normal" = null)
for(var/path in GLOB.mam_tails_list)
- var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path]
+ var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index a55952d62f..4901fad892 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -11,7 +11,7 @@
mutanttail = /obj/item/organ/tail/lizard
coldmod = 1.5
heatmod = 0.67
- mutant_bodyparts = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round",
+ mutant_bodyparts = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "mam_snouts" = "Round",
"horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None",
"legs" = "Digitigrade", "taur" = "None", "deco_wings" = "None")
attack_verb = "slash"
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index b61e29b685..8c30adb8f9 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -57,89 +57,10 @@
..()
/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE)
- var/current_job = J?.title
var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman
- switch(current_job)
- if("Chaplain")
- O = new /datum/outfit/plasmaman/chaplain
-
- if("Curator")
- O = new /datum/outfit/plasmaman/curator
-
- if("Janitor")
- O = new /datum/outfit/plasmaman/janitor
-
- if("Botanist")
- O = new /datum/outfit/plasmaman/botany
-
- if("Bartender", "Lawyer")
- O = new /datum/outfit/plasmaman/bar
-
- if("Cook")
- O = new /datum/outfit/plasmaman/chef
-
- if("Security Officer")
- O = new /datum/outfit/plasmaman/security
-
- if("Detective")
- O = new /datum/outfit/plasmaman/detective
-
- if("Warden")
- O = new /datum/outfit/plasmaman/warden
-
- if("Cargo Technician", "Quartermaster")
- O = new /datum/outfit/plasmaman/cargo
-
- if("Shaft Miner")
- O = new /datum/outfit/plasmaman/mining
-
- if("Medical Doctor")
- O = new /datum/outfit/plasmaman/medical
-
- if("Chemist")
- O = new /datum/outfit/plasmaman/chemist
-
- if("Geneticist")
- O = new /datum/outfit/plasmaman/genetics
-
- if("Roboticist")
- O = new /datum/outfit/plasmaman/robotics
-
- if("Virologist")
- O = new /datum/outfit/plasmaman/viro
-
- if("Scientist")
- O = new /datum/outfit/plasmaman/science
-
- if("Station Engineer")
- O = new /datum/outfit/plasmaman/engineering
-
- if("Atmospheric Technician")
- O = new /datum/outfit/plasmaman/atmospherics
-
- if("Captain")
- O = new /datum/outfit/plasmaman/captain
-
- if("Head of Personnel")
- O = new /datum/outfit/plasmaman/hop
-
- if("Head of Security")
- O = new /datum/outfit/plasmaman/hos
-
- if("Chief Engineer")
- O = new /datum/outfit/plasmaman/ce
-
- if("Chief Medical Officer")
- O = new /datum/outfit/plasmaman/cmo
-
- if("Research Director")
- O = new /datum/outfit/plasmaman/rd
-
- if("Mime")
- O = new /datum/outfit/plasmaman/mime
-
- if("Clown")
- O = new /datum/outfit/plasmaman/clown
+ if(J)
+ if(J.plasma_outfit)
+ O = new J.plasma_outfit
H.equipOutfit(O, visualsOnly)
H.internal = H.get_item_for_held_index(2)
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index b760af9850..228a69c30f 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -82,13 +82,11 @@
M.AddSpell(SW)
shadowwalk = SW
-
/obj/item/organ/brain/nightmare/Remove(special = FALSE)
if(shadowwalk && owner)
owner.RemoveSpell(shadowwalk)
return ..()
-
/obj/item/organ/heart/nightmare
name = "heart of darkness"
desc = "An alien organ that twists and writhes when exposed to light."
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index c504e5ccd3..1b6bef9dc6 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -24,7 +24,7 @@
burnmod = 1
..()
-/datum/species/skeleton/greater/check_roundstart_eligible()
+/datum/species/skeleton/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
return TRUE
return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/synthliz.dm b/code/modules/mob/living/carbon/human/species_types/synthliz.dm
index 5ed806b302..173411eaa1 100644
--- a/code/modules/mob/living/carbon/human/species_types/synthliz.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synthliz.dm
@@ -22,4 +22,4 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "robotic"
\ No newline at end of file
+ species_type = "robotic"
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
index a1a2a33c54..c10521dfd9 100644
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synths.dm
@@ -100,14 +100,12 @@
else
return ..()
-
/datum/species/synth/handle_body(mob/living/carbon/human/H)
if(fake_species)
fake_species.handle_body(H)
else
return ..()
-
/datum/species/synth/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour)
if(fake_species)
fake_species.handle_body(H,forced_colour)
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index 5c83482bde..e7c5644e26 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -313,7 +313,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/insectguts,
/obj/effect/decal/cleanable/semen,
- /obj/effect/decal/cleanable/femcum,
+ /obj/effect/decal/cleanable/semen/femcum,
/obj/effect/decal/cleanable/generic,
/obj/effect/decal/cleanable/glass,,
/obj/effect/decal/cleanable/cobweb,
diff --git a/code/modules/plumbing/ducts.dm b/code/modules/plumbing/ducts.dm
new file mode 100644
index 0000000000..8a27f2669c
--- /dev/null
+++ b/code/modules/plumbing/ducts.dm
@@ -0,0 +1,433 @@
+/*
+All the important duct code:
+/code/datums/components/plumbing/plumbing.dm
+/code/datums/ductnet.dm
+*/
+/obj/machinery/duct
+ name = "fluid duct"
+ icon = 'icons/obj/plumbing/fluid_ducts.dmi'
+ icon_state = "nduct"
+
+ ///bitfield with the directions we're connected in
+ var/connects
+ ///set to TRUE to disable smart duct behaviour
+ var/dumb = FALSE
+ ///wheter we allow our connects to be changed after initialization or not
+ var/lock_connects = FALSE
+ ///our ductnet, wich tracks what we're connected to
+ var/datum/ductnet/duct
+ ///amount we can transfer per process. note that the ductnet can carry as much as the lowest capacity duct
+ var/capacity = 10
+
+ ///the color of our duct
+ var/duct_color = null
+ ///TRUE to ignore colors, so yeah we also connect with other colors without issue
+ var/ignore_colors = FALSE
+ ///1,2,4,8,16
+ var/duct_layer = DUCT_LAYER_DEFAULT
+ ///whether we allow our layers to be altered
+ var/lock_layers = FALSE
+ ///TRUE to let colors connect when forced with a wrench, false to just not do that at all
+ var/color_to_color_support = TRUE
+ ///wheter to even bother with plumbing code or not
+ var/active = TRUE
+ ///track ducts we're connected to. Mainly for ducts we connect to that we normally wouldn't, like different layers and colors, for when we regenerate the ducts
+ var/list/neighbours = list()
+ ///wheter we just unanchored or drop whatever is in the variable. either is safe
+ var/drop_on_wrench = /obj/item/stack/ducts
+
+/obj/machinery/duct/Initialize(mapload, no_anchor, color_of_duct = "#ffffff", layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
+ . = ..()
+
+ if(no_anchor)
+ active = FALSE
+ set_anchored(FALSE)
+ else if(!can_anchor())
+ qdel(src)
+ CRASH("Overlapping ducts detected")
+
+ if(force_connects)
+ connects = force_connects //skip change_connects() because we're still initializing and we need to set our connects at one point
+ if(!lock_layers)
+ duct_layer = layer_of_duct
+ if(!ignore_colors)
+ duct_color = color_of_duct
+ if(duct_color)
+ add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
+
+ handle_layer()
+
+ for(var/obj/machinery/duct/D in loc)
+ if(D == src)
+ continue
+ if(D.duct_layer & duct_layer)
+ disconnect_duct()
+
+ if(active)
+ attempt_connect()
+
+
+///start looking around us for stuff to connect to
+/obj/machinery/duct/proc/attempt_connect()
+
+ for(var/atom/movable/AM in loc)
+ var/datum/component/plumbing/P = AM.GetComponent(/datum/component/plumbing)
+ if(P?.active)
+ disconnect_duct() //let's not built under plumbing machinery
+ return
+ for(var/D in GLOB.cardinals)
+ if(dumb && !(D & connects))
+ continue
+ for(var/atom/movable/AM in get_step(src, D))
+ if(connect_network(AM, D))
+ add_connects(D)
+ update_icon()
+
+///see if whatever we found can be connected to
+/obj/machinery/duct/proc/connect_network(atom/movable/AM, direction, ignore_color)
+ if(istype(AM, /obj/machinery/duct))
+ return connect_duct(AM, direction, ignore_color)
+
+ var/plumber = AM.GetComponent(/datum/component/plumbing)
+ if(!plumber)
+ return
+ return connect_plumber(plumber, direction)
+
+///connect to a duct
+/obj/machinery/duct/proc/connect_duct(obj/machinery/duct/D, direction, ignore_color)
+ var/opposite_dir = turn(direction, 180)
+ if(!active || !D.active)
+ return
+
+ if(!dumb && D.dumb && !(opposite_dir & D.connects))
+ return
+ if(dumb && D.dumb && !(connects & D.connects)) //we eliminated a few more scenarios in attempt connect
+ return
+
+ if((duct == D.duct) && duct)//check if we're not just comparing two null values
+ add_neighbour(D, direction)
+
+ D.add_connects(opposite_dir)
+ D.update_icon()
+ return TRUE //tell the current pipe to also update it's sprite
+ if(!(D in neighbours)) //we cool
+ if((duct_color != D.duct_color) && !(ignore_colors || D.ignore_colors))
+ return
+ if(!(duct_layer & D.duct_layer))
+ return
+
+ if(D.duct)
+ if(duct)
+ duct.assimilate(D.duct)
+ else
+ D.duct.add_duct(src)
+ else
+ if(duct)
+ duct.add_duct(D)
+ else
+ create_duct()
+ duct.add_duct(D)
+ add_neighbour(D, direction)
+ //tell our buddy its time to pass on the torch of connecting to pipes. This shouldn't ever infinitely loop since it only works on pipes that havent been inductrinated
+ D.attempt_connect()
+
+ return TRUE
+
+///connect to a plumbing object
+/obj/machinery/duct/proc/connect_plumber(datum/component/plumbing/P, direction)
+ var/opposite_dir = turn(direction, 180)
+ if(duct_layer != DUCT_LAYER_DEFAULT) //plumbing devices don't support multilayering. 3 is the default layer so we only use that. We can change this later
+ return FALSE
+
+ if(!P.active)
+ return
+
+ var/comp_directions = P.supply_connects + P.demand_connects //they should never, ever have supply and demand connects overlap or catastrophic failure
+ if(opposite_dir & comp_directions)
+ if(!duct)
+ create_duct()
+ if(duct.add_plumber(P, opposite_dir))
+ neighbours[P.parent] = direction
+ return TRUE
+
+///we disconnect ourself from our neighbours. we also destroy our ductnet and tell our neighbours to make a new one
+/obj/machinery/duct/proc/disconnect_duct(skipanchor)
+ if(!skipanchor) //since set_anchored calls us too.
+ set_anchored(FALSE)
+ active = FALSE
+ if(duct)
+ duct.remove_duct(src)
+ lose_neighbours()
+ reset_connects(0)
+ update_icon()
+ if(ispath(drop_on_wrench) && !QDELING(src))
+ new drop_on_wrench(drop_location())
+ qdel(src)
+
+///''''''''''''''''optimized''''''''''''''''' proc for quickly reconnecting after a duct net was destroyed
+/obj/machinery/duct/proc/reconnect()
+ if(neighbours.len && !duct)
+ create_duct()
+ for(var/atom/movable/AM in neighbours)
+ if(istype(AM, /obj/machinery/duct))
+ var/obj/machinery/duct/D = AM
+ if(D.duct)
+ if(D.duct == duct) //we're already connected
+ continue
+ else
+ duct.assimilate(D.duct)
+ continue
+ else
+ duct.add_duct(D)
+ D.reconnect()
+ else
+ var/datum/component/plumbing/P = AM.GetComponent(/datum/component/plumbing)
+ if(AM in get_step(src, neighbours[AM])) //did we move?
+ if(P)
+ connect_plumber(P, neighbours[AM])
+ else
+ neighbours -= AM //we moved
+
+///Special proc to draw a new connect frame based on neighbours. not the norm so we can support multiple duct kinds
+/obj/machinery/duct/proc/generate_connects()
+ if(lock_connects)
+ return
+ connects = 0
+ for(var/A in neighbours)
+ connects |= neighbours[A]
+ update_icon()
+
+///create a new duct datum
+/obj/machinery/duct/proc/create_duct()
+ duct = new()
+ duct.add_duct(src)
+
+///add a duct as neighbour. this means we're connected and will connect again if we ever regenerate
+/obj/machinery/duct/proc/add_neighbour(obj/machinery/duct/D, direction)
+ if(!(D in neighbours))
+ neighbours[D] = direction
+ if(!(src in D.neighbours))
+ D.neighbours[src] = turn(direction, 180)
+
+///remove all our neighbours, and remove us from our neighbours aswell
+/obj/machinery/duct/proc/lose_neighbours()
+ for(var/obj/machinery/duct/D in neighbours)
+ D.neighbours.Remove(src)
+ neighbours = list()
+
+///add a connect direction
+/obj/machinery/duct/proc/add_connects(new_connects) //make this a define to cut proc calls?
+ if(!lock_connects)
+ connects |= new_connects
+
+///remove a connect direction
+/obj/machinery/duct/proc/remove_connects(dead_connects)
+ if(!lock_connects)
+ connects &= ~dead_connects
+
+///remove our connects
+/obj/machinery/duct/proc/reset_connects()
+ if(!lock_connects)
+ connects = 0
+
+///get a list of the ducts we can connect to if we are dumb
+/obj/machinery/duct/proc/get_adjacent_ducts()
+ var/list/adjacents = list()
+ for(var/A in GLOB.cardinals)
+ if(A & connects)
+ for(var/obj/machinery/duct/D in get_step(src, A))
+ if((turn(A, 180) & D.connects) && D.active)
+ adjacents += D
+ return adjacents
+
+/obj/machinery/duct/update_icon_state()
+ var/temp_icon = initial(icon_state)
+ for(var/D in GLOB.cardinals)
+ if(D & connects)
+ if(D == NORTH)
+ temp_icon += "_n"
+ if(D == SOUTH)
+ temp_icon += "_s"
+ if(D == EAST)
+ temp_icon += "_e"
+ if(D == WEST)
+ temp_icon += "_w"
+ icon_state = temp_icon
+
+///update the layer we are on
+/obj/machinery/duct/proc/handle_layer()
+ var/offset
+ switch(duct_layer)//it's a bitfield, but it's fine because it only works when there's one layer, and multiple layers should be handled differently
+ if(FIRST_DUCT_LAYER)
+ offset = -10
+ if(SECOND_DUCT_LAYER)
+ offset = -5
+ if(THIRD_DUCT_LAYER)
+ offset = 0
+ if(FOURTH_DUCT_LAYER)
+ offset = 5
+ if(FIFTH_DUCT_LAYER)
+ offset = 10
+ pixel_x = offset
+ pixel_y = offset
+
+
+/obj/machinery/duct/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ if(anchorvalue)
+ active = TRUE
+ attempt_connect()
+ else
+ disconnect_duct(TRUE)
+
+/obj/machinery/duct/wrench_act(mob/living/user, obj/item/I) //I can also be the RPD
+ ..()
+ add_fingerprint(user)
+ I.play_tool_sound(src)
+ if(anchored || can_anchor())
+ set_anchored(!anchored)
+ user.visible_message( \
+ "[user] [anchored ? null : "un"]fastens \the [src].", \
+ "You [anchored ? null : "un"]fasten \the [src].", \
+ "You hear ratcheting.")
+ return TRUE
+///collection of all the sanity checks to prevent us from stacking ducts that shouldn't be stacked
+/obj/machinery/duct/proc/can_anchor(turf/T)
+ if(!T)
+ T = get_turf(src)
+ for(var/obj/machinery/duct/D in T)
+ if(!anchored || D == src)
+ continue
+ for(var/A in GLOB.cardinals)
+ if(A & connects && A & D.connects)
+ return FALSE
+ return TRUE
+
+/obj/machinery/duct/doMove(destination)
+ . = ..()
+ disconnect_duct()
+ anchored = FALSE
+
+/obj/machinery/duct/Destroy()
+ disconnect_duct()
+ return ..()
+
+/obj/machinery/duct/MouseDrop_T(atom/A, mob/living/user)
+ if(!istype(A, /obj/machinery/duct))
+ return
+ var/obj/machinery/duct/D = A
+ var/obj/item/I = user.get_active_held_item()
+ if(I?.tool_behaviour != TOOL_WRENCH)
+ to_chat(user, "You need to be holding a wrench in your active hand to do that!")
+ return
+ if(get_dist(src, D) != 1)
+ return
+ var/direction = get_dir(src, D)
+ if(!(direction in GLOB.cardinals))
+ return
+ if(duct_layer != D.duct_layer)
+ return
+
+ add_connects(direction) //the connect of the other duct is handled in connect_network, but do this here for the parent duct because it's not necessary in normal cases
+ add_neighbour(D, direction)
+ connect_network(D, direction, TRUE)
+ update_icon()
+
+///has a total of 5 layers and doesnt give a shit about color. its also dumb so doesnt autoconnect.
+/obj/machinery/duct/multilayered
+ name = "duct layer-manifold"
+ icon = 'icons/obj/2x2.dmi'
+ icon_state = "multiduct"
+ pixel_x = -15
+ pixel_y = -15
+
+ color_to_color_support = FALSE
+ duct_layer = FIRST_DUCT_LAYER | SECOND_DUCT_LAYER | THIRD_DUCT_LAYER | FOURTH_DUCT_LAYER | FIFTH_DUCT_LAYER
+ drop_on_wrench = null
+
+ lock_connects = TRUE
+ lock_layers = TRUE
+ ignore_colors = TRUE
+ dumb = TRUE
+
+ active = FALSE
+ anchored = FALSE
+
+/obj/machinery/duct/multilayered/Initialize(mapload, no_anchor, color_of_duct, layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
+ . = ..()
+ update_connects()
+
+/obj/machinery/duct/multilayered/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_blocker)
+
+/obj/machinery/duct/multilayered/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ update_connects()
+
+/obj/machinery/duct/multilayered/proc/update_connects()
+ if(dir & NORTH || dir & SOUTH)
+ connects = NORTH | SOUTH
+ else
+ connects = EAST | WEST
+
+///don't connect to other multilayered stuff because honestly it shouldn't be done and I dont wanna deal with it
+/obj/machinery/duct/multilayered/connect_duct(obj/machinery/duct/D, direction, ignore_color)
+ if(istype(D, /obj/machinery/duct/multilayered))
+ return
+ return ..()
+
+/obj/machinery/duct/multilayered/handle_layer()
+ return
+
+/obj/item/stack/ducts
+ name = "stack of duct"
+ desc = "A stack of fluid ducts."
+ singular_name = "duct"
+ icon = 'icons/obj/plumbing/fluid_ducts.dmi'
+ icon_state = "ducts"
+ custom_materials = list(/datum/material/iron=500)
+ w_class = WEIGHT_CLASS_TINY
+ novariants = FALSE
+ max_amount = 50
+ item_flags = NOBLUDGEON
+ merge_type = /obj/item/stack/ducts
+ ///Color of our duct
+ var/duct_color = "grey"
+ ///Default layer of our duct
+ var/duct_layer = "Default Layer"
+ ///Assoc index with all the available layers. yes five might be a bit much. Colors uses a global by the way
+ var/list/layers = list("First Layer" = FIRST_DUCT_LAYER, "Second Layer" = SECOND_DUCT_LAYER, "Default Layer" = DUCT_LAYER_DEFAULT,
+ "Fourth Layer" = FOURTH_DUCT_LAYER, "Fifth Layer" = FIFTH_DUCT_LAYER)
+
+/obj/item/stack/ducts/examine(mob/user)
+ . = ..()
+ . += "It's current color and layer are [duct_color] and [duct_layer]. Use in-hand to change."
+
+/obj/item/stack/ducts/attack_self(mob/user)
+ var/new_layer = input("Select a layer", "Layer") as null|anything in layers
+ if(new_layer)
+ duct_layer = new_layer
+ var/new_color = input("Select a color", "Color") as null|anything in GLOB.pipe_paint_colors
+ if(new_color)
+ duct_color = new_color
+ add_atom_colour(GLOB.pipe_paint_colors[new_color], FIXED_COLOUR_PRIORITY)
+
+/obj/item/stack/ducts/afterattack(atom/A, user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ if(istype(A, /obj/machinery/duct))
+ var/obj/machinery/duct/D = A
+ if(!D.anchored)
+ add(1)
+ qdel(D)
+ if(istype(A, /turf/open) && use(1))
+ var/turf/open/OT = A
+ new /obj/machinery/duct(OT, FALSE, GLOB.pipe_paint_colors[duct_color], layers[duct_layer])
+ playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
+
+/obj/item/stack/ducts/fifty
+ amount = 50
diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm
new file mode 100644
index 0000000000..0566945e3b
--- /dev/null
+++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm
@@ -0,0 +1,98 @@
+/**Basic plumbing object.
+* It doesn't really hold anything special, YET.
+* Objects that are plumbing but not a subtype are as of writing liquid pumps and the reagent_dispenser tank
+* Also please note that the plumbing component is toggled on and off by the component using a signal from default_unfasten_wrench, so dont worry about it
+*/
+/obj/machinery/plumbing
+ name = "pipe thing"
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ icon_state = "pump"
+ density = TRUE
+ active_power_usage = 30
+ use_power = ACTIVE_POWER_USE
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ ///Plumbing machinery is always gonna need reagents, so we might aswell put it here
+ var/buffer = 50
+ ///Flags for reagents, like INJECTABLE, TRANSPARENT bla bla everything thats in DEFINES/reagents.dm
+ var/reagent_flags = TRANSPARENT
+ ///wheter we partake in rcd construction or not
+ var/rcd_constructable = TRUE
+ ///cost of the plumbing rcd construction
+ var/rcd_cost = 15
+ ///delay of constructing it throught the plumbing rcd
+ var/rcd_delay = 10
+
+/obj/machinery/plumbing/Initialize(mapload, bolt = TRUE)
+ . = ..()
+ anchored = bolt
+ create_reagents(buffer, reagent_flags)
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+
+/obj/machinery/plumbing/proc/can_be_rotated(mob/user,rotation_type)
+ return TRUE
+
+
+/obj/machinery/plumbing/examine(mob/user)
+ . = ..()
+ . += "The maximum volume display reads: [reagents.maximum_volume] units."
+
+/obj/machinery/plumbing/wrench_act(mob/living/user, obj/item/I)
+ ..()
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/machinery/plumbing/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ to_chat(user, "You start furiously plunging [name].")
+ if(do_after(user, 30, target = src))
+ to_chat(user, "You finish plunging the [name].")
+ reagents.reaction(get_turf(src), TOUCH) //splash on the floor
+ reagents.clear_reagents()
+
+/obj/machinery/plumbing/welder_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(anchored)
+ to_chat(user, "The [name] needs to be unbolted to do that!You start slicing the [name] apart.You slice the [name] apart. target_temperature && acclimate_state != COOLING)
+ acclimate_state = COOLING
+ update_icon()
+ if(!emptying)
+ if(reagents.chem_temp >= target_temperature && target_temperature + allowed_temperature_difference >= reagents.chem_temp) //cooling here
+ emptying = TRUE
+ if(reagents.chem_temp <= target_temperature && target_temperature - allowed_temperature_difference <= reagents.chem_temp) //heating here
+ emptying = TRUE
+
+ reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
+ reagents.handle_reactions()
+
+/obj/machinery/plumbing/acclimator/update_icon()
+ icon_state = initial(icon_state)
+ switch(acclimate_state)
+ if(COOLING)
+ icon_state += "_cold"
+ if(HEATING)
+ icon_state += "_hot"
+
+/obj/machinery/plumbing/acclimator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemAcclimator", name)
+ ui.open()
+
+/obj/machinery/plumbing/acclimator/ui_data(mob/user)
+ var/list/data = list()
+
+ data["enabled"] = enabled
+ data["chem_temp"] = reagents.chem_temp
+ data["target_temperature"] = target_temperature
+ data["allowed_temperature_difference"] = allowed_temperature_difference
+ data["acclimate_state"] = acclimate_state
+ data["max_volume"] = reagents.maximum_volume
+ data["reagent_volume"] = reagents.total_volume
+ data["emptying"] = emptying
+ return data
+
+/obj/machinery/plumbing/acclimator/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("set_target_temperature")
+ var/target = text2num(params["temperature"])
+ target_temperature = clamp(target, 0, 1000)
+ if("set_allowed_temperature_difference")
+ var/target = text2num(params["temperature"])
+ allowed_temperature_difference = clamp(target, 0, 1000)
+ if("toggle_power")
+ enabled = !enabled
+ if("change_volume")
+ var/target = text2num(params["volume"])
+ reagents.maximum_volume = clamp(round(target), 1, buffer)
+
+#undef COOLING
+#undef HEATING
+#undef NEUTRAL
diff --git a/code/modules/plumbing/plumbers/autohydro.dm b/code/modules/plumbing/plumbers/autohydro.dm
new file mode 100644
index 0000000000..dbc70dfcf5
--- /dev/null
+++ b/code/modules/plumbing/plumbers/autohydro.dm
@@ -0,0 +1,65 @@
+/obj/machinery/hydroponics/constructable/automagic
+ name = "automated hydroponics system"
+ desc = "The bane of botanists everywhere. Accepts chemical reagents via plumbing, automatically harvests and removes dead plants."
+ obj_flags = CAN_BE_HIT | UNIQUE_RENAME
+ circuit = /obj/item/circuitboard/machine/hydroponics/automagic
+ self_sufficiency_req = 400 //automating hydroponics makes gaia sad so she needs more drugs to turn they tray godly.
+ canirrigate = FALSE
+
+
+/obj/machinery/hydroponics/constructable/automagic/attackby(obj/item/O, mob/user, params)
+ if(istype(O, /obj/item/reagent_containers))
+ return FALSE //avoid fucky wuckies
+ ..()
+
+/obj/machinery/hydroponics/constructable/automagic/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \
+ "You [anchored ? "fasten" : "unfasten"] [src]")
+ var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing)
+ if(anchored)
+ CP.enable()
+ else
+ CP.disable()
+
+/obj/machinery/hydroponics/constructable/automagic/Destroy()
+ . = ..()
+ STOP_PROCESSING(SSobj, src)
+
+/obj/machinery/hydroponics/constructable/automagic/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+ create_reagents(100 , AMOUNT_VISIBLE)
+
+/obj/machinery/hydroponics/constructable/automagic/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+ AddComponent(/datum/component/plumbing/simple_demand)
+
+/obj/machinery/hydroponics/constructable/proc/can_be_rotated(mob/user, rotation_type)
+ return !anchored
+
+/obj/machinery/hydroponics/constructable/automagic/process()
+ if(reagents)
+ applyChemicals(reagents)
+ reagents.clear_reagents()
+ if(dead)
+ dead = 0
+ qdel(myseed)
+ myseed = null
+ update_icon()
+ name = initial(name)
+ desc = initial(desc)
+ if(harvest)
+ myseed.harvest_userless()
+ harvest = 0
+ lastproduce = age
+ if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
+ qdel(myseed)
+ myseed = null
+ dead = 0
+ name = initial(name)
+ desc = initial(desc)
+ update_icon()
+ ..()
diff --git a/code/modules/plumbing/plumbers/bottler.dm b/code/modules/plumbing/plumbers/bottler.dm
new file mode 100644
index 0000000000..396c7cac22
--- /dev/null
+++ b/code/modules/plumbing/plumbers/bottler.dm
@@ -0,0 +1,79 @@
+/obj/machinery/plumbing/bottler
+ name = "chemical bottler"
+ desc = "Puts reagents into containers, like bottles and beakers."
+ icon_state = "bottler"
+ layer = ABOVE_ALL_MOB_LAYER
+ reagent_flags = TRANSPARENT | DRAINABLE
+ rcd_cost = 50
+ rcd_delay = 50
+ buffer = 100
+ ///how much do we fill
+ var/wanted_amount = 10
+ ///where things are sent
+ var/turf/goodspot = null
+ ///where things are taken
+ var/turf/inputspot = null
+ ///where beakers that are already full will be sent
+ var/turf/badspot = null
+
+/obj/machinery/plumbing/bottler/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+ setDir(dir)
+
+/obj/machinery/plumbing/bottler/can_be_rotated(mob/user, rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return FALSE
+ return TRUE
+
+///changes the tile array
+/obj/machinery/plumbing/bottler/setDir(newdir)
+ . = ..()
+ switch(dir)
+ if(NORTH)
+ goodspot = get_step(get_turf(src), NORTH)
+ inputspot = get_step(get_turf(src), SOUTH)
+ badspot = get_step(get_turf(src), EAST)
+ if(SOUTH)
+ goodspot = get_step(get_turf(src), SOUTH)
+ inputspot = get_step(get_turf(src), NORTH)
+ badspot = get_step(get_turf(src), WEST)
+ if(WEST)
+ goodspot = get_step(get_turf(src), WEST)
+ inputspot = get_step(get_turf(src), EAST)
+ badspot = get_step(get_turf(src), NORTH)
+ if(EAST)
+ goodspot = get_step(get_turf(src), EAST)
+ inputspot = get_step(get_turf(src), WEST)
+ badspot = get_step(get_turf(src), SOUTH)
+
+///changing input ammount with a window
+/obj/machinery/plumbing/bottler/interact(mob/user)
+ . = ..()
+ wanted_amount = clamp(round(input(user,"maximum is 100u","set ammount to fill with") as num|null, 1), 1, 100)
+ reagents.clear_reagents()
+ to_chat(user, " The [src] will now fill for [wanted_amount]u.")
+
+/obj/machinery/plumbing/bottler/process()
+ if(stat & NOPOWER)
+ return
+ ///see if machine has enough to fill
+ if(reagents.total_volume >= wanted_amount && anchored)
+ var/obj/AM = pick(inputspot.contents)///pick a reagent_container that could be used
+ if(istype(AM, /obj/item/reagent_containers) && (!istype(AM, /obj/item/reagent_containers/hypospray/medipen)))
+ var/obj/item/reagent_containers/B = AM
+ ///see if it would overflow else inject
+ if((B.reagents.total_volume + wanted_amount) <= B.reagents.maximum_volume)
+ reagents.trans_to(B, wanted_amount)
+ B.forceMove(goodspot)
+ return
+ ///glass was full so we move it away
+ AM.forceMove(badspot)
+ if(istype(AM, /obj/item/slime_extract)) ///slime extracts need inject
+ AM.forceMove(goodspot)
+ reagents.trans_to(AM, wanted_amount)
+ return
+ if(istype(AM, /obj/item/slimecross/industrial)) ///no need to move slimecross industrial things
+ reagents.trans_to(AM, wanted_amount)
+ return
diff --git a/code/modules/plumbing/plumbers/destroyer.dm b/code/modules/plumbing/plumbers/destroyer.dm
new file mode 100644
index 0000000000..b61383ea4a
--- /dev/null
+++ b/code/modules/plumbing/plumbers/destroyer.dm
@@ -0,0 +1,21 @@
+/obj/machinery/plumbing/disposer
+ name = "chemical disposer"
+ desc = "Breaks down chemicals and annihilates them."
+ icon_state = "disposal"
+ ///we remove 10 reagents per second
+ var/disposal_rate = 10
+
+/obj/machinery/plumbing/disposer/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+
+/obj/machinery/plumbing/disposer/process()
+ if(stat & NOPOWER)
+ return
+ if(reagents.total_volume)
+ if(icon_state != initial(icon_state) + "_working") //threw it here instead of update icon since it only has two states
+ icon_state = initial(icon_state) + "_working"
+ reagents.remove_any(disposal_rate)
+ else
+ if(icon_state != initial(icon_state))
+ icon_state = initial(icon_state)
diff --git a/code/modules/plumbing/plumbers/fermenter.dm b/code/modules/plumbing/plumbers/fermenter.dm
new file mode 100644
index 0000000000..b1e1e4b676
--- /dev/null
+++ b/code/modules/plumbing/plumbers/fermenter.dm
@@ -0,0 +1,59 @@
+/obj/machinery/plumbing/fermenter //FULLY AUTOMATIC BEER BREWING. TRULY, THE FUTURE.
+ name = "chemical fermenter"
+ desc = "Turns plants into various types of booze."
+ icon_state = "fermenter"
+ layer = ABOVE_ALL_MOB_LAYER
+ reagent_flags = TRANSPARENT | DRAINABLE
+ rcd_cost = 30
+ rcd_delay = 30
+ buffer = 400
+ ///input dir
+ var/eat_dir = SOUTH
+
+/obj/machinery/plumbing/fermenter/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+/obj/machinery/plumbing/fermenter/can_be_rotated(mob/user,rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return FALSE
+ switch(eat_dir)
+ if(WEST)
+ eat_dir = NORTH
+ return TRUE
+ if(EAST)
+ eat_dir = SOUTH
+ return TRUE
+ if(NORTH)
+ eat_dir = EAST
+ return TRUE
+ if(SOUTH)
+ eat_dir = WEST
+ return TRUE
+
+/obj/machinery/plumbing/fermenter/CanPass(atom/movable/AM)
+ . = ..()
+ if(!anchored)
+ return
+ var/move_dir = get_dir(loc, AM.loc)
+ if(move_dir == eat_dir)
+ return TRUE
+
+/obj/machinery/plumbing/fermenter/Crossed(atom/movable/AM)
+ . = ..()
+ ferment(AM)
+
+/obj/machinery/plumbing/fermenter/proc/ferment(atom/AM)
+ if(stat & NOPOWER)
+ return
+ if(reagents.holder_full())
+ return
+ if(!isitem(AM))
+ return
+ if(istype(AM, /obj/item/reagent_containers/food/snacks/grown))
+ var/obj/item/reagent_containers/food/snacks/grown/G = AM
+ if(G.distill_reagent)
+ var/amount = G.seed.potency * 0.25
+ reagents.add_reagent(G.distill_reagent, amount)
+ qdel(G)
diff --git a/code/modules/plumbing/plumbers/filter.dm b/code/modules/plumbing/plumbers/filter.dm
new file mode 100644
index 0000000000..1ffd170507
--- /dev/null
+++ b/code/modules/plumbing/plumbers/filter.dm
@@ -0,0 +1,65 @@
+///chemical plumbing filter. If it's not filtered by left and right, it goes straight.
+/obj/machinery/plumbing/filter
+ name = "chemical filter"
+ desc = "A chemical filter for filtering chemicals. The left and right outputs appear to be from the perspective of the input port."
+ icon_state = "filter"
+ density = FALSE
+
+ ///whitelist of chems id's that go to the left side. Empty to disable port
+ var/list/left = list()
+ ///whitelist of chem id's that go to the right side. Empty to disable port
+ var/list/right = list()
+ ///whitelist of chems but their name instead of path
+ var/list/english_left = list()
+ ///whitelist of chems but their name instead of path
+ var/list/english_right = list()
+
+/obj/machinery/plumbing/filter/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/filter, bolt)
+
+/obj/machinery/plumbing/filter/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemFilter", name)
+ ui.open()
+
+/obj/machinery/plumbing/filter/ui_data(mob/user)
+ var/list/data = list()
+ data["left"] = english_left
+ data["right"] = english_right
+ return data
+
+/obj/machinery/plumbing/filter/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("add")
+ var/new_chem_name = params["name"]
+ var/chem_id = get_chem_id(new_chem_name)
+ if(chem_id)
+ switch(params["which"])
+ if("left")
+ if(!left.Find(chem_id))
+ english_left += new_chem_name
+ left += chem_id
+ if("right")
+ if(!right.Find(chem_id))
+ english_right += new_chem_name
+ right += chem_id
+ else
+ to_chat(usr, "No such known reagent exists!")
+
+ if("remove")
+ var/chem_name = params["reagent"]
+ var/chem_id = get_chem_id(chem_name)
+ switch(params["which"])
+ if("left")
+ if(english_left.Find(chem_name))
+ english_left -= chem_name
+ left -= chem_id
+ if("right")
+ if(english_right.Find(chem_name))
+ english_right -= chem_name
+ right -= chem_id
diff --git a/code/modules/plumbing/plumbers/grinder_chemical.dm b/code/modules/plumbing/plumbers/grinder_chemical.dm
new file mode 100644
index 0000000000..f39c79f906
--- /dev/null
+++ b/code/modules/plumbing/plumbers/grinder_chemical.dm
@@ -0,0 +1,64 @@
+/obj/machinery/plumbing/grinder_chemical
+ name = "chemical grinder"
+ desc = "chemical grinder."
+ icon_state = "grinder_chemical"
+ layer = ABOVE_ALL_MOB_LAYER
+ reagent_flags = TRANSPARENT | DRAINABLE
+ rcd_cost = 30
+ rcd_delay = 30
+ buffer = 400
+ var/eat_dir = NORTH
+
+/obj/machinery/plumbing/grinder_chemical/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+/obj/machinery/plumbing/grinder_chemical/can_be_rotated(mob/user,rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return FALSE
+ switch(eat_dir)
+ if(WEST)
+ eat_dir = NORTH
+ return TRUE
+ if(EAST)
+ eat_dir = SOUTH
+ return TRUE
+ if(NORTH)
+ eat_dir = EAST
+ return TRUE
+ if(SOUTH)
+ eat_dir = WEST
+ return TRUE
+
+/obj/machinery/plumbing/grinder_chemical/CanPass(atom/movable/AM)
+ . = ..()
+ if(!anchored)
+ return
+ var/move_dir = get_dir(loc, AM.loc)
+ if(move_dir == eat_dir)
+ return TRUE
+
+/obj/machinery/plumbing/grinder_chemical/Crossed(atom/movable/AM)
+ . = ..()
+ grind(AM)
+
+/obj/machinery/plumbing/grinder_chemical/proc/grind(atom/AM)
+ if(stat & NOPOWER)
+ return
+ if(reagents.holder_full())
+ return
+ if(!isitem(AM))
+ return
+ var/obj/item/I = AM
+ if(I.juice_results || I.grind_results)
+ if(I.juice_results)
+ I.on_juice()
+ reagents.add_reagent_list(I.juice_results)
+ if(I.reagents)
+ I.reagents.trans_to(src, I.reagents.total_volume)
+ qdel(I)
+ return
+ I.on_grind()
+ reagents.add_reagent_list(I.grind_results)
+ qdel(I)
diff --git a/code/modules/plumbing/plumbers/medipenrefill.dm b/code/modules/plumbing/plumbers/medipenrefill.dm
new file mode 100644
index 0000000000..fb7553a4d5
--- /dev/null
+++ b/code/modules/plumbing/plumbers/medipenrefill.dm
@@ -0,0 +1,94 @@
+/obj/machinery/medipen_refiller
+ name = "Medipen Refiller"
+ desc = "A machine that refills used medipens with chemicals."
+ icon = 'icons/obj/machines/medipen_refiller.dmi'
+ icon_state = "medipen_refiller"
+ density = TRUE
+ circuit = /obj/item/circuitboard/machine/medipen_refiller
+ idle_power_usage = 100
+ /// list of medipen subtypes it can refill
+ var/list/allowed = list(/obj/item/reagent_containers/hypospray/medipen = /datum/reagent/medicine/epinephrine,
+ /obj/item/reagent_containers/hypospray/medipen/ekit = /datum/reagent/medicine/epinephrine,
+ /obj/item/reagent_containers/hypospray/medipen/firelocker = /datum/reagent/medicine/oxandrolone,
+ /obj/item/reagent_containers/hypospray/medipen/stimpack = /datum/reagent/medicine/ephedrine,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss = /datum/reagent/medicine/coagulant/weak)
+ /// var to prevent glitches in the animation
+ var/busy = FALSE
+
+/obj/machinery/medipen_refiller/Initialize()
+ . = ..()
+ create_reagents(100, TRANSPARENT)
+ for(var/obj/item/stock_parts/matter_bin/B in component_parts)
+ reagents.maximum_volume += 100 * B.rating
+ AddComponent(/datum/component/plumbing/simple_demand)
+
+
+/obj/machinery/medipen_refiller/RefreshParts()
+ var/new_volume = 100
+ for(var/obj/item/stock_parts/matter_bin/B in component_parts)
+ new_volume += 100 * B.rating
+ if(!reagents)
+ create_reagents(new_volume, TRANSPARENT)
+ reagents.maximum_volume = new_volume
+ return TRUE
+
+/// handles the messages and animation, calls refill to end the animation
+/obj/machinery/medipen_refiller/attackby(obj/item/I, mob/user, params)
+ if(busy)
+ to_chat(user, "The machine is busy.")
+ return
+ if(istype(I, /obj/item/reagent_containers) && I.is_open_container())
+ var/obj/item/reagent_containers/RC = I
+ var/units = RC.reagents.trans_to(src, RC.amount_per_transfer_from_this)
+ if(units)
+ to_chat(user, "You transfer [units] units of the solution to the [name].")
+ return
+ else
+ to_chat(user, "The [name] is full.")
+ return
+ if(istype(I, /obj/item/reagent_containers/hypospray/medipen))
+ var/obj/item/reagent_containers/hypospray/medipen/P = I
+ if(!(LAZYFIND(allowed, P.type)))
+ to_chat(user, "Error! Unknown schematics.")
+ return
+ if(P.reagents?.reagent_list.len)
+ to_chat(user, "The medipen is already filled.")
+ return
+ if(reagents.has_reagent(allowed[P.type], 10))
+ busy = TRUE
+ add_overlay("active")
+ addtimer(CALLBACK(src, .proc/refill, P, user), 20)
+ qdel(P)
+ return
+ to_chat(user, "There aren't enough reagents to finish this operation.")
+ return
+ ..()
+
+/obj/machinery/medipen_refiller/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ to_chat(user, "You start furiously plunging [name].")
+ if(do_after(user, 30, target = src))
+ to_chat(user, "You finish plunging the [name].")
+ reagents.clear_reagents()
+
+/obj/machinery/medipen_refiller/wrench_act(mob/living/user, obj/item/I)
+ ..()
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/machinery/medipen_refiller/crowbar_act(mob/user, obj/item/I)
+ ..()
+ default_deconstruction_crowbar(I)
+ return TRUE
+
+/obj/machinery/medipen_refiller/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(!.)
+ return default_deconstruction_screwdriver(user, "medipen_refiller_open", "medipen_refiller", I)
+
+/// refills the medipen
+/obj/machinery/medipen_refiller/proc/refill(obj/item/reagent_containers/hypospray/medipen/P, mob/user)
+ new P.type(loc)
+ reagents.remove_reagent(allowed[P.type], 10)
+ cut_overlays()
+ busy = FALSE
+ to_chat(user, "Medipen refilled.")
diff --git a/code/modules/plumbing/plumbers/pill_press.dm b/code/modules/plumbing/plumbers/pill_press.dm
new file mode 100644
index 0000000000..56510fac87
--- /dev/null
+++ b/code/modules/plumbing/plumbers/pill_press.dm
@@ -0,0 +1,127 @@
+///We take a constant input of reagents, and produce a pill once a set volume is reached
+/obj/machinery/plumbing/pill_press
+ name = "chemical press"
+ desc = "A press that makes pills, patches and bottles."
+ icon_state = "pill_press"
+ ///maximum size of a pill
+ var/max_pill_volume = 50
+ ///maximum size of a patch
+ var/max_patch_volume = 40
+ ///maximum size of a bottle
+ var/max_bottle_volume = 30
+ ///current operating product (pills or patches)
+ var/product = "pill"
+ ///the minimum size a pill or patch can be
+ var/min_volume = 5
+ ///the maximum size a pill or patch can be
+ var/max_volume = 50
+ ///selected size of the product
+ var/current_volume = 10
+ ///prefix for the product name
+ var/product_name = "factory"
+ ///the icon_state number for the pill.
+ var/pill_number = RANDOM_PILL_STYLE
+ ///list of id's and icons for the pill selection of the ui
+ var/list/pill_styles
+ ///list of products stored in the machine, so we dont have 610 pills on one tile
+ var/list/stored_products = list()
+ ///max amount of pills allowed on our tile before we start storing them instead
+ var/max_floor_products = 50 //haha massive pill piles
+
+/obj/machinery/plumbing/pill_press/examine(mob/user)
+ . = ..()
+ . += "The [name] currently has [stored_products.len] stored. There needs to be less than [max_floor_products ] on the floor to continue dispensing."
+
+/obj/machinery/plumbing/pill_press/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+
+ //expertly copypasted from chemmasters
+ var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/pills)
+ pill_styles = list()
+ for (var/x in 1 to PILL_STYLE_COUNT)
+ var/list/SL = list()
+ SL["id"] = x
+ SL["htmltag"] = assets.icon_tag("pill[x]")
+ pill_styles += list(SL)
+
+
+/obj/machinery/plumbing/pill_press/process()
+ if(stat & NOPOWER)
+ return
+ if(reagents.total_volume >= current_volume)
+ if (product == "pill")
+ var/obj/item/reagent_containers/pill/P = new(src)
+ reagents.trans_to(P, current_volume)
+ P.name = trim("[product_name] pill")
+ stored_products += P
+ if(pill_number == RANDOM_PILL_STYLE)
+ P.icon_state = "pill[rand(1,21)]"
+ else
+ P.icon_state = "pill[pill_number]"
+ if(P.icon_state == "pill4") //mirrored from chem masters
+ P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality."
+ else if (product == "patch")
+ var/obj/item/reagent_containers/pill/patch/P = new(src)
+ reagents.trans_to(P, current_volume)
+ P.name = trim("[product_name] patch")
+ stored_products += P
+ else if (product == "bottle")
+ var/obj/item/reagent_containers/glass/bottle/P = new(src)
+ reagents.trans_to(P, current_volume)
+ P.name = trim("[product_name] bottle")
+ stored_products += P
+ if(stored_products.len)
+ var/pill_amount = 0
+ for(var/obj/item/reagent_containers/pill/P in loc)
+ pill_amount++
+ if(pill_amount >= max_floor_products) //too much so just stop
+ break
+ if(pill_amount < max_floor_products)
+ var/atom/movable/AM = stored_products[1] //AM because forceMove is all we need
+ stored_products -= AM
+ AM.forceMove(drop_location())
+
+
+/obj/machinery/plumbing/pill_press/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/pills),
+ )
+
+/obj/machinery/plumbing/pill_press/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemPress", name)
+ ui.open()
+
+/obj/machinery/plumbing/pill_press/ui_data(mob/user)
+ var/list/data = list()
+ data["pill_style"] = pill_number
+ data["current_volume"] = current_volume
+ data["product_name"] = product_name
+ data["pill_styles"] = pill_styles
+ data["product"] = product
+ data["min_volume"] = min_volume
+ data["max_volume"] = max_volume
+ return data
+
+/obj/machinery/plumbing/pill_press/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("change_pill_style")
+ pill_number = clamp(text2num(params["id"]), 1 , PILL_STYLE_COUNT)
+ if("change_current_volume")
+ current_volume = clamp(text2num(params["volume"]), min_volume, max_volume)
+ if("change_product_name")
+ product_name = html_encode(params["name"])
+ if("change_product")
+ product = params["product"]
+ if (product == "pill")
+ max_volume = max_pill_volume
+ else if (product == "patch")
+ max_volume = max_patch_volume
+ else if (product == "bottle")
+ max_volume = max_bottle_volume
+ current_volume = clamp(current_volume, min_volume, max_volume)
diff --git a/code/modules/plumbing/plumbers/pumps.dm b/code/modules/plumbing/plumbers/pumps.dm
new file mode 100644
index 0000000000..c24e48098d
--- /dev/null
+++ b/code/modules/plumbing/plumbers/pumps.dm
@@ -0,0 +1,64 @@
+///We pump liquids from activated(plungerated) geysers to a plumbing outlet. We don't need to be wired.
+/obj/machinery/plumbing/liquid_pump
+ name = "liquid pump"
+ desc = "Pump up those sweet liquids from under the surface. Uses thermal energy from geysers to power itself." //better than placing 200 cables, because it wasnt fun
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ icon_state = "pump"
+ anchored = FALSE
+ density = TRUE
+ idle_power_usage = 10
+ active_power_usage = 1000
+
+ rcd_cost = 30
+ rcd_delay = 40
+
+ ///units we pump per process (2 seconds)
+ var/pump_power = 2
+ ///set to true if the loop couldnt find a geyser in process, so it remembers and stops checking every loop until moved. more accurate name would be absolutely_no_geyser_under_me_so_dont_try
+ var/geyserless = FALSE
+ ///The geyser object
+ var/obj/structure/geyser/geyser
+ ///volume of our internal buffer
+ var/volume = 200
+
+/obj/machinery/plumbing/liquid_pump/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+///please note that the component has a hook in the parent call, wich handles activating and deactivating
+/obj/machinery/plumbing/liquid_pump/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ geyser = null
+ update_icon()
+ geyserless = FALSE //we switched state, so lets just set this back aswell
+
+/obj/machinery/plumbing/liquid_pump/process()
+ if(!anchored || panel_open || geyserless)
+ return
+
+ if(!geyser)
+ for(var/obj/structure/geyser/G in loc.contents)
+ geyser = G
+ update_icon()
+ if(!geyser) //we didnt find one, abort
+ geyserless = TRUE
+ visible_message("The [name] makes a sad beep!")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50)
+ return
+
+ pump()
+
+///pump up that sweet geyser nectar
+/obj/machinery/plumbing/liquid_pump/proc/pump()
+ if(!geyser || !geyser.reagents)
+ return
+ geyser.reagents.trans_to(src, pump_power)
+
+/obj/machinery/plumbing/liquid_pump/update_icon_state()
+ if(geyser)
+ icon_state = initial(icon_state) + "-on"
+ else if(panel_open)
+ icon_state = initial(icon_state) + "-open"
+ else
+ icon_state = initial(icon_state)
diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm
new file mode 100644
index 0000000000..949543c300
--- /dev/null
+++ b/code/modules/plumbing/plumbers/reaction_chamber.dm
@@ -0,0 +1,63 @@
+///a reaction chamber for plumbing. pretty much everything can react, but this one keeps the reagents seperated and only reacts under your given terms
+/obj/machinery/plumbing/reaction_chamber
+ name = "reaction chamber"
+ desc = "Keeps chemicals seperated until given conditions are met."
+ icon_state = "reaction_chamber"
+ buffer = 200
+ reagent_flags = TRANSPARENT | NO_REACT
+
+ /**list of set reagents that the reaction_chamber allows in, and must all be present before mixing is enabled.
+ * example: list(/datum/reagent/water = 20, /datum/reagent/fuel/oil = 50)
+ */
+ var/list/required_reagents = list()
+ ///our reagent goal has been reached, so now we lock our inputs and start emptying
+ var/emptying = FALSE
+
+/obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/reaction_chamber, bolt)
+
+/obj/machinery/plumbing/reaction_chamber/on_reagent_change()
+ if(reagents.total_volume == 0 && emptying) //we were emptying, but now we aren't
+ emptying = FALSE
+ reagent_flags |= NO_REACT
+
+/obj/machinery/plumbing/reaction_chamber/power_change()
+ . = ..()
+ if(use_power != NO_POWER_USE)
+ icon_state = initial(icon_state) + "_on"
+ else
+ icon_state = initial(icon_state)
+
+/obj/machinery/plumbing/reaction_chamber/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemReactionChamber", name)
+ ui.open()
+
+/obj/machinery/plumbing/reaction_chamber/ui_data(mob/user)
+ var/list/data = list()
+ var/list/text_reagents = list()
+ for(var/A in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath
+ var/datum/reagent/R = A
+ text_reagents[initial(R.name)] = required_reagents[R]
+
+ data["reagents"] = text_reagents
+ data["emptying"] = emptying
+ return data
+
+/obj/machinery/plumbing/reaction_chamber/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("remove")
+ var/reagent = get_chem_id(params["chem"])
+ if(reagent)
+ required_reagents.Remove(reagent)
+ if("add")
+ var/input_reagent = get_chem_id(params["chem"])
+ if(input_reagent && !required_reagents.Find(input_reagent))
+ var/input_amount = text2num(params["amount"])
+ if(input_amount)
+ required_reagents[input_reagent] = input_amount
diff --git a/code/modules/plumbing/plumbers/splitters.dm b/code/modules/plumbing/plumbers/splitters.dm
new file mode 100644
index 0000000000..a26813486c
--- /dev/null
+++ b/code/modules/plumbing/plumbers/splitters.dm
@@ -0,0 +1,50 @@
+///it splits the reagents however you want. So you can "every 60 units, 45 goes left and 15 goes straight". The side direction is EAST, you can change this in the component
+/obj/machinery/plumbing/splitter
+ name = "Chemical Splitter"
+ desc = "A chemical splitter for smart chemical factorization. Waits till a set of conditions is met and then stops all input and splits the buffer evenly or other in two ducts."
+ icon_state = "splitter"
+ buffer = 100
+ density = FALSE
+
+ ///constantly switches between TRUE and FALSE. TRUE means the batch tick goes straight, FALSE means the next batch goes in the side duct.
+ var/turn_straight = TRUE
+ ///how much we must transfer straight. note input can be as high as 10 reagents per process, usually
+ var/transfer_straight = 5
+ ///how much we must transfer to the side
+ var/transfer_side = 5
+ //the maximum you can set the transfer to
+ var/max_transfer = 9
+
+
+/obj/machinery/plumbing/splitter/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/splitter, bolt)
+
+/obj/machinery/plumbing/splitter/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemSplitter", name)
+ ui.open()
+
+/obj/machinery/plumbing/splitter/ui_data(mob/user)
+ var/list/data = list()
+ data["straight"] = transfer_straight
+ data["side"] = transfer_side
+ data["max_transfer"] = max_transfer
+ return data
+
+/obj/machinery/plumbing/splitter/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("set_amount")
+ var/direction = params["target"]
+ var/value = clamp(text2num(params["amount"]), 1, max_transfer)
+ switch(direction)
+ if("straight")
+ transfer_straight = value
+ if("side")
+ transfer_side = value
+ else
+ return FALSE
diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm
new file mode 100644
index 0000000000..c2bc3439ff
--- /dev/null
+++ b/code/modules/plumbing/plumbers/synthesizer.dm
@@ -0,0 +1,111 @@
+///A single machine that produces a single chem. Can be placed in unison with others through plumbing to create chemical factories
+/obj/machinery/plumbing/synthesizer
+ name = "chemical synthesizer"
+ desc = "Produces a single chemical at a given volume. Must be plumbed. Most effective when working in unison with other chemical synthesizers, heaters and filters."
+
+ icon_state = "synthesizer"
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ rcd_cost = 25
+ rcd_delay = 15
+
+ ///Amount we produce for every process. Ideally keep under 5 since thats currently the standard duct capacity
+ var/amount = 1
+ ///The maximum we can produce for every process
+ buffer = 5
+ ///I track them here because I have no idea how I'd make tgui loop like that
+ var/static/list/possible_amounts = list(0,1,2,3,4,5)
+ ///The reagent we are producing. We are a typepath, but are also typecast because there's several occations where we need to use initial.
+ var/datum/reagent/reagent_id = null
+ ///straight up copied from chem dispenser. Being a subtype would be extremely tedious and making it global would restrict potential subtypes using different dispensable_reagents
+ var/list/dispensable_reagents = list(
+ /datum/reagent/aluminium,
+ /datum/reagent/bromine,
+ /datum/reagent/carbon,
+ /datum/reagent/chlorine,
+ /datum/reagent/copper,
+ /datum/reagent/consumable/ethanol,
+ /datum/reagent/fluorine,
+ /datum/reagent/hydrogen,
+ /datum/reagent/iodine,
+ /datum/reagent/iron,
+ /datum/reagent/lithium,
+ /datum/reagent/mercury,
+ /datum/reagent/nitrogen,
+ /datum/reagent/oxygen,
+ /datum/reagent/phosphorus,
+ /datum/reagent/potassium,
+ /datum/reagent/radium,
+ /datum/reagent/silicon,
+ /datum/reagent/silver,
+ /datum/reagent/sodium,
+ /datum/reagent/stable_plasma,
+ /datum/reagent/consumable/sugar,
+ /datum/reagent/sulfur,
+ /datum/reagent/toxin/acid,
+ /datum/reagent/water,
+ /datum/reagent/fuel,
+ )
+
+/obj/machinery/plumbing/synthesizer/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+/obj/machinery/plumbing/synthesizer/process()
+ if(stat & NOPOWER || !reagent_id || !amount)
+ return
+ if(reagents.total_volume >= amount) //otherwise we get leftovers, and we need this to be precise
+ return
+ reagents.add_reagent(reagent_id, amount)
+
+/obj/machinery/plumbing/synthesizer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemSynthesizer", name)
+ ui.open()
+
+/obj/machinery/plumbing/synthesizer/ui_data(mob/user)
+ var/list/data = list()
+
+ var/is_hallucinating = user.hallucinating()
+ var/list/chemicals = list()
+
+ for(var/A in dispensable_reagents)
+ var/datum/reagent/R = GLOB.chemical_reagents_list[A]
+ if(R)
+ var/chemname = R.name
+ if(is_hallucinating && prob(5))
+ chemname = "[pick_list_replacements("hallucination.json", "chemicals")]"
+ chemicals.Add(list(list("title" = chemname, "id" = ckey(R.name))))
+ data["chemicals"] = chemicals
+ data["amount"] = amount
+ data["possible_amounts"] = possible_amounts
+
+ data["current_reagent"] = ckey(initial(reagent_id.name))
+ return data
+
+/obj/machinery/plumbing/synthesizer/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("amount")
+ var/new_amount = text2num(params["target"])
+ if(new_amount in possible_amounts)
+ amount = new_amount
+ . = TRUE
+ if("select")
+ var/new_reagent = GLOB.name2reagent[params["reagent"]]
+ if(new_reagent in dispensable_reagents)
+ reagent_id = new_reagent
+ . = TRUE
+ update_icon()
+ reagents.clear_reagents()
+
+/obj/machinery/plumbing/synthesizer/update_overlays()
+ . = ..()
+ var/mutable_appearance/r_overlay = mutable_appearance(icon, "[icon_state]_overlay")
+ if(reagent_id)
+ r_overlay.color = initial(reagent_id.color)
+ else
+ r_overlay.color = "#FFFFFF"
+ . += r_overlay
diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
index d0d846c144..78ca6e9280 100644
--- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm
+++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
@@ -76,7 +76,8 @@
return 1
/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0)
- if(INTERACTING_WITH(user, A))
+ if(INTERACTING_WITH(user, src) || INTERACTING_WITH(user, A))
+ to_chat(user, "You're already doing that!")
return FALSE
var/num_loaded = 0
if(!can_load(user))
@@ -84,13 +85,16 @@
if(istype(A, /obj/item/ammo_box))
var/obj/item/ammo_box/AM = A
for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
- if(load_delay && do_after(user, load_delay, target = src))
- var/did_load = give_round(AC, replace_spent)
- if(did_load)
- AM.stored_ammo -= AC
- num_loaded++
- if(!did_load || !multiload)
- break
+ if(load_delay || AM.load_delay)
+ var/loadtime = max(AM.load_delay, load_delay)
+ if(!do_after(user, loadtime, target = src))
+ return FALSE
+ var/did_load = give_round(AC, replace_spent)
+ if(did_load)
+ AM.stored_ammo -= AC
+ num_loaded++
+ if(!did_load || !multiload)
+ break
if(istype(A, /obj/item/ammo_casing))
var/obj/item/ammo_casing/AC = A
if(give_round(AC, replace_spent))
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index fa8099a257..1aefa51a51 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -53,6 +53,8 @@
..()
if (istype(A, /obj/item/ammo_box/magazine))
var/obj/item/ammo_box/magazine/AM = A
+ if(AM.load_delay && !do_after(user, AM.load_delay, target = src))
+ return FALSE
if (!magazine && istype(AM, mag_type))
if(user.transferItemToLoc(AM, src))
magazine = AM
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index c2662a8342..c489edf88e 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -1165,3 +1165,9 @@
random_reagents += R
var/picked_reagent = pick(random_reagents)
return picked_reagent
+
+/proc/get_chem_id(chem_name)
+ for(var/X in GLOB.chemical_reagents_list)
+ var/datum/reagent/R = GLOB.chemical_reagents_list[X]
+ if(ckey(chem_name) == ckey(lowertext(R.name)))
+ return X
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 207325e1b3..32ac7cecba 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -1,5 +1,3 @@
-#define PILL_STYLE_COUNT 22 //Update this if you add more pill icons or you die
-#define RANDOM_PILL_STYLE 22 //Dont change this one though
/obj/machinery/chem_master
name = "ChemMaster 3000"
diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm
index a539897c9d..d22523c4b8 100644
--- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm
+++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm
@@ -36,6 +36,14 @@
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
reagents.maximum_volume += REAGENTS_BASE_VOLUME * B.rating
+/obj/machinery/smoke_machine/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+ AddComponent(/datum/component/plumbing/simple_demand) //this SURELY CANT' LEAD TO BAD THINGS HAPPENING.
+
+/obj/machinery/smoke_machine/proc/can_be_rotated(mob/user, rotation_type)
+ return !anchored
+
/obj/machinery/smoke_machine/update_icon_state()
if((!is_operational()) || (!on) || (reagents.total_volume == 0))
if (panel_open)
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index a85ac8b085..672127cb11 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
if (length(initial(R.name)))
.[ckey(initial(R.name))] = t
+
//Various reagents
//Toxin & acid reagents
//Hydroponics stuff
@@ -52,6 +53,14 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
var/metabolizing = FALSE
var/chemical_flags // See fermi/readme.dm REAGENT_DEAD_PROCESS, REAGENT_DONOTSPLIT, REAGENT_ONLYINVERSE, REAGENT_ONMOBMERGE, REAGENT_INVISIBLE, REAGENT_FORCEONNEW, REAGENT_SNEAKYNAME
var/value = REAGENT_VALUE_NONE //How much does it sell for in cargo?
+ var/datum/material/material //are we made of material?
+
+/datum/reagent/New()
+ . = ..()
+
+ if(material)
+ material = SSmaterials.GetMaterialRef(material)
+
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
. = ..()
@@ -220,4 +229,3 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
bloodsuckerdatum.handle_eat_human_food(disgust, blood_puke, force)
if(blood_change)
bloodsuckerdatum.AddBloodVolume(blood_change)
-
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index a6e78ae98c..18203f1a4c 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -132,8 +132,8 @@
"You're covered in boiling oil!")
M.emote("scream")
playsound(M, 'sound/machines/fryer/deep_fryer_emerge.ogg', 25, TRUE)
- var/oil_damage = max((holder.chem_temp / fry_temperature) * 0.33,1) //Damage taken per unit
- M.adjustFireLoss(oil_damage * max(reac_volume,20)) //Damage caps at 20
+ var/oil_damage = min((holder.chem_temp / fry_temperature) * 0.33,1) //Damage taken per unit
+ M.adjustFireLoss(oil_damage * min(reac_volume,20)) //Damage caps at 20
else
..()
return TRUE
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 8bb34c0a82..37010cbbb5 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -515,12 +515,13 @@
overdose_threshold = 30
pH = 2
value = REAGENT_VALUE_UNCOMMON
+ var/healing = 0.5
/datum/reagent/medicine/omnizine/on_mob_life(mob/living/carbon/M)
- M.adjustToxLoss(-0.5*REM, 0)
- M.adjustOxyLoss(-0.5*REM, 0)
- M.adjustBruteLoss(-0.5*REM, 0)
- M.adjustFireLoss(-0.5*REM, 0)
+ M.adjustToxLoss(-healing*REM, 0)
+ M.adjustOxyLoss(-healing*REM, 0)
+ M.adjustBruteLoss(-healing*REM, 0)
+ M.adjustFireLoss(-healing*REM, 0)
..()
. = 1
@@ -532,6 +533,12 @@
..()
. = 1
+/datum/reagent/medicine/omnizine/protozine
+ name = "Protozine"
+ description = "A less environmentally friendly and somewhat weaker variant of omnizine."
+ color = "#d8c7b7"
+ healing = 0.2
+
/datum/reagent/medicine/calomel
name = "Calomel"
description = "Quickly purges the body of all chemicals. Toxin damage is dealt if the patient is in good condition."
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index b60ee8d108..5c01fd8cf6 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -313,6 +313,13 @@
metabolization_rate = 45 * REAGENTS_METABOLISM
. = 1
+/datum/reagent/water/hollowwater
+ name = "Hollow Water"
+ description = "An ubiquitous chemical substance that is composed of hydrogen and oxygen, but it looks kinda hollow."
+ color = "#88878777"
+ taste_description = "emptyiness"
+
+
/datum/reagent/water/holywater
name = "Holy Water"
description = "Water blessed by some deity."
@@ -950,6 +957,7 @@
color = "#1C1300" // rgb: 30, 20, 0
taste_description = "sour chalk"
pH = 5
+ material = /datum/material/diamond
/datum/reagent/carbon/reaction_turf(turf/T, reac_volume)
if(!isspaceturf(T))
@@ -1072,6 +1080,7 @@
pH = 6
overdose_threshold = 30
color = "#c2391d"
+ material = /datum/material/iron
/datum/reagent/iron/on_mob_life(mob/living/carbon/C)
if((HAS_TRAIT(C, TRAIT_NOMARROW)))
@@ -1103,6 +1112,7 @@
reagent_state = SOLID
color = "#F7C430" // rgb: 247, 196, 48
taste_description = "expensive metal"
+ material = /datum/material/gold
/datum/reagent/silver
name = "Silver"
@@ -1110,6 +1120,7 @@
reagent_state = SOLID
color = "#D0D0D0" // rgb: 208, 208, 208
taste_description = "expensive yet reasonable metal"
+ material = /datum/material/silver
/datum/reagent/silver/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(M.has_bane(BANE_SILVER))
@@ -1123,6 +1134,7 @@
color = "#B8B8C0" // rgb: 184, 184, 192
taste_description = "the inside of a reactor"
pH = 4
+ material = /datum/material/uranium
/datum/reagent/uranium/on_mob_life(mob/living/carbon/M)
M.apply_effect(1/M.metabolism_efficiency,EFFECT_IRRADIATE,0)
@@ -1144,6 +1156,7 @@
taste_description = "fizzling blue"
pH = 12
value = REAGENT_VALUE_RARE
+ material = /datum/material/bluespace
/datum/reagent/bluespace/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
@@ -1182,6 +1195,7 @@
color = "#A8A8A8" // rgb: 168, 168, 168
taste_mult = 0
pH = 10
+ material = /datum/material/glass
/datum/reagent/fuel
name = "Welding fuel"
@@ -2206,6 +2220,66 @@
color = "#f7685e"
metabolization_rate = REAGENTS_METABOLISM * 0.25
+/datum/reagent/wittel
+ name = "Wittel"
+ description = "An extremely rare metallic-white substance only found on demon-class planets."
+ color = "#FFFFFF" // rgb: 255, 255, 255
+ taste_mult = 0 // oderless and tasteless
+
+/datum/reagent/metalgen
+ name = "Metalgen"
+ data = list("material"=null)
+ description = "A purple metal morphic liquid, said to impose it's metallic properties on whatever it touches."
+ color = "#b000aa"
+ taste_mult = 0 // oderless and tasteless
+ var/applied_material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR
+ var/minumum_material_amount = 100
+
+/datum/reagent/metalgen/reaction_obj(obj/O, volume)
+ metal_morph(O)
+ return
+
+/datum/reagent/metalgen/reaction_turf(turf/T, volume)
+ metal_morph(T)
+ return
+
+///turn an object into a special material
+/datum/reagent/metalgen/proc/metal_morph(atom/A)
+ var/metal_ref = data["material"]
+ if(!metal_ref)
+ return
+ var/metal_amount = 0
+
+ for(var/B in A.custom_materials) //list with what they're made of
+ metal_amount += A.custom_materials[B]
+
+ if(!metal_amount)
+ metal_amount = minumum_material_amount //some stuff doesn't have materials at all. To still give them properties, we give them a material. Basically doesnt exist
+
+ var/list/metal_dat = list()
+ metal_dat[metal_ref] = metal_amount //if we pass the list directly, byond turns metal_ref into "metal_ref" kjewrg8fwcyvf
+
+ A.material_flags = applied_material_flags
+ A.set_custom_materials(metal_dat)
+
+/datum/reagent/gravitum
+ name = "Gravitum"
+ description = "A rare kind of null fluid, capable of temporalily removing all weight of whatever it touches." //i dont even
+ color = "#050096" // rgb: 5, 0, 150
+ taste_mult = 0 // oderless and tasteless
+ metabolization_rate = 0.1 * REAGENTS_METABOLISM //20 times as long, so it's actually viable to use
+ var/time_multiplier = 1 MINUTES //1 minute per unit of gravitum on objects. Seems overpowered, but the whole thing is very niche
+
+/datum/reagent/gravitum/reaction_obj(obj/O, volume)
+ O.AddElement(/datum/element/forced_gravity, 0)
+
+ addtimer(CALLBACK(O, .proc/_RemoveElement, /datum/element/forced_gravity, 0), volume * time_multiplier)
+
+/datum/reagent/gravitum/on_mob_add(mob/living/L)
+ L.AddElement(/datum/element/forced_gravity, 0) //0 is the gravity, and in this case weightless
+
+/datum/reagent/gravitum/on_mob_end_metabolize(mob/living/L)
+ L.RemoveElement(/datum/element/forced_gravity, 0)
//body bluids
/datum/reagent/consumable/semen
@@ -2218,6 +2292,7 @@
color = "#FFFFFF" // rgb: 255, 255, 255
can_synth = FALSE
nutriment_factor = 0.5 * REAGENTS_METABOLISM
+ var/decal_path = /obj/effect/decal/cleanable/semen
/datum/reagent/consumable/semen/reaction_turf(turf/T, reac_volume)
if(!istype(T))
@@ -2227,7 +2302,7 @@
var/obj/effect/decal/cleanable/semen/S = locate() in T
if(!S)
- S = new(T)
+ S = new decal_path(T)
if(data["blood_DNA"])
S.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
@@ -2251,51 +2326,20 @@
blood_DNA |= S.blood_DNA
return ..()
-/datum/reagent/consumable/femcum
+/datum/reagent/consumable/semen/femcum
name = "Female Ejaculate"
description = "Vaginal lubricant found in most mammals and other animals of similar nature. Where you found this is your own business."
taste_description = "something with a tang" // wew coders who haven't eaten out a girl.
- taste_mult = 2
- data = list("donor"=null,"viruses"=null,"donor_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null)
- reagent_state = LIQUID
color = "#AAAAAA77"
- can_synth = FALSE
- nutriment_factor = 0.5 * REAGENTS_METABOLISM
+ decal_path = /obj/effect/decal/cleanable/semen/femcum
-/obj/effect/decal/cleanable/femcum
+/obj/effect/decal/cleanable/semen/femcum
name = "female ejaculate"
- desc = null
- gender = PLURAL
- density = 0
- layer = ABOVE_NORMAL_TURF_LAYER
- icon = 'icons/obj/genitals/effects.dmi'
icon_state = "fem1"
random_icon_states = list("fem1", "fem2", "fem3", "fem4")
blood_state = null
bloodiness = null
-/obj/effect/decal/cleanable/femcum/Initialize(mapload)
- . = ..()
- dir = GLOB.cardinals
- add_blood_DNA(list("Non-human DNA" = "A+"))
-
-/obj/effect/decal/cleanable/femcum/replace_decal(obj/effect/decal/cleanable/femcum/F)
- if(F.blood_DNA)
- blood_DNA |= F.blood_DNA
- return ..()
-
-/datum/reagent/consumable/femcum/reaction_turf(turf/T, reac_volume)
- if(!istype(T))
- return
- if(reac_volume < 10)
- return
-
- var/obj/effect/decal/cleanable/femcum/S = locate() in T
- if(!S)
- S = new(T)
- if(data["blood_DNA"])
- S.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
-
/datum/reagent/determination
name = "Determination"
description = "For when you need to push on a little more. Do NOT allow near plants."
@@ -2364,6 +2408,7 @@ datum/reagent/eldritch
color = "#E6E6DA"
taste_mult = 0
+
/datum/reagent/hairball
name = "Hairball"
description = "A bundle of keratinous bits and fibers, not easily digestible."
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 8dca028b4a..07934d9880 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -64,6 +64,7 @@
toxpwr = 3
pH = 4
value = REAGENT_VALUE_RARE //sheets are worth more
+ material = /datum/material/plasma
/datum/reagent/toxin/plasma/on_mob_life(mob/living/carbon/C)
if(holder.has_reagent(/datum/reagent/medicine/epinephrine))
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index bb9a951cac..9e0c78d2e6 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -219,6 +219,12 @@
results = list(/datum/reagent/medicine/strange_reagent = 3)
required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1)
+/datum/chemical_reaction/strange_reagent/alt
+ name = "Strange Reagent"
+ id = /datum/reagent/medicine/strange_reagent
+ results = list(/datum/reagent/medicine/strange_reagent = 2)
+ required_reagents = list(/datum/reagent/medicine/omnizine/protozine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1)
+
/datum/chemical_reaction/mannitol
name = "Mannitol"
id = /datum/reagent/medicine/mannitol
@@ -345,4 +351,20 @@
/datum/chemical_reaction/medmesh/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
for(var/i = 1, i <= created_volume, i++)
- new /obj/item/stack/medical/mesh/advanced(location)
\ No newline at end of file
+ new /obj/item/stack/medical/mesh/advanced(location)
+
+/datum/chemical_reaction/suture
+ required_reagents = list(/datum/reagent/cellulose = 2, /datum/reagent/medicine/styptic_powder = 2)
+
+/datum/chemical_reaction/suture/on_reaction(datum/reagents/holder, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/i = 1, i <= created_volume, i++)
+ new /obj/item/stack/medical/suture/(location)
+
+/datum/chemical_reaction/mesh
+ required_reagents = list(/datum/reagent/cellulose = 2, /datum/reagent/medicine/silver_sulfadiazine = 2)
+
+/datum/chemical_reaction/mesh/on_reaction(datum/reagents/holder, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/i = 1, i <= created_volume, i++)
+ new /obj/item/stack/medical/mesh/(location)
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 09b7eabbc1..92861a94ed 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -1,3 +1,34 @@
+/datum/chemical_reaction/metalgen
+ name = "metalgen"
+ id = /datum/reagent/metalgen
+ required_reagents = list(/datum/reagent/wittel = 1, /datum/reagent/bluespace = 1, /datum/reagent/toxin/mutagen = 1)
+ results = list(/datum/reagent/metalgen = 1)
+
+/datum/chemical_reaction/metalgen_imprint
+ name = "metalgen imprint"
+ id = /datum/reagent/metalgen
+ required_reagents = list(/datum/reagent/metalgen = 1, /datum/reagent/liquid_dark_matter = 1)
+ results = list(/datum/reagent/metalgen = 1)
+
+/datum/chemical_reaction/holywater
+ name = "Holy Water"
+ id = /datum/reagent/water/holywater
+ results = list(/datum/reagent/water/holywater = 1)
+ required_reagents = list(/datum/reagent/water/hollowwater = 1)
+ required_catalysts = list(/datum/reagent/water/holywater = 1)
+
+/datum/chemical_reaction/metalgen_imprint/on_reaction(datum/reagents/holder, created_volume)
+ var/datum/reagent/metalgen/MM = holder.get_reagent(/datum/reagent/metalgen)
+ for(var/datum/reagent/R in holder.reagent_list)
+ if(R.material && R.volume >= 40)
+ MM.data["material"] = R.material
+ holder.remove_reagent(R.type, 40)
+
+/datum/chemical_reaction/gravitum
+ name = "gravitum"
+ id = /datum/reagent/gravitum
+ required_reagents = list(/datum/reagent/wittel = 1, /datum/reagent/sorium = 10)
+ results = list(/datum/reagent/gravitum = 10)
/datum/chemical_reaction/sterilizine
name = "Sterilizine"
@@ -711,7 +742,7 @@
/datum/chemical_reaction/slime_extractification/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
new /obj/item/slime_extract/grey(location)
-
+
// Liquid Carpets
/datum/chemical_reaction/carpet
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 79edb37913..6541deda1c 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -24,7 +24,8 @@
/obj/structure/reagent_dispensers/Initialize()
create_reagents(tank_volume, DRAINABLE | AMOUNT_VISIBLE)
- reagents.add_reagent(reagent_id, tank_volume)
+ if(reagent_id)
+ reagents.add_reagent(reagent_id, tank_volume)
. = ..()
/obj/structure/reagent_dispensers/proc/boom()
@@ -91,6 +92,38 @@
user.put_in_hands(S)
paper_cups--
+/obj/structure/reagent_dispensers/plumbed
+ name = "stationairy water tank"
+ anchored = TRUE
+ icon_state = "water_stationairy"
+ desc = "A stationairy, plumbed, water tank."
+
+/obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/I)
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/structure/reagent_dispensers/plumbed/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \
+ "You [anchored ? "fasten" : "unfasten"] [src]")
+ var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing)
+ if(anchored)
+ CP.enable()
+ else
+ CP.disable()
+
+/obj/structure/reagent_dispensers/plumbed/ComponentInitialize()
+ AddComponent(/datum/component/plumbing/simple_supply)
+
+/obj/structure/reagent_dispensers/plumbed/storage
+ name = "stationairy storage tank"
+ icon_state = "tank_stationairy"
+ reagent_id = null //start empty
+
+/obj/structure/reagent_dispensers/plumbed/storage/ComponentInitialize()
+ AddComponent(/datum/component/plumbing/tank)
+
//////////////
//Fuel Tanks//
//////////////
@@ -271,5 +304,3 @@
icon_state = "bluekeg"
reagent_id = /datum/reagent/consumable/ethanol/neurotoxin
tank_volume = 100 //2.5x less than the other kegs because it's harder to get
-
-
diff --git a/code/modules/research/designs/machine_desings/machine_designs_medical.dm b/code/modules/research/designs/machine_desings/machine_designs_medical.dm
index 329fb7bf6e..84a3ed10d5 100644
--- a/code/modules/research/designs/machine_desings/machine_designs_medical.dm
+++ b/code/modules/research/designs/machine_desings/machine_designs_medical.dm
@@ -105,3 +105,11 @@
build_path = /obj/item/circuitboard/machine/bloodbankgen
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
category = list ("Medical Machinery")
+
+/datum/design/board/medipen_refiller
+ name = "Machine Design (Medipen Refiller)"
+ desc = "The circuit board for a Medipen Refiller."
+ id = "medipen_refiller"
+ build_path = /obj/item/circuitboard/machine/medipen_refiller
+ category = list ("Medical Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
diff --git a/code/modules/research/designs/machine_desings/machine_designs_service.dm b/code/modules/research/designs/machine_desings/machine_designs_service.dm
index 5cbff1c66a..af4f650793 100644
--- a/code/modules/research/designs/machine_desings/machine_designs_service.dm
+++ b/code/modules/research/designs/machine_desings/machine_designs_service.dm
@@ -81,6 +81,14 @@
category = list ("Hydroponics Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
+/datum/design/board/hydroponics/auto
+ name = "Machine Design (Automatic Hydroponics Tray Board)"
+ desc = "The circuit board for an automatic hydroponics tray. GIVE ME THE PLANT, CAPTAIN."
+ id = "autohydrotray"
+ build_path = /obj/machinery/hydroponics/constructable/automagic
+ category = list ("Hydroponics Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE | DEPARTMENTAL_FLAG_MEDICAL
+
/datum/design/board/monkey_recycler
name = "Machine Design (Monkey Recycler Board)"
desc = "The circuit board for a monkey recycler."
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index e1e55f3476..0a9fce2e67 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -961,3 +961,158 @@
build_path = /obj/item/bodypart/r_arm/robot/surplus_upgraded
category = list("Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+
+/datum/design/acclimator
+ name = "Plumbing Acclimator"
+ desc = "A heating and cooling device for pipes!"
+ id = "acclimator"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/acclimator
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/disposer
+ name = "Plumbing Disposer"
+ desc = "Using the power of Science, dissolves reagents into nothing (almost)."
+ id = "disposer"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 500, /datum/material/glass = 100)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/disposer
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_filter
+ name = "Plumbing Filter"
+ desc = "Filters out chemicals by their NTDB ID."
+ id = "plumb_filter"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/filter
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_synth
+ name = "Plumbing Synthesizer"
+ desc = "Using standard mass-energy dynamic autoconverters, generates reagents from power and puts them in a pipe."
+ id = "plumb_synth"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 5000, /datum/material/glass = 1000, /datum/material/plastic = 1000)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/synthesizer
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_grinder
+ name = "Plumbing-Linked Autogrinder"
+ desc = "Automatically extracts reagents from an item by grinding it. Think of the possibilities! Note: does not grind people."
+ id = "plumb_grinder"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 2000, /datum/material/glass = 1500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/grinder_chemical
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/reaction_chamber
+ name = "Plumbing Reaction Chamber"
+ desc = "You can set a list of allowed reagents and amounts. Once the chamber has these reagents, will let the products through."
+ id = "reaction_chamber"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/reaction_chamber
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/duct_print
+ name = "Plumbing Ducts"
+ desc = "Ducts for plumbing! Now lathed for efficiency."
+ id = "duct_print"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/plastic = 400)
+ construction_time = 1
+ build_path = /obj/item/stack/ducts
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_splitter
+ name = "Plumbing Chemical Splitter"
+ desc = "A splitter. Has 2 outputs. Can be configured to allow a certain amount through each side."
+ id = "plumb_splitter"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 750, /datum/material/glass = 250)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/splitter
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/pill_press
+ name = "Plumbing Automatic Pill Former"
+ desc = "Automatically forms pills to the required parameters with piped reagents! A good replacement for those lazy, useless chemists."
+ id = "pill_press"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/pill_press
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_pump
+ name = "Liquid Extraction Pump"
+ desc = "Use it for extracting liquids from lavaland's geysers!"
+ id = "plumb_pump"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/liquid_pump
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_in
+ name = "Plumbing Input Device"
+ desc = "A big piped funnel for putting stuff in the pipe network."
+ id = "plumb_in"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 400, /datum/material/glass = 400)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/input
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_out
+ name = "Plumbing Output Device"
+ desc = "A big piped funnel for taking stuff out of the pipe network."
+ id = "plumb_out"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 400, /datum/material/glass = 400)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/output
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_tank
+ name = "Plumbed Storage Tank"
+ desc = "A tank for storing plumbed reagents."
+ id = "plumb_tank"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 10000, /datum/material/glass = 10000, /datum/material/plastic = 4000)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/tank
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_rcd
+ name = "Plumbed Autoconstruction Device"
+ desc = "A RCD for plumbing machines! Cannot make ducts."
+ id = "plumb_rcd"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 20000, /datum/material/glass = 10000, /datum/material/plastic = 20000, /datum/material/titanium = 2000, /datum/material/diamond = 800, /datum/material/gold = 2000, /datum/material/silver = 2000)
+ construction_time = 150
+ build_path = /obj/item/construction/plumbing
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
diff --git a/code/modules/research/nanites/extra_settings/text.dm b/code/modules/research/nanites/extra_settings/text.dm
index 56aa3dd07f..d3cad27bcf 100644
--- a/code/modules/research/nanites/extra_settings/text.dm
+++ b/code/modules/research/nanites/extra_settings/text.dm
@@ -10,6 +10,9 @@
/datum/nanite_extra_setting/text/get_copy()
return new /datum/nanite_extra_setting/text(value)
+/datum/nanite_extra_setting/text/get_value()
+ return html_encode(value)
+
/datum/nanite_extra_setting/text/get_frontend_list(name)
return list(list(
"name" = name,
diff --git a/code/modules/research/nanites/nanite_programs/suppression.dm b/code/modules/research/nanites/nanite_programs/suppression.dm
index 3b0d6d0d06..d2aa243fee 100644
--- a/code/modules/research/nanites/nanite_programs/suppression.dm
+++ b/code/modules/research/nanites/nanite_programs/suppression.dm
@@ -176,7 +176,7 @@
sent_message = message_setting.get_value()
if(host_mob.stat == DEAD)
return
- to_chat(host_mob, "You hear a strange, robotic voice in your head... \"[sent_message]\"")
+ to_chat(host_mob, "You hear a strange, robotic voice in your head... \"[html_encode(sent_message)]\"")
/datum/nanite_program/comm/hallucination
name = "Hallucination"
diff --git a/code/modules/research/techweb/nodes/medical_nodes.dm b/code/modules/research/techweb/nodes/medical_nodes.dm
index 3a9e654b81..150e420c09 100644
--- a/code/modules/research/techweb/nodes/medical_nodes.dm
+++ b/code/modules/research/techweb/nodes/medical_nodes.dm
@@ -24,6 +24,23 @@
design_ids = list("defib_decay", "defib_shock", "defib_heal", "defib_speed")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+/datum/techweb_node/plumbing
+ id = "plumbing"
+ display_name = "Reagent Plumbing Technology"
+ description = "Plastic tubes, and machinery used for manipulating things in them."
+ prereq_ids = list("base")
+ design_ids = list("acclimator", "disposer", "plumb_filter", "plumb_synth", "plumb_grinder", "reaction_chamber", "duct_print", "plumb_splitter", "pill_press", "plumb_pump", "plumb_in", "plumb_out", "plumb_tank", "medipen_refiller")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
+
+
+/datum/techweb_node/advplumbing
+ id = "advplumbing"
+ display_name = "Advanced Plumbing Technology"
+ description = "Plumbing RCD."
+ prereq_ids = list("plumbing", "adv_engi")
+ design_ids = list("plumb_rcd", "autohydrotray")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+
//////////////////////Cybernetics/////////////////////
/datum/techweb_node/surplus_limbs
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
index f5a11db60f..b3d99f22ab 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
@@ -43,6 +43,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
. = ..()
internal_shuttle_creator = new()
internal_shuttle_creator.owner_rsd = src
+ desc += " Attention, the max size of the shuttle is [SHUTTLE_CREATOR_MAX_SIZE]."
overlay_holder = new()
/obj/item/shuttle_creator/Destroy()
@@ -237,13 +238,13 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
port.register()
- icon_state = "rsd_used"
+ icon_state = "rsd_empty"
//Clear highlights
overlay_holder.clear_highlights()
GLOB.custom_shuttle_count ++
- message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
- log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
+ message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
+ log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
return TRUE
/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user)
@@ -350,7 +351,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
loggedOldArea = get_area(get_turf(user))
loggedTurfs |= turfs
overlay_holder.highlight_area(turfs)
- //TODO READD THIS SHIT: icon_state = "rsd_used"
+ //TODO READD THIS SHIT: icon_state = "rsd_empty"
to_chat(user, "You add the area into the buffer of the [src], you made add more areas or select an airlock to act as a docking port to complete the shuttle.")
return turfs
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index 34de3b490c..795d35adc4 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -46,7 +46,8 @@
/obj/item/wrench/medical = 1,
/obj/item/storage/belt/medolier/full = 2,
/obj/item/gun/syringe/dart = 2,
- /obj/item/storage/briefcase/medical = 2)
+ /obj/item/storage/briefcase/medical = 2,
+ /obj/item/plunger/reinforced = 2)
armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
diff --git a/html/changelog.html b/html/changelog.html
index 56bb7500b4..ab4eb79cd6 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,53 @@
-->
+ 22 August 2020
+ Time-Green (copypasta'd by lolman360) updated:
+
+ - plumbing
+ - automatic hydro trays
+
+
+ 21 August 2020
+ LetterN updated:
+
+ - Updates and adds some of the tips
+
+ Putnam3145 updated:
+
+ - added reftracking as a compile flag
+
+ SmArtKar updated:
+
+ - RSD limitation is now 500 tiles
+ - Fixed broken RSD sprites
+ - Removed that shuttle limit
+
+ timothyteakettle updated:
+
+ - two snouts can once again be chosen in customization
+ - lizard snouts work again
+
+
+ 20 August 2020
+ DeltaFire15 updated:
+
+ - The cooking oil damage formula is no longer scuffed.
+ - Changed the clockie help-link to lead to our own wiki.
+
+ Fikou updated:
+
+ - admins can now do html in ahelps properly
+
+ Hatterhat updated:
+
+ - Pirate threats are now announced as "business propositions", and their arrivals are now also announced properly.
+
+ tiramisuapimancer updated:
+
+ - Ethereal hair is now their body color instead of accidentally white
+
+
18 August 2020
DeltaFire15 updated:
@@ -1374,77 +1421,6 @@
- calculations for punch hit chance has been drastically buffed in favor of the attacker.
-
- 20 June 2020
- LetterN updated:
-
- - Asset cache from tg
- - Made the map viewer not look bad
- - Admin matrix right-bracket
-
- bunny232 updated:
-
- - Removed unsavory things from the vent clog event
-
-
- 19 June 2020
- Bhijn updated:
-
- - Atmos can no longer become completely bricked
-
- Funce updated:
-
- - Square root circuit should now actually work.
-
- SmArtKar updated:
-
- TheSpaghetti updated:
-
- - more insectoid insects
-
- kevinz000 updated:
-
- - bay/polaris style say_emphasis has been added. You can now |italicize| _underline_ and +bold+ your messages.
-
- timothyteakettle updated:
-
- - Adds the brain trauma event, where one player gets a random brain trauma!
- - Adds the wisdom cow event, where the wisdom cow appears on the station!
- - Adds the fake virus event, where people get fake virus symptoms.
- - Adds the stray cargo pod event, where a cargo pod crashes into the station.
- - Adds the fugitives event, where fugitives are loose on the station, and it's the hunters jobs to capture them.
-
-
- 18 June 2020
- Detective-Google updated:
-
- - cog is now less the suck
- - couple little derpy bits
- - malf disk and illegal tech disk moved from ashwalker base (guaranteed) to tendrils (chance based)
-
- SmArtKar updated:
-
- - Ported shuttles from beestation
-
- timothyteakettle updated:
-
- - embeds got reworked, sticky tape was added, more bullets that ricochet also added
- - medbots can now be tipped over
- - added more medbot sounds
-
-
- 17 June 2020
- SmArtKar updated:
-
- - New ID icons
- - Sutures and Meshes
-
- Trilbyspaceclone updated:
-
- - Gin export takes gin now
-
GoonStation 13 Development Team
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index f8749c8dc5..e03cc15b14 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -26998,3 +26998,30 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- refactor: snowflake code for mutant bodypart selection has been rewritten to be
~14x shorter
- tweak: meat type and horns can now be selected by any species
+2020-08-20:
+ DeltaFire15:
+ - bugfix: The cooking oil damage formula is no longer scuffed.
+ - tweak: Changed the clockie help-link to lead to our own wiki.
+ Fikou:
+ - admin: admins can now do html in ahelps properly
+ Hatterhat:
+ - balance: Pirate threats are now announced as "business propositions", and their
+ arrivals are now also announced properly.
+ tiramisuapimancer:
+ - bugfix: Ethereal hair is now their body color instead of accidentally white
+2020-08-21:
+ LetterN:
+ - rscadd: Updates and adds some of the tips
+ Putnam3145:
+ - admin: added reftracking as a compile flag
+ SmArtKar:
+ - tweak: RSD limitation is now 500 tiles
+ - bugfix: Fixed broken RSD sprites
+ - config: Removed that shuttle limit
+ timothyteakettle:
+ - bugfix: two snouts can once again be chosen in customization
+ - bugfix: lizard snouts work again
+2020-08-22:
+ Time-Green (copypasta'd by lolman360):
+ - rscadd: plumbing
+ - rscadd: automatic hydro trays
diff --git a/icons/obj/lavaland/terrain.dmi b/icons/obj/lavaland/terrain.dmi
new file mode 100644
index 0000000000..4db51145ee
Binary files /dev/null and b/icons/obj/lavaland/terrain.dmi differ
diff --git a/icons/obj/machines/medipen_refiller.dmi b/icons/obj/machines/medipen_refiller.dmi
new file mode 100644
index 0000000000..300d218d2d
Binary files /dev/null and b/icons/obj/machines/medipen_refiller.dmi differ
diff --git a/icons/obj/plumbing/fluid_ducts.dmi b/icons/obj/plumbing/fluid_ducts.dmi
new file mode 100644
index 0000000000..87d9d2233b
Binary files /dev/null and b/icons/obj/plumbing/fluid_ducts.dmi differ
diff --git a/icons/obj/plumbing/plumbers.dmi b/icons/obj/plumbing/plumbers.dmi
new file mode 100644
index 0000000000..242622e000
Binary files /dev/null and b/icons/obj/plumbing/plumbers.dmi differ
diff --git a/icons/obj/watercloset.dmi b/icons/obj/watercloset.dmi
index 4a299f29dd..2b64176878 100644
Binary files a/icons/obj/watercloset.dmi and b/icons/obj/watercloset.dmi differ
diff --git a/strings/tips.txt b/strings/tips.txt
index a1d2befab3..5dc4e1b985 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -25,7 +25,7 @@ As the Captain, you have absolute access and control over the station, but this
As the Captain, you have a fancy pen that can be used as a holdout dagger or even as a scalpel in surgery!
As the Captain, you can purchase a new emergency shuttle using a communications console. Some require credits, while others give you credits in exchange. Keep in mind that purchasing dangerous shuttles will incur the ire of your crew.
As the Chief Medical Officer, your hypospray is like the ones that your Medical Doctors can buy, except it comes in a fancy box that can hold several more hypovials than the standard, and already comes preloaded with specially-made high-capacity hypovials that hold double the reagents the standard ones do.
-As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and geneticists during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
+As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and paramedics during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
As a Medical Doctor, pester Research for improved surgical tools. They work faster, combine the purposes of several tools in one (scalpel/saw, retractor/hemostat, drill/cautery), and don't cost many materials to boot!
As a Medical Doctor, the surgical saw and drill are both powerful weapons, the saw is sharp and can slice and dice, while the drill can quickly blind someone if aimed for the eyes. The laser scalpel is an upgraded version producible with Research's aid, and it has the highest force of most common place weapons, while still remaining sharp.
As a Medical Doctor, your belt can hold a full set of surgical tools. Using sterilizine before each attempt during surgery will reduce your failure chance on tricky steps or when using less-than-optimal equipment.
@@ -34,11 +34,14 @@ As a Medical Doctor, while both heal toxin damage, the difference between charco
As a Medical Doctor, you can surgically implant or extract things from people's chests by performing a cavity implant. This could range from inserting a suicide bomb to embedding the nuke disk into the Captain's chest.
As a Medical Doctor, it's of utmost urgency that you tend to anyone who's been hugged by a facehugger. You only have a couple of minutes from the initial attachment to perform organ manipulation to their chest and remove the rapidly developing alien embryo before it bursts out and immediately kills your patient.
As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. Using disarm attempt will intentionally fail the surgery step.
-As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't usually revivable or cloneable. However it may prove useful to be creative in your revivification techniques with these bodies.
+As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and can't be revived. However it may prove useful to be creative in your revivification techniques with these bodies.
As a Medical Doctor, treating plasmamen is not impossible! Salbutamol and epinephrine stops them from suffocating due to lack of internals and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
As a Medical Doctor, you can point your penlight at people to create a medical hologram. This lets them know that you're coming to treat them.
As a Medical Doctor, you can extract implants by holding an empty implant case in your offhand while performing the extraction step.
As a Medical Doctor, clone scanning people will implant them with a health tracker that displays their vitals in the clone records. Useful to check on crew members that didn't activate suit sensors!
+As a Medical Doctor, you can deal with patients who have absurd amounts of wounds by putting them in cryo. This will slowly treat all of their wounds simultaneously, but is much slower than direct treatment.
+As a Medical Doctor, Critical Slash wounds are one of the most dangerous conditions someone can have. Apply gauze, epipens, sutures, cauteries, whatever you can, as soon as possible!
+As a Medical Doctor, Saline-Glucose not only acts as a temporary boost to a patient's blood level, it also speeds regeneration! Perfect for drained patients!
As a Medical Doctor, medical gauze is an incredibly underrated tool. It can be used to entirely halt a limb from bleeding or sling one that's been shattered until it can be given proper attention. This even works on the dead, too! Be sure to stop someone's bleeding whether they're in critical condition or a corpse, as dragging someone whom is bleeding will rapidly deplete them of all their blood.
As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. See which chemicals have the best synergy, both in healing, and in harming. Experiment!
As a Chemist, some chemicals can only be synthesized by heating up the contents in the chemical heater.
@@ -135,10 +138,10 @@ As a Security Officer, examining someone while wearing your security HUDglasses
As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell.
As a Security Officer, you can just about any firearm on your vest, this even works with other non-standard armor-substitutes like security winter coats!
As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department.
-As the Detective, you can use your forensics scanner from a distance.
+As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers.
As the Detective, your revolver can be loaded with .357 ammunition. Use a screwdriver to permanently modify your revolver into using this type of ammunition, be warned however, firing it has a decent chance to cause the revolver to misfire and shoot you in the foot.
As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime.
-As the Lawyer, you can try to convince the captain and Head of Security to hold trials for prisoners in the courtroom.
+As the Lawyer, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom.
As the Head of Personnel, you are not higher ranking than other heads of staff, even though you are expected to take the Captain's place first should he go missing. If the situation seems too rough for you, consider allowing another head to become temporary Captain.
As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out and your comparative vulnerability.
As the Mime, your invisible wall power blocks people as well as projectiles. You can use it in a pinch to delay your pursuer.
@@ -187,7 +190,7 @@ As a Shaft Miner, every monster on Lavaland has a pattern you can exploit to min
As a Shaft Miner, you can harvest goliath plates from goliaths and upgrade your explorer's suit, mining hardsuits as well as Firefighter APLUs with them, greatly reducing incoming melee damage.
As a Shaft Miner, always have a GPS on you, so a fellow miner or cyborg can come to save you if you die.
As a Shaft Miner, you can craft a variety of equipment from the local fauna. Bone axes, lava boats and ash drake armour are just a few of them!
-As a Traitor, the cryptographic sequencer (emag) can not only open doors, but also lockers, crates, APCs and more. It can hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you may just be able to create some difficult to obtain substances, or contact your employers to request special objectives! Experiment!
+As a Traitor, the cryptographic sequencer (emag) can not only open lockers, crates, APCs and more. It can also do things like hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even contact the Syndicate. Experiment!
As a Traitor, subverting the AI to serve you can make it an extremely powerful ally. However, be careful of the wording in the laws you give it, as it may use your poorly written laws against you!
As a Traitor, the Captain and the Head of Security are two of the most difficult to kill targets on the station. If either one is your target, plan carefully.
As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool if you manage to nab an autolathe for yourself.
@@ -235,6 +238,7 @@ As a Changeling, absorbing someone will give you their full memory. This can inc
As a Changeling, absorbing another Changeling will permanently boost your chemical reserve, allow you to pick more abilities, and make the victim unable to revive. Be careful when exposing your identity to other Changelings, as they may be out of those wonderful benefits.
As a Changeling, BZ gas will dramatically slow down or even halt your natural chemical regeneration, be sure to avoid it at all costs as some lunatics may try and flood portions of the station to deal with you.
As a Changeling, death is not the end for you! You can revive after two minutes from being dead by triggering your stasis ability, and then waiting for the prompt to resurrect yourself to show up.
+As a Changeling, your Regenerate Limbs power will quickly heal all of your wounds, but they'll still leave scars. Changelings can use Fleshmend to get rid of scars, or you can ingest Carpotoxin to get rid of them like a normal person.
As a Cultist, do not cause too much chaos before your objective is completed. If the shuttle gets called too soon, you may not have enough time to win.
As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face.
As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, stamina damage to Ratvarian scum, and some damage to fellow cultists of Nar-Sie nearby, but will create a fire where the rune stands on use.
@@ -281,12 +285,17 @@ As a Devil, you gain power for every three souls you control, however you also b
As a Devil, as long as you control at least one other soul, you will automatically resurrect, as long as a banishment ritual is not performed.
At which time a Devil's nameth is spake on the tongue of man, the Devil may appeareth.
You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other.
-When hacking doors, cutting and mending the "test light wire" will restore power to the door.
-When hacking, remote singulars pulse when attached to a wire and pinged. This can allow you to hack things or set traps from far away.
+When hacking doors, cutting and mending a "test light wire" will restore power to the door.
When crafting most items, you can either manually combine parts or use the crafting menu.
Suit storage units not only remove blood and dirt from clothing, but also radiation!
-Suit storage units entirely purge radiation from any carbon mob put inside of them when cycling, at the cost of some horrific burns, this is a very effective strategy to clean someone up after they bathed in the engine.
Remote devices will work when used through cameras. For example: Bluespace RPEDs and door remotes.
+You can light a cigar on a supermatter crystal.
+Using sticky tape on items can make them stick to people and walls! Be careful, grenades might stick to your hand during the moment of truth!
+In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity?
+Wearing riot armor makes you significantly more effective at performing tackle takedowns, but will use extra stamina with each leap! It will also significantly protect you from other tackles!
+Epipens contain a powerful coagulant that drastically reduces bleeding on all bleeding wounds. If you don't have time to properly treat someone with lots of slashes or piercings, stick them with a pen to buy some time!
+Anything you can light a cigarette with, you can use to cauterize a bleeding wound. Technically, that includes the supermatter.
+Suit storage units entirely purge radiation from any carbon mob put inside of them when cycling, at the cost of some horrific burns, this is a very effective strategy to clean someone up after they bathed in the engine.
Laser pointers can be upgraded by replacing its micro laser with a better one from RnD! Use a screwdriver on it to remove the old laser. Upgrading the laser pointer gives you better odds of stunning a cyborg, and even blinding people with sunglasses.
Being out of combat mode makes makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by resting.
Resting makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by being out of combat mode.
diff --git a/tgstation.dme b/tgstation.dme
index 8effb0cf93..4114606a2f 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -81,6 +81,7 @@
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
+#include "code\__DEFINES\plumbing.dm"
#include "code\__DEFINES\pool.dm"
#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\preferences.dm"
@@ -302,6 +303,7 @@
#include "code\controllers\subsystem\events.dm"
#include "code\controllers\subsystem\fail2topic.dm"
#include "code\controllers\subsystem\fire_burning.dm"
+#include "code\controllers\subsystem\fluid.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\holodeck.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
@@ -376,6 +378,7 @@
#include "code\datums\datumvars.dm"
#include "code\datums\dna.dm"
#include "code\datums\dog_fashion.dm"
+#include "code\datums\ductnet.dm"
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\explosion.dm"
@@ -491,6 +494,11 @@
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
+#include "code\datums\components\plumbing\_plumbing.dm"
+#include "code\datums\components\plumbing\chemical_acclimator.dm"
+#include "code\datums\components\plumbing\filter.dm"
+#include "code\datums\components\plumbing\reaction_chamber.dm"
+#include "code\datums\components\plumbing\splitter.dm"
#include "code\datums\components\storage\storage.dm"
#include "code\datums\components\storage\ui.dm"
#include "code\datums\components\storage\concrete\_concrete.dm"
@@ -1284,6 +1292,7 @@
#include "code\game\objects\structures\crates_lockers\crates\secure.dm"
#include "code\game\objects\structures\crates_lockers\crates\wooden.dm"
#include "code\game\objects\structures\icemoon\cave_entrance.dm"
+#include "code\game\objects\structures\lavaland\geyser.dm"
#include "code\game\objects\structures\lavaland\necropolis_tendril.dm"
#include "code\game\objects\structures\signs\_signs.dm"
#include "code\game\objects\structures\signs\signs_departments.dm"
@@ -1396,6 +1405,7 @@
#include "code\modules\admin\view_variables\mark_datum.dm"
#include "code\modules\admin\view_variables\mass_edit_variables.dm"
#include "code\modules\admin\view_variables\modify_variables.dm"
+#include "code\modules\admin\view_variables\reference_tracking.dm"
#include "code\modules\admin\view_variables\topic.dm"
#include "code\modules\admin\view_variables\topic_basic.dm"
#include "code\modules\admin\view_variables\topic_list.dm"
@@ -2863,6 +2873,21 @@
#include "code\modules\photography\photos\album.dm"
#include "code\modules\photography\photos\frame.dm"
#include "code\modules\photography\photos\photo.dm"
+#include "code\modules\plumbing\ducts.dm"
+#include "code\modules\plumbing\plumbers\_plumb_machinery.dm"
+#include "code\modules\plumbing\plumbers\acclimator.dm"
+#include "code\modules\plumbing\plumbers\autohydro.dm"
+#include "code\modules\plumbing\plumbers\bottler.dm"
+#include "code\modules\plumbing\plumbers\destroyer.dm"
+#include "code\modules\plumbing\plumbers\fermenter.dm"
+#include "code\modules\plumbing\plumbers\filter.dm"
+#include "code\modules\plumbing\plumbers\grinder_chemical.dm"
+#include "code\modules\plumbing\plumbers\medipenrefill.dm"
+#include "code\modules\plumbing\plumbers\pill_press.dm"
+#include "code\modules\plumbing\plumbers\pumps.dm"
+#include "code\modules\plumbing\plumbers\reaction_chamber.dm"
+#include "code\modules\plumbing\plumbers\splitters.dm"
+#include "code\modules\plumbing\plumbers\synthesizer.dm"
#include "code\modules\pool\pool_controller.dm"
#include "code\modules\pool\pool_drain.dm"
#include "code\modules\pool\pool_effects.dm"
|