diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm
index 706f2d760d5..fa90b1ee083 100644
--- a/code/__DEFINES/DNA.dm
+++ b/code/__DEFINES/DNA.dm
@@ -213,10 +213,8 @@ GLOBAL_LIST_INIT(organ_process_order, list(
ORGAN_SLOT_XENO_RESINSPINNER,
ORGAN_SLOT_XENO_ACIDGLAND,
ORGAN_SLOT_XENO_NEUROTOXINGLAND,
- ORGAN_SLOT_XENO_EGGSAC,))
-
-//Defines for Golem Species IDs
-#define SPECIES_GOLEM "golem"
+ ORGAN_SLOT_XENO_EGGSAC,
+))
// Defines for used in creating "perks" for the species preference pages.
/// A key that designates UI icon displayed on the perk.
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
index 2e11369e52d..53fa1021a0f 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
@@ -80,6 +80,9 @@
#define COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL "mob_mind_transferred_out_of"
#define CANCEL_ROLL (1<<1)
+///signal sent when a mob has their holy role set. Sent to the mob having their role changed.
+#define COMSIG_MOB_MIND_SET_HOLY_ROLE "mob_mind_set_holy_role"
+
///from base of obj/allowed(mob/M): (/obj) returns ACCESS_ALLOWED if mob has id access to the obj
#define COMSIG_MOB_TRIED_ACCESS "tried_access"
#define ACCESS_ALLOWED (1<<0)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 4109dd9a7d3..aafd2954450 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -115,6 +115,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(
//Human sub-species
#define isabductor(A) (is_species(A, /datum/species/abductor))
+#define isspirit(A) (is_species(A, /datum/species/spirit))
#define isgolem(A) (is_species(A, /datum/species/golem))
#define islizard(A) (is_species(A, /datum/species/lizard))
#define isashwalker(A) (is_species(A, /datum/species/lizard/ashwalker))
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 9dd06dae572..0c48ccdcf43 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -153,18 +153,20 @@
#define BODYTYPE_PLANT (1<<6)
//This limb is shadowy and will regen if shadowheal is active
#define BODYTYPE_SHADOW (1<<7)
+//This limb is a ghost limb and can phase through walls.
+#define BODYTYPE_GHOST (1<<8)
// SKYRAT EDIT ADDITION
/// Nanomachine bodypart
-#define BODYTYPE_NANO (1<<8)
+#define BODYTYPE_NANO (1<<9)
///The limb fits a modular custom shape
-#define BODYSHAPE_CUSTOM (1<<9)
+#define BODYSHAPE_CUSTOM (1<<10)
///The limb fits a taur body
-#define BODYSHAPE_TAUR (1<<10)
+#define BODYSHAPE_TAUR (1<<11)
///The limb causes shoes to no longer be displayed, useful for taurs.
-#define BODYSHAPE_HIDE_SHOES (1<<11)
+#define BODYSHAPE_HIDE_SHOES (1<<12)
///The limb causes glasses and hats to be drawn on layers 5 and 4 respectively. Currently used for snouts with the (Top) suffix, which are drawn on layer 6 and would normally cover facewear
-#define BODYSHAPE_ALT_FACEWEAR_LAYER (1<<12)
+#define BODYSHAPE_ALT_FACEWEAR_LAYER (1<<13)
// SKYRAT EDIT END
@@ -188,11 +190,14 @@
#define SPECIES_DULLAHAN "dullahan"
#define SPECIES_ETHEREAL "ethereal"
#define SPECIES_ETHEREAL_LUSTROUS "lustrous"
+#define SPECIES_GHOST "ghost"
+#define SPECIES_GOLEM "golem"
#define SPECIES_FELINE "felinid"
#define SPECIES_FLYPERSON "fly"
#define SPECIES_HUMAN "human"
#define SPECIES_JELLYPERSON "jelly"
#define SPECIES_SLIMEPERSON "slime"
+#define SPECIES_SPIRIT "spirit"
#define SPECIES_LUMINESCENT "luminescent"
#define SPECIES_STARGAZER "stargazer"
#define SPECIES_LIZARD "lizard"
diff --git a/code/__DEFINES/surgery.dm b/code/__DEFINES/surgery.dm
index 4ebd7bad0b7..a79522e6e21 100644
--- a/code/__DEFINES/surgery.dm
+++ b/code/__DEFINES/surgery.dm
@@ -32,10 +32,12 @@
#define ORGAN_HAZARDOUS (1<<12)
/// This is an external organ, not an inner one. Used in several checks.
#define ORGAN_EXTERNAL (1<<13)
+/// This is a ghost organ, which can be used for wall phasing.
+#define ORGAN_GHOST (1<<14)
/// This is a mutant organ, having this makes you a -derived mutant to health analyzers.
-#define ORGAN_MUTANT (1<<14)
+#define ORGAN_MUTANT (1<<15)
/// BUBBER EDIT ADD - Nanoswarm organ
-#define ORGAN_NANOMACHINE (1<<15)
+#define ORGAN_NANOMACHINE (1<<16)
/// Scarring on the right eye
#define RIGHT_EYE_SCAR (1<<0)
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 7b8d40cd831..7cd1083b798 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -750,6 +750,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Cannot be turned into a funny skeleton by the plasma river
#define TRAIT_NO_PLASMA_TRANSFORM "no_plasma_transform"
+/// Mind trait that allows you to see blessed tiles.
+#define TRAIT_SEE_BLESSED_TILES "sees_blessed_tiles"
+
/// Weather immunities, also protect mobs inside them.
#define TRAIT_LAVA_IMMUNE "lava_immune" //Used by lava turfs and The Floor Is Lava.
#define TRAIT_ASHSTORM_IMMUNE "ashstorm_immune"
@@ -976,6 +979,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// This mob always lands on their feet when they fall, for better or for worse.
#define TRAIT_CATLIKE_GRACE "catlike_grace"
+///Won't show up on cameras when they snap a photo.
+#define TRAIT_INVISIBLE_TO_CAMERA "invisible_to_camera"
+
///if the atom has a sticker attached to it
#define TRAIT_STICKERED "stickered"
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index bd374ac0a67..edea6b63478 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -33,6 +33,8 @@
#define CHANGELING_TRAIT "changeling"
#define CULT_TRAIT "cult"
#define LICH_TRAIT "lich"
+///Comes from a holy source.
+#define HOLY_TRAIT "holy"
#define VENDING_MACHINE_TRAIT "vending_machine"
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 046a269a910..9b72332fd42 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -311,6 +311,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_GARLIC_BREATH" = TRAIT_GARLIC_BREATH,
"TRAIT_GARLIC_REAGENT" = TRAIT_GARLIC_REAGENT,
"TRAIT_GENELESS" = TRAIT_GENELESS,
+ "TRAIT_GHOSTROLE_ON_REVIVE" = TRAIT_GHOSTROLE_ON_REVIVE,
"TRAIT_GIANT" = TRAIT_GIANT,
"TRAIT_GODMODE" = TRAIT_GODMODE,
"TRAIT_GOOD_HEARING" = TRAIT_GOOD_HEARING,
@@ -357,6 +358,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_IS_WET" = TRAIT_IS_WET,
"TRAIT_IWASBATONED" = TRAIT_IWASBATONED,
"TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH,
+ "TRAIT_SEE_BLESSED_TILES" = TRAIT_SEE_BLESSED_TILES,
"TRAIT_SYNDIE_KISS" = TRAIT_SYNDIE_KISS,
"TRAIT_KNOCKEDOUT" = TRAIT_KNOCKEDOUT,
"TRAIT_KNOW_ENGI_WIRES" = TRAIT_KNOW_ENGI_WIRES,
@@ -624,9 +626,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NOGRAV_ALWAYS_DRIFT" = TRAIT_NOGRAV_ALWAYS_DRIFT,
"TRAIT_SPEECH_BOOSTER" = TRAIT_SPEECH_BOOSTER,
"TRAIT_MINING_PARRYING" = TRAIT_MINING_PARRYING,
- "TRAIT_ILLUSORY_EFFECT" = TRAIT_ILLUSORY_EFFECT,
- "TRAIT_GHOSTROLE_ON_REVIVE" = TRAIT_GHOSTROLE_ON_REVIVE,
"TRAIT_IGNORE_FIRE_PROTECTION" = TRAIT_IGNORE_FIRE_PROTECTION,
+ "TRAIT_ILLUSORY_EFFECT" = TRAIT_ILLUSORY_EFFECT,
+ "TRAIT_INVISIBLE_TO_CAMERA" = TRAIT_INVISIBLE_TO_CAMERA,
"TRAIT_LEFT_EYE_SCAR" = TRAIT_LEFT_EYE_SCAR,
"TRAIT_RIGHT_EYE_SCAR" = TRAIT_RIGHT_EYE_SCAR,
"HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT" = HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT,
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 6b40f5858b9..4062759c1fa 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -763,6 +763,9 @@
var/mob/living/carbon/C = usr
C.check_self_for_injuries()
+/atom/movable/screen/healthdoll/proc/update_body_zones()
+ return
+
/atom/movable/screen/healthdoll/living
icon_state = "fullhealth0"
screen_loc = ui_living_healthdoll
@@ -777,15 +780,22 @@
/atom/movable/screen/healthdoll/human/Initialize(mapload, datum/hud/hud_owner)
. = ..()
+ if(isnull(hud_owner)) //we require a hud owner to work properly, so return out.
+ return
+ update_body_zones()
+ update_appearance()
+
+/atom/movable/screen/healthdoll/human/update_body_zones()
limbs = list()
- for(var/i in GLOB.all_body_zones)
+ vis_contents.Cut()
+ var/mob/living/carbon/human/owner = hud.mymob
+ for(var/body_zone in owner.get_all_limbs())
var/atom/movable/screen/healthdoll_limb/limb = new(src, null)
// layer chest above other limbs, it's the center after all
- limb.layer = i == BODY_ZONE_CHEST ? layer + 0.05 : layer
- limbs[i] = limb
+ limb.layer = body_zone == BODY_ZONE_CHEST ? layer + 0.05 : layer
+ limbs[body_zone] = limb
// why viscontents? why not overlays? - because i want to animate filters
vis_contents += limb
- update_appearance()
/atom/movable/screen/healthdoll/human/Destroy()
QDEL_LIST_ASSOC_VAL(limbs)
diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm
index e676a877864..ad05f409ff6 100644
--- a/code/datums/mind/_mind.dm
+++ b/code/datums/mind/_mind.dm
@@ -139,6 +139,9 @@
if(NAMEOF(src, assigned_role))
set_assigned_role(var_value)
. = TRUE
+ if(NAMEOF(src, holy_role))
+ set_holy_role(var_value)
+ . = TRUE
if(!isnull(.))
datum_flags |= DF_VAR_EDITED
return
@@ -508,6 +511,22 @@
. = assigned_role
assigned_role = new_role
+///Sets your holy role, giving/taking away traits related to if you're gaining/losing it.
+/datum/mind/proc/set_holy_role(new_holy_role)
+ if(holy_role == new_holy_role)
+ return
+ var/was_holy = holy_role
+ holy_role = new_holy_role
+ if(holy_role)
+ ADD_TRAIT(src, TRAIT_SEE_BLESSED_TILES, HOLY_TRAIT)
+ else
+ REMOVE_TRAIT(src, TRAIT_SEE_BLESSED_TILES, HOLY_TRAIT)
+ SEND_SIGNAL(current, COMSIG_MOB_MIND_SET_HOLY_ROLE, new_holy_role)
+ //the signal stops tracking when losing holy roles, but since we're gaining it, give us our HUDs if we're becoming holy.
+ if(!was_holy && holy_role)
+ for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
+ blessed_hud.check_hud(current)
+
/// Sets us to the passed job datum, then greets them to their new job.
/// Use this one for when you're assigning this mind to a new job for the first time,
/// or for when someone's receiving a job they'd really want to be greeted to.
diff --git a/code/datums/storage/storage.dm b/code/datums/storage/storage.dm
index 0f5b2b4deb7..ca22f81573e 100644
--- a/code/datums/storage/storage.dm
+++ b/code/datums/storage/storage.dm
@@ -921,7 +921,7 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
return open_storage_on_signal(source, user) ? CLICK_ACTION_SUCCESS : NONE
/// Opens the storage to the mob, showing them the contents to their UI.
-/datum/storage/proc/open_storage(mob/to_show, can_reach_target = parent) // BUBBER EDIT CHANGE - ORIGINAL: /datum/storage/proc/open_storage(mob/to_show)
+/datum/storage/proc/open_storage(mob/living/to_show, can_reach_target = parent) // BUBBER EDIT CHANGE - ORIGINAL: /datum/storage/proc/open_storage(mob/living/to_show)
if(isobserver(to_show))
show_contents(to_show)
return FALSE
@@ -929,6 +929,14 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
if(!isliving(to_show) || !to_show.can_perform_action(can_reach_target, ALLOW_RESTING | FORBID_TELEKINESIS_REACH)) // BUBBER EDIT CHANGE -- ORIGINAL: if(!isliving(to_show) || !to_show.can_perform_action(parent, ALLOW_RESTING | FORBID_TELEKINESIS_REACH))
return FALSE
+ //because of the check above, it's safe to assume we're living now.
+ if(!(to_show.mobility_flags & MOBILITY_STORAGE))
+ return FALSE
+
+ //because of the check above, it's safe to assume we're living now.
+ if(!(to_show.mobility_flags & MOBILITY_STORAGE))
+ return FALSE
+
if(locked)
if(!silent)
parent.balloon_alert(to_show, "closed!")
diff --git a/code/game/atom/alternate_appearance.dm b/code/game/atom/alternate_appearance.dm
index 09b5c1190d1..6df75d2508a 100644
--- a/code/game/atom/alternate_appearance.dm
+++ b/code/game/atom/alternate_appearance.dm
@@ -101,6 +101,13 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
var/add_ghost_version = FALSE
var/datum/atom_hud/alternate_appearance/basic/observers/ghost_appearance
uses_global_hud_category = FALSE
+ ///List of signals we hook onto which we'll update HUDs when we receive this.
+ var/list/signals_registering = list(
+ COMSIG_MOB_ANTAGONIST_REMOVED,
+ COMSIG_MOB_GHOSTIZED,
+ COMSIG_MOB_MIND_TRANSFERRED_INTO,
+ COMSIG_MOB_MIND_TRANSFERRED_OUT_OF,
+ )
/datum/atom_hud/alternate_appearance/basic/New(key, image/I, options = AA_TARGET_SEE_APPEARANCE)
..()
@@ -131,20 +138,10 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
QDEL_NULL(ghost_appearance)
/datum/atom_hud/alternate_appearance/basic/track_mob(mob/new_viewer)
- RegisterSignals(new_viewer, list(
- COMSIG_MOB_ANTAGONIST_REMOVED,
- COMSIG_MOB_GHOSTIZED,
- COMSIG_MOB_MIND_TRANSFERRED_INTO,
- COMSIG_MOB_MIND_TRANSFERRED_OUT_OF,
- ), PROC_REF(check_hud), override = TRUE)
+ RegisterSignals(new_viewer, signals_registering, PROC_REF(check_hud), override = TRUE)
/datum/atom_hud/alternate_appearance/basic/untrack_mob(mob/former_viewer)
- UnregisterSignal(former_viewer, list(
- COMSIG_MOB_ANTAGONIST_REMOVED,
- COMSIG_MOB_GHOSTIZED,
- COMSIG_MOB_MIND_TRANSFERRED_INTO,
- COMSIG_MOB_MIND_TRANSFERRED_OUT_OF,
- ))
+ UnregisterSignal(former_viewer, signals_registering)
/datum/atom_hud/alternate_appearance/basic/add_atom_to_hud(atom/A)
LAZYINITLIST(A.hud_list)
@@ -189,13 +186,18 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
antag_datum_type = /datum/antagonist/cult
/datum/atom_hud/alternate_appearance/basic/blessed_aware
+ signals_registering = list(
+ COMSIG_MOB_ANTAGONIST_REMOVED,
+ COMSIG_MOB_GHOSTIZED,
+ COMSIG_MOB_MIND_TRANSFERRED_INTO,
+ COMSIG_MOB_MIND_TRANSFERRED_OUT_OF,
+ COMSIG_MOB_MIND_SET_HOLY_ROLE,
+ )
-/datum/atom_hud/alternate_appearance/basic/blessed_aware/mobShouldSee(mob/M)
- if(M.mind?.holy_role)
- return TRUE
- if (istype(M, /mob/living/basic/construct/wraith))
- return TRUE
- if(isrevenant(M) || IS_WIZARD(M) || IS_HERETIC(M))
+/datum/atom_hud/alternate_appearance/basic/blessed_aware/mobShouldSee(mob/viewing_mob)
+ if(!viewing_mob.mind)
+ return FALSE
+ if(HAS_MIND_TRAIT(viewing_mob, TRAIT_SEE_BLESSED_TILES))
return TRUE
return FALSE
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 517fe1a65ae..eda6b70b69d 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -314,7 +314,7 @@
return
return ..()
-/obj/machinery/door/proc/try_to_activate_door(mob/user, access_bypass = FALSE)
+/obj/machinery/door/proc/try_to_activate_door(mob/living/user, access_bypass = FALSE)
add_fingerprint(user)
if(operating || (obj_flags & EMAGGED) || !can_open_with_hands)
return
@@ -328,6 +328,10 @@
run_animation(DOOR_DENY_ANIMATION)
/obj/machinery/door/allowed(mob/M)
+ if(isliving(M))
+ var/mob/living/living_user = M
+ if(!(living_user.mobility_flags & MOBILITY_USE))
+ return FALSE
if(emergency)
return TRUE
if(unrestricted_side(M))
diff --git a/code/game/objects/effects/blessing.dm b/code/game/objects/effects/blessing.dm
index 5308625a4d9..540be85db4e 100644
--- a/code/game/objects/effects/blessing.dm
+++ b/code/game/objects/effects/blessing.dm
@@ -9,19 +9,18 @@
/obj/effect/blessing/Initialize(mapload)
. = ..()
- for(var/obj/effect/blessing/B in loc)
- if(B != src)
- return INITIALIZE_HINT_QDEL
- var/image/I = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = ABOVE_NORMAL_TURF_LAYER, loc = src)
- I.alpha = 64
- I.appearance_flags = RESET_ALPHA
- add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, "blessing", I)
+ var/image/blessing_icon = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = ABOVE_NORMAL_TURF_LAYER, loc = src)
+ blessing_icon.alpha = 64
+ blessing_icon.appearance_flags = RESET_ALPHA
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, "blessing", blessing_icon)
+
RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_cult_teleport))
/obj/effect/blessing/Destroy()
UnregisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORTING)
return ..()
+///Called from intercept teleport signal, blocks cult teleporting from being able to teleport on us.
/obj/effect/blessing/proc/block_cult_teleport(datum/source, channel, turf/origin)
SIGNAL_HANDLER
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index d5525f5393c..3a0e8de4a0a 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -606,12 +606,15 @@
return
return attempt_pickup(user)
-/obj/item/proc/attempt_pickup(mob/user, skip_grav = FALSE)
+/obj/item/proc/attempt_pickup(mob/living/user, skip_grav = FALSE)
. = TRUE
if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup.
return
+ if(!(user.mobility_flags & MOBILITY_PICKUP))
+ return
+
if(!skip_grav)
//Heavy gravity makes picking up things very slow.
var/grav = user.has_gravity()
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 5d09226154e..92bb20f69d4 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -229,7 +229,7 @@
if(mode == SCANNER_VERBOSE)
// Follow same body zone list every time so it's consistent across all humans
- for(var/zone in GLOB.all_body_zones)
+ for(var/zone in carbontarget.get_all_limbs())
var/obj/item/bodypart/limb = carbontarget.get_bodypart(zone)
if(isnull(limb))
dmgreport += "
"
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 93379d0436b..3f1ae1fb3b4 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -248,8 +248,7 @@
/// Checks a bunch of stuff to see if we can heal the patient, including can_heal
/// Gives a feedback if we can't ultimatly heal the patient (unless silent is TRUE)
/obj/item/stack/medical/proc/try_heal_checks(mob/living/patient, mob/living/user, healed_zone, silent = FALSE)
- if(!(healed_zone in GLOB.all_body_zones))
- stack_trace("Invalid zone ([healed_zone || "null"]) passed to try_heal_checks.")
+ if(!(healed_zone in patient.get_all_limbs()))
healed_zone = BODY_ZONE_CHEST
if(!can_heal(patient, user, healed_zone, silent))
@@ -747,7 +746,7 @@
oof_ouch.apply_wound(bone, wound_source = "bone gel")
var/datum/wound/blunt/bone/critical/oof_OUCH = new
oof_OUCH.apply_wound(bone, wound_source = "bone gel")
- for(var/zone in GLOB.all_body_zones)
+ for(var/zone in patient.get_all_limbs())
patient.apply_damage(60, BRUTE, zone)
use(1)
return BRUTELOSS
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index c0c7f060805..ec8ed1ea802 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -919,7 +919,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune) \
)
- AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
+ AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
/obj/item/melee/skateboard/holyboard/proc/on_cult_rune_removed(obj/effect/target, mob/living/user)
SIGNAL_HANDLER
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index a1c3f213d1a..d80fd500987 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -433,6 +433,9 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
return TRUE
if(welded || locked)
return FALSE
+ if(isliving(user))
+ if(!(user.mobility_flags & MOBILITY_USE))
+ return FALSE
if(strong_grab)
if(user)
to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening."))
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 3212516f471..c7517f2ee8d 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -516,6 +516,8 @@ GLOBAL_LIST_EMPTY(station_turfs)
qdel(L)
/turf/proc/Bless()
+ if(locate(/obj/effect/blessing) in src)
+ return
new /obj/effect/blessing(src)
//////////////////////////////
diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm
index 325bdd70a60..45c7bd1c957 100644
--- a/code/modules/antagonists/ert/ert.dm
+++ b/code/modules/antagonists/ert/ert.dm
@@ -132,14 +132,14 @@
/datum/antagonist/ert/medic/inquisitor/on_gain()
. = ..()
- owner.holy_role = HOLY_ROLE_PRIEST
+ owner.set_holy_role(HOLY_ROLE_PRIEST)
/datum/antagonist/ert/security/inquisitor
outfit = /datum/outfit/centcom/ert/security/inquisitor
/datum/antagonist/ert/security/inquisitor/on_gain()
. = ..()
- owner.holy_role = HOLY_ROLE_PRIEST
+ owner.set_holy_role(HOLY_ROLE_PRIEST)
/datum/antagonist/ert/chaplain
role = "Chaplain"
@@ -150,14 +150,14 @@
/datum/antagonist/ert/chaplain/on_gain()
. = ..()
- owner.holy_role = HOLY_ROLE_PRIEST
+ owner.set_holy_role(HOLY_ROLE_PRIEST)
/datum/antagonist/ert/commander/inquisitor
outfit = /datum/outfit/centcom/ert/commander/inquisitor
/datum/antagonist/ert/commander/inquisitor/on_gain()
. = ..()
- owner.holy_role = HOLY_ROLE_PRIEST
+ owner.set_holy_role(HOLY_ROLE_PRIEST)
/datum/antagonist/ert/intern
name = "CentCom Intern"
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index 69ed76df700..b9472e0081c 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -274,6 +274,7 @@
gain_knowledge(starting_knowledge)
+ ADD_TRAIT(owner, TRAIT_SEE_BLESSED_TILES, REF(src))
addtimer(CALLBACK(src, PROC_REF(passive_influence_gain)), passive_gain_timer) // Gain +1 knowledge every 20 minutes.
return ..()
@@ -283,6 +284,7 @@
var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
knowledge.on_lose(owner.current, src)
+ REMOVE_TRAIT(owner, TRAIT_SEE_BLESSED_TILES, REF(src))
QDEL_LIST_ASSOC_VAL(researched_knowledge)
return ..()
diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm
index 01000da5908..8bb77ab71c0 100644
--- a/code/modules/antagonists/survivalist/survivalist.dm
+++ b/code/modules/antagonists/survivalist/survivalist.dm
@@ -51,11 +51,13 @@
/datum/antagonist/survivalist/magic/on_gain()
. = ..()
- ADD_TRAIT(owner, TRAIT_MAGICALLY_GIFTED, REF(src))
+ owner.add_traits(list(TRAIT_MAGICALLY_GIFTED, TRAIT_SEE_BLESSED_TILES), REF(src))
+ for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
+ blessed_hud.check_hud(owner.current)
/datum/antagonist/survivalist/magic/on_removal()
- REMOVE_TRAIT(owner, TRAIT_MAGICALLY_GIFTED, REF(src))
- return..()
+ owner.remove_traits(list(TRAIT_MAGICALLY_GIFTED, TRAIT_SEE_BLESSED_TILES), REF(src))
+ return ..()
/// Applied by the battle royale objective
/datum/antagonist/survivalist/battle_royale
diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm
index 617908b5ba6..db9203fdf49 100644
--- a/code/modules/antagonists/wizard/wizard.dm
+++ b/code/modules/antagonists/wizard/wizard.dm
@@ -59,10 +59,12 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key)
/datum/antagonist/wizard_minion/on_gain()
create_objectives()
. = ..()
- ADD_TRAIT(owner, TRAIT_MAGICALLY_GIFTED, REF(src))
+ owner.add_traits(list(TRAIT_MAGICALLY_GIFTED, TRAIT_SEE_BLESSED_TILES), REF(src))
+ for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
+ blessed_hud.check_hud(owner.current)
/datum/antagonist/wizard_minion/on_removal()
- REMOVE_TRAIT(owner, TRAIT_MAGICALLY_GIFTED, REF(src))
+ owner.remove_traits(list(TRAIT_MAGICALLY_GIFTED, TRAIT_SEE_BLESSED_TILES), REF(src))
return ..()
/datum/antagonist/wizard_minion/proc/create_objectives()
@@ -90,7 +92,9 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key)
. = ..()
if(allow_rename)
rename_wizard()
- ADD_TRAIT(owner, TRAIT_MAGICALLY_GIFTED, REF(src))
+ owner.add_traits(list(TRAIT_MAGICALLY_GIFTED, TRAIT_SEE_BLESSED_TILES), REF(src))
+ for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
+ blessed_hud.check_hud(owner.current)
/datum/antagonist/wizard/Destroy()
QDEL_NULL(ritual)
@@ -184,7 +188,7 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key)
qdel(spell)
owner.current.actions -= spell
- REMOVE_TRAIT(owner, TRAIT_MAGICALLY_GIFTED, REF(src))
+ owner.remove_traits(list(TRAIT_MAGICALLY_GIFTED, TRAIT_SEE_BLESSED_TILES), REF(src))
return ..()
/datum/antagonist/wizard/proc/equip_wizard()
diff --git a/code/modules/jobs/job_types/chaplain/chaplain.dm b/code/modules/jobs/job_types/chaplain/chaplain.dm
index 6de60d67004..6f84c0f840a 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain.dm
@@ -45,7 +45,7 @@
var/obj/item/book/bible/booze/holy_bible = new
if(GLOB.religion)
if(human_spawned.mind)
- human_spawned.mind.holy_role = HOLY_ROLE_PRIEST
+ human_spawned.mind.set_holy_role(HOLY_ROLE_PRIEST)
holy_bible.deity_name = GLOB.deity
holy_bible.name = GLOB.bible_name
// These checks are important as there's no guarantee the "HOLY_ROLE_HIGHPRIEST" chaplain has selected a bible skin.
@@ -62,7 +62,7 @@
GLOB.religious_sect.on_conversion(human_spawned)
return
if(human_spawned.mind)
- human_spawned.mind.holy_role = HOLY_ROLE_HIGHPRIEST
+ human_spawned.mind.set_holy_role(HOLY_ROLE_HIGHPRIEST)
var/new_religion = player_client?.prefs?.read_preference(/datum/preference/name/religion) || DEFAULT_RELIGION
var/new_deity = player_client?.prefs?.read_preference(/datum/preference/name/deity) || DEFAULT_DEITY
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
index 4d958b7e7ff..ff0690ffaed 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
@@ -35,7 +35,7 @@
on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \
)
- AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
+ AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
if((!GLOB.holy_weapon_type || !station_holy_item) && type == /obj/item/nullrod)
var/list/rods = list()
diff --git a/code/modules/library/bibles.dm b/code/modules/library/bibles.dm
index 4b84c32873e..112f75aa402 100644
--- a/code/modules/library/bibles.dm
+++ b/code/modules/library/bibles.dm
@@ -371,12 +371,12 @@ GLOBAL_LIST_INIT(bibleitemstates, list(
tip_text = "Clear rune", \
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \
)
- AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
+ AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
/obj/item/book/bible/syndicate/attack_self(mob/living/carbon/human/user, modifiers)
if(!uses || !istype(user))
return
- user.mind.holy_role = HOLY_ROLE_PRIEST
+ user.mind.set_holy_role(HOLY_ROLE_PRIEST)
uses -= 1
to_chat(user, span_userdanger("You try to open the book AND IT BITES YOU!"))
playsound(src.loc, 'sound/effects/snap.ogg', 50, TRUE)
diff --git a/code/modules/lost_crew/damages/melee.dm b/code/modules/lost_crew/damages/melee.dm
index 4896930772a..462e22a2b41 100644
--- a/code/modules/lost_crew/damages/melee.dm
+++ b/code/modules/lost_crew/damages/melee.dm
@@ -13,7 +13,7 @@
var/hits = ((max_hits - min_hits) * severity + min_hits)
for(var/i in 1 to hits)
weapon.attack(body, body) //needs an attacker, no reason it cant be the body as well
- body.zone_selected = pick(GLOB.all_body_zones)
+ body.zone_selected = pick(body.get_all_limbs())
/datum/corpse_damage/cause_of_death/melee_weapon/proc/get_weapon(mob/living/carbon/human/body)
return new weapon(null)
diff --git a/code/modules/lost_crew/damages/projectile.dm b/code/modules/lost_crew/damages/projectile.dm
index 64541cf1eaf..f38ef63dc45 100644
--- a/code/modules/lost_crew/damages/projectile.dm
+++ b/code/modules/lost_crew/damages/projectile.dm
@@ -13,7 +13,7 @@
var/hits = ((max_hits - min_hits) * severity + min_hits)
for(var/i in 1 to hits)
- body.projectile_hit(projectile, def_zone = pick(GLOB.all_body_zones), piercing_hit = TRUE)
+ body.projectile_hit(projectile, def_zone = pick(body.get_all_limbs()), piercing_hit = TRUE)
/datum/corpse_damage/cause_of_death/projectile/laser
projectile = /obj/projectile/beam/laser
diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm
index c881d9dce45..00620f1074d 100644
--- a/code/modules/mining/equipment/mining_tools.dm
+++ b/code/modules/mining/equipment/mining_tools.dm
@@ -373,7 +373,7 @@
var/atom/throw_target = get_edge_target_turf(target_mob, get_dir(user, get_step_away(target_mob, user)))
target_mob.throw_at(throw_target, 2, 2, user, gentle = TRUE)
target_mob.Knockdown(2 SECONDS)
- var/body_zone = pick(GLOB.all_body_zones)
+ var/body_zone = pick(user.get_all_limbs())
user.apply_damage(force / recoil_factor, BRUTE, body_zone, user.run_armor_check(body_zone, MELEE))
to_chat(user, span_danger("The weight of the Big Slappy recoils!"))
log_combat(user, user, "recoiled Big Slappy into")
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index a2e605fdcc3..db9ded291ca 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -66,6 +66,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
/// The POI we're orbiting (orbit menu)
var/orbiting_ref
+ ///The description camera obscuras have when they get a photo of us.
+ var/photo_description = "You can also see a g-g-g-g-ghooooost!"
var/static/list/observer_hud_traits = list(
TRAIT_SECURITY_HUD,
TRAIT_MEDICAL_HUD,
@@ -148,7 +150,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
/mob/dead/observer/get_photo_description(obj/item/camera/camera)
if(!invisibility || camera.see_ghosts)
- return "You can also see a g-g-g-g-ghooooost!"
+ return photo_description
/mob/dead/observer/narsie_act()
var/old_color = color
diff --git a/code/modules/mob/living/basic/cult/constructs/wraith.dm b/code/modules/mob/living/basic/cult/constructs/wraith.dm
index e73718d4fe3..141c23bdcc2 100644
--- a/code/modules/mob/living/basic/cult/constructs/wraith.dm
+++ b/code/modules/mob/living/basic/cult/constructs/wraith.dm
@@ -20,6 +20,7 @@
/mob/living/basic/construct/wraith/Initialize(mapload)
. = ..()
+ ADD_TRAIT(src, TRAIT_SEE_BLESSED_TILES, INNATE_TRAIT)
var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/jaunt = locate() in actions
if(isnull(jaunt))
return .
diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm
index 5ac15722a5b..2f8c4318802 100644
--- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm
+++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm
@@ -95,7 +95,7 @@
/mob/living/basic/revenant/Initialize(mapload)
. = ..()
AddElement(/datum/element/simple_flying)
- add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT)
+ add_traits(list(TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_SEE_BLESSED_TILES), INNATE_TRAIT)
grant_actions_by_list(abilities)
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index 759b04c91e1..27e50cd4b4f 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -498,6 +498,13 @@
desc = "This juicy piece of meat has a oversized brain stem and cerebellum, with not much of a limbic system to speak of at all. You would expect its owner to be pretty cold blooded."
organ_traits = list(TRAIT_TACKLING_TAILED_DEFENDER)
+/obj/item/organ/brain/ghost
+ name = "ghost brain"
+ desc = "How are you even able to hold this?"
+ icon_state = "brain-ghost"
+ movement_type = PHASING
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
/obj/item/organ/brain/abductor
name = "grey brain"
desc = "A piece of juicy meat found in an ayy lmao's head."
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 0323136b0c6..56ce9cb85c5 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -922,7 +922,7 @@
/// Creates body parts for this carbon completely from scratch.
/// Optionally takes a map of body zones to what type to instantiate instead of them.
/mob/living/carbon/proc/create_bodyparts(list/overrides)
- var/list/bodyparts_paths = bodyparts.Copy()
+ var/list/bodyparts_paths = overrides?.Copy() || bodyparts.Copy()
bodyparts = list()
for(var/obj/item/bodypart/bodypart_path as anything in bodyparts_paths)
var/real_body_part_path = overrides?[initial(bodypart_path.body_zone)] || bodypart_path
@@ -969,7 +969,6 @@
synchronize_bodytypes()
synchronize_bodyshapes()
-
///Proc to hook behavior on bodypart removals. Do not directly call. You're looking for [/obj/item/bodypart/proc/drop_limb()].
/mob/living/carbon/proc/remove_bodypart(obj/item/bodypart/old_bodypart, special)
SHOULD_NOT_OVERRIDE(TRUE)
@@ -1297,6 +1296,13 @@
return
AddComponent(/datum/component/rot, 6 MINUTES, 10 MINUTES, 1)
+/mob/living/carbon/get_photo_description(obj/item/camera/camera)
+ if(HAS_TRAIT(src, TRAIT_INVISIBLE_TO_CAMERA))
+ if(camera.see_ghosts)
+ return /mob/dead/observer::photo_description
+ return null
+ return ..()
+
/**
* This proc is used to determine whether or not the mob can handle touching an acid affected object.
*/
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 558e9e6e28c..225084863c9 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -49,12 +49,10 @@
if(get_bodypart(BODY_ZONE_HEAD) && !get_organ_by_type(/obj/item/organ/brain))
. += span_deadsay("It appears that [t_his] brain is missing...")
- var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
var/list/disabled = list()
for(var/obj/item/bodypart/body_part as anything in bodyparts)
if(body_part.bodypart_disabled)
disabled += body_part
- missing -= body_part.body_zone
for(var/obj/item/embedded as anything in body_part.embedded_objects)
if(embedded.get_embed().stealthy_embed)
continue
@@ -85,16 +83,16 @@
//stores missing limbs
var/l_limbs_missing = 0
var/r_limbs_missing = 0
- for(var/gone in missing)
- if(gone == BODY_ZONE_HEAD)
- . += span_deadsay("[t_His] [parse_zone(gone)] is missing!")
+ for(var/missing_limb in get_missing_limbs())
+ if(missing_limb == BODY_ZONE_HEAD)
+ . += span_deadsay("[t_His] [parse_zone(missing_limb)] is missing!")
continue
- if(gone == BODY_ZONE_L_ARM || gone == BODY_ZONE_L_LEG)
+ if(missing_limb == BODY_ZONE_L_ARM || missing_limb == BODY_ZONE_L_LEG)
l_limbs_missing++
- else if(gone == BODY_ZONE_R_ARM || gone == BODY_ZONE_R_LEG)
+ else if(missing_limb == BODY_ZONE_R_ARM || missing_limb == BODY_ZONE_R_LEG)
r_limbs_missing++
- . += span_boldwarning("[capitalize(t_his)] [parse_zone(gone)] is missing!")
+ . += span_boldwarning("[capitalize(t_his)] [parse_zone(missing_limb)] is missing!")
if(l_limbs_missing >= 2 && r_limbs_missing == 0)
. += span_tinydanger("[t_He] look[p_s()] all right now...")
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index ccc66e12bc9..16cd84c494d 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -388,6 +388,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
human_who_gained_species.mob_respiration_type = inherent_respiration_type
human_who_gained_species.butcher_results = knife_butcher_results?.Copy()
+ //update body zones to match what they are supposed to have
+ human_who_gained_species.hud_used?.healthdoll.update_body_zones()
+
// BUBBER EDIT BEGIN - Bake digitigrade into the species on apply
try_make_digitigrade(human_who_gained_species)
// BUBBER EDIT END
@@ -438,6 +441,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
human_who_gained_species.living_flags &= ~STOP_OVERLAY_UPDATE_BODY_PARTS
+ //we don't allow it to update during species transition, so update it now
+ human_who_gained_species.hud_used?.healthdoll.update_appearance()
+
/**
* Proc called when a carbon is no longer this species.
*
@@ -639,6 +645,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
switch(slot)
if(ITEM_SLOT_HANDS)
+ if(!(H.mobility_flags & MOBILITY_PICKUP))
+ return FALSE
if(H.get_empty_held_indexes())
return TRUE
return FALSE
@@ -2066,7 +2074,10 @@ GLOBAL_LIST_EMPTY(features_by_species)
/// Creates body parts for the target completely from scratch based on the species
/datum/species/proc/create_fresh_body(mob/living/carbon/target)
- target.create_bodyparts(bodypart_overrides)
+ var/list/override_limbs = list()
+ for(var/bodypart in bodypart_overrides)
+ override_limbs += bodypart_overrides[bodypart]
+ target.create_bodyparts(override_limbs)
/**
* Checks if the species has a head with these head flags, by default.
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 169072407aa..bea69cc45ce 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -747,7 +747,8 @@
// Updates the health bar, also sends signal
. = ..()
// Handles changing limb colors and stuff
- hud_used.healthdoll?.update_appearance()
+ if(!(living_flags & STOP_OVERLAY_UPDATE_BODY_PARTS))
+ hud_used.healthdoll?.update_appearance()
/mob/living/carbon/human/fully_heal(heal_flags = HEAL_ALL)
if(heal_flags & HEAL_NEGATIVE_MUTATIONS)
@@ -1204,6 +1205,12 @@
/mob/living/carbon/human/species/lizard/silverscale
race = /datum/species/lizard/silverscale
+/mob/living/carbon/human/species/spirit
+ race = /datum/species/spirit
+
+/mob/living/carbon/human/species/ghost
+ race = /datum/species/spirit/ghost
+
/mob/living/carbon/human/species/ethereal
race = /datum/species/ethereal
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 83f2339b7e4..3b469cf4ab3 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -566,7 +566,7 @@
combined_msg += span_notice("You check yourself for injuries.")
- var/list/missing = GLOB.all_body_zones.Copy()
+ var/list/missing = get_all_limbs()
for(var/obj/item/bodypart/body_part as anything in bodyparts)
missing -= body_part.body_zone
diff --git a/code/modules/mob/living/carbon/human/species_types/ghost.dm b/code/modules/mob/living/carbon/human/species_types/ghost.dm
new file mode 100644
index 00000000000..78785177b85
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/ghost.dm
@@ -0,0 +1,256 @@
+///Spirit mob that lacks legs but still roams the station as part of the unliving.
+/datum/species/spirit
+ name = "Spirit"
+ id = SPECIES_SPIRIT
+ inherent_traits = list(
+ TRAIT_GENELESS,
+ TRAIT_NEVER_WOUNDED,
+ TRAIT_NOBLOOD,
+ TRAIT_NODISMEMBER,
+ TRAIT_NO_DNA_COPY,
+ TRAIT_NO_PLASMA_TRANSFORM,
+ TRAIT_NO_UNDERWEAR,
+ TRAIT_UNHUSKABLE,
+ TRAIT_NO_FLOATING_ANIM,
+ TRAIT_MOVE_FLYING,
+ )
+ inherent_biotypes = MOB_SPIRIT
+ no_equip_flags = ITEM_SLOT_FEET
+ changesource_flags = MIRROR_BADMIN | WABBAJACK | SLIME_EXTRACT
+ sexes = FALSE
+ meat = /obj/item/ectoplasm
+
+ mutantheart = null
+ mutantappendix = null
+ mutantears = /obj/item/organ/ears/ghost
+ mutantstomach = /obj/item/organ/stomach/ghost
+ mutantliver = /obj/item/organ/liver/ghost
+ mutantlungs = /obj/item/organ/lungs/ghost
+ mutanteyes = /obj/item/organ/eyes/ghost
+ mutantbrain = /obj/item/organ/brain/ghost
+ mutanttongue = /obj/item/organ/tongue/ghost
+
+ bodypart_overrides = list(
+ BODY_ZONE_HEAD = /obj/item/bodypart/head/ghost,
+ BODY_ZONE_CHEST = /obj/item/bodypart/chest/ghost/spirit,
+ BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/ghost,
+ BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/ghost,
+ )
+
+ ///Boolean on whether this species type is available at roundstart during halloween, used to deny subtypes.
+ var/halloween_exclusive = TRUE
+
+/datum/species/spirit/check_roundstart_eligible()
+ if(check_holidays(HALLOWEEN) && halloween_exclusive)
+ return TRUE
+ return ..()
+
+/datum/species/spirit/get_physical_attributes()
+ return "Spirits are the spiritual remains of long-passed entities. They lack legs, can fly, but still eat, breathe, hear and see."
+
+/datum/species/spirit/get_species_description()
+ return "Spirits are spirits of long-dead creatures whom, for one reason or another, still roam around."
+
+/datum/species/spirit/get_species_lore()
+ return list(
+ "Spirits are the non-physical remains that linger onto their mortal coil. \
+ They still need their protein and organs to keep themselves \"alive\", \
+ which leads to many of them still believing they are still part of the living, \
+ whether or not they are is a very open-ended debate between philosophers.",
+ )
+
+/datum/species/spirit/create_pref_unique_perks()
+ var/list/to_add = list()
+
+ to_add += list(list(
+ SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK,
+ SPECIES_PERK_ICON = "body",
+ SPECIES_PERK_NAME = "Leg-less",
+ SPECIES_PERK_DESC = "Ghosts lack legs and float, preventing you from falling into holes in the ground.",
+ ))
+
+ to_add += list(list(
+ SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK,
+ SPECIES_PERK_ICON = "shoe-prints",
+ SPECIES_PERK_NAME = "No Feet",
+ SPECIES_PERK_DESC = "You lack feet, therefore the ability to wear any shoes!",
+ ))
+
+ return to_add
+
+/**
+ * Ghost subtype
+ * This is the type of ghost that can actually phase through walls,
+ * exclusive to magic mirrors & admins, as roundstart-ability to phase anywhere
+ * is not something that is generally fun to play against.
+ */
+/datum/species/spirit/ghost
+ name = "Ghost"
+ id = SPECIES_GHOST
+ inherent_traits = list(
+ TRAIT_GENELESS,
+ TRAIT_NEVER_WOUNDED,
+ TRAIT_NOBLOOD,
+ TRAIT_NODISMEMBER,
+ TRAIT_NO_DNA_COPY,
+ TRAIT_NO_PLASMA_TRANSFORM,
+ TRAIT_NO_UNDERWEAR,
+ TRAIT_UNHUSKABLE,
+ TRAIT_NO_FLOATING_ANIM,
+ TRAIT_MOVE_FLYING,
+ //ghost-unique
+ TRAIT_SEE_BLESSED_TILES,
+ )
+ //they have a different chest.
+ bodypart_overrides = list(
+ BODY_ZONE_HEAD = /obj/item/bodypart/head/ghost,
+ BODY_ZONE_CHEST = /obj/item/bodypart/chest/ghost,
+ BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/ghost,
+ BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/ghost,
+ )
+ changesource_flags = MIRROR_BADMIN | MIRROR_PRIDE | MIRROR_MAGIC
+ halloween_exclusive = FALSE
+
+ ///Innate passthrough ability given to ghosts that allows them to phase but drops their stuff.
+ var/datum/action/innate/toggle_passthrough/passthrough_ability
+
+/datum/species/spirit/ghost/get_physical_attributes()
+ return "Ghosts are the spiritual remains of long-passed entities. They lack legs, can fly, can choose at will to become incorporeal, \
+ but still eat, breathe, hear and see."
+
+/datum/species/spirit/ghost/get_species_lore()
+ return list(
+ "Ghosts are one of the spookiest creatures known in the galaxy. \
+ While they still need their protein to sustain themselves, they are able to control their own bodies, \
+ going through walls and getting rid of all their posessions at will. \
+ Most knowledge known about them is kept secret by Nanotrasen's top Chaplains, who are keen \
+ to keep it private.",
+ )
+
+/datum/species/spirit/ghost/on_species_gain(mob/living/carbon/human/new_ghost, datum/species/old_species, pref_load, regenerate_icons)
+ . = ..()
+ passthrough_ability = new(src)
+ passthrough_ability.Grant(new_ghost)
+ for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
+ blessed_hud.check_hud(new_ghost)
+
+/datum/species/spirit/ghost/on_species_loss(mob/living/carbon/human/former_ghost, datum/species/new_species, pref_load)
+ . = ..()
+ QDEL_NULL(passthrough_ability)
+ //this has to be called after parent so inherent traits is cleared before we update our HUDs
+ for(var/datum/atom_hud/alternate_appearance/basic/blessed_aware/blessed_hud in GLOB.active_alternate_appearances)
+ blessed_hud.check_hud(former_ghost)
+
+/datum/species/spirit/ghost/create_pref_unique_perks()
+ var/list/to_add = ..()
+
+ to_add += list(list(
+ SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK,
+ SPECIES_PERK_ICON = "ghost",
+ SPECIES_PERK_NAME = "Incorporeal",
+ SPECIES_PERK_DESC = "Ghost are able to control their body to the extent where you can willingly make yourself able \
+ to phase through anything, including your own equipment.",
+ ))
+
+ return to_add
+
+/**
+ * Passthrough ability
+ *
+ * Ghost innate ability that allows them to enter ghost mode,
+ * which gives them the phasing ability, but makes them drop everything
+ * and completely unable to wear anything.
+ */
+/datum/action/innate/toggle_passthrough
+ name = "Toggle passthrough"
+ desc = "Toggles your ability to phase through everything, including your gear and any incompatible organs/limbs."
+ button_icon = 'icons/hud/actions.dmi'
+ button_icon_state = "ghost"
+
+/datum/action/innate/toggle_passthrough/Grant(mob/grant_to)
+ . = ..()
+ RegisterSignal(grant_to, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_new_limb))
+
+/datum/action/innate/toggle_passthrough/Remove(mob/remove_from)
+ swap_mode(force_off = TRUE)
+ UnregisterSignal(remove_from, COMSIG_CARBON_POST_ATTACH_LIMB)
+ return ..()
+
+/datum/action/innate/toggle_passthrough/Activate()
+ . = ..()
+ if(!iscarbon(owner))
+ return
+ swap_mode()
+
+/datum/action/innate/toggle_passthrough/IsAvailable(feedback)
+ if(!isliving(owner))
+ return FALSE
+ var/mob/living/living_owner = owner
+ if(living_owner.has_reagent(/datum/reagent/water/holywater))
+ return FALSE
+ //technically you can trap a ghost by blessing them as theyre phasing,
+ //but they can still be dragged out.
+ if(locate(/obj/effect/blessing) in get_turf(owner))
+ return FALSE
+ var/obj/item/bodypart/chest/their_chest = living_owner.get_bodypart(BODY_ZONE_CHEST)
+ if(!their_chest || !(their_chest.bodytype & BODYTYPE_GHOST))
+ return FALSE
+ return ..()
+
+///Called when the owner of this action gets a new limb, if it isn't a ghost-limb
+///the action will turn itself off, and you'll lose said limb if you try using this action again.
+/datum/action/innate/toggle_passthrough/proc/on_new_limb(mob/source, obj/item/bodypart/new_part, special)
+ SIGNAL_HANDLER
+ if(!iscarbon(owner))
+ return
+ if(!(new_part.bodytype & BODYTYPE_GHOST))
+ swap_mode(force_off = TRUE)
+
+///Swaps the mode, allowing us to phase through stuff but drops everything. Optional 'force_off' arg to prevent being able to turn it on.
+/datum/action/innate/toggle_passthrough/proc/swap_mode(force_off)
+ //we can only turn off, early return if we're trying to turn it on instead.
+ if(force_off && HAS_TRAIT_FROM(owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT))
+ return
+
+ var/mob/living/carbon/carbon_owner = owner
+ var/datum/species/carbon_species = carbon_owner.dna.species
+
+ //drop any limbs & organs that can't phase, now that you're phasing.
+ for(var/obj/item/bodypart/bodypart as anything in carbon_owner.bodyparts)
+ if(!(bodypart.bodytype & BODYTYPE_GHOST))
+ bodypart.drop_limb(special = FALSE, dismembered = FALSE, move_to_floor = TRUE)
+ for(var/obj/item/organ/organ as anything in carbon_owner.organs)
+ if(!(organ.organ_flags & ORGAN_GHOST))
+ organ.Remove(owner, special = FALSE)
+
+ if(HAS_TRAIT_FROM(carbon_owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT))
+ REMOVE_TRAIT(carbon_owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT)
+ carbon_owner.add_traits(list(
+ TRAIT_MOVE_PHASING,
+ TRAIT_PIERCEIMMUNE,
+ TRAIT_INVISIBLE_TO_CAMERA,
+ TRAIT_HANDS_BLOCKED, //MOBILITY_USE | MOBILITY_PICKUP | MOBILITY_STORAGE
+ TRAIT_PULL_BLOCKED, //MOBILITY_PULL
+ TRAIT_UI_BLOCKED, //MOBILITY_UI
+ ), SPECIES_TRAIT)
+ carbon_species.update_no_equip_flags(carbon_owner, ALL)
+ RegisterSignal(carbon_owner, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, PROC_REF(attempt_move))
+ else
+ ADD_TRAIT(carbon_owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT)
+ carbon_owner.remove_traits(list(
+ TRAIT_MOVE_PHASING,
+ TRAIT_PIERCEIMMUNE,
+ TRAIT_INVISIBLE_TO_CAMERA,
+ TRAIT_HANDS_BLOCKED,
+ TRAIT_PULL_BLOCKED,
+ TRAIT_UI_BLOCKED,
+ ), SPECIES_TRAIT)
+ carbon_species.update_no_equip_flags(carbon_owner, initial(carbon_species.no_equip_flags))
+ UnregisterSignal(carbon_owner, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE)
+
+///Called when attempting to move to a new tile while the action is active, returns to cancel moving.
+/datum/action/innate/toggle_passthrough/proc/attempt_move(mob/source, new_loc, direct)
+ SIGNAL_HANDLER
+ if(locate(/obj/effect/blessing) in new_loc)
+ to_chat(source, span_warning("Holy energies block your path!"))
+ return COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index e610f05bb54..77921120468 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -85,7 +85,7 @@
to_add += list(list(
SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK,
- SPECIES_PERK_ICON = "pickaxe",
+ SPECIES_PERK_ICON = "tools",
SPECIES_PERK_NAME = "Natural Miners",
SPECIES_PERK_DESC = "Golems can see dimly in the dark, sense minerals, break boulders, and mine stone with their bare hands. \
They can even smelt ores in an internal furnace, if their surrounding environment is hot enough.",
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index fe1b697352b..b03e31f1063 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2664,6 +2664,8 @@ GLOBAL_LIST_EMPTY(fire_appearances)
/// Proc to append behavior to the condition of being handsblocked. Called when the condition starts.
/mob/living/proc/on_handsblocked_start()
+ if(active_storage)
+ active_storage.hide_contents(src)
drop_all_held_items()
add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_HANDS_BLOCKED)
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 6d1d404f35e..f6290670449 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -94,8 +94,7 @@
//Reload alternate appearances
for(var/datum/atom_hud/alternate_appearance/alt_hud as anything in GLOB.active_alternate_appearances)
- if(!alt_hud.apply_to_new_mob(src))
- alt_hud.hide_from(src, absolute = TRUE)
+ alt_hud.check_hud(src)
update_client_colour()
update_mouse_pointer()
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index 1c6ac03dffd..9e9f635fe50 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -305,7 +305,9 @@
mobs_spotted += mob
if(mob.stat == DEAD)
dead_spotted += mob
- desc += mob.get_photo_description(src)
+ var/info = mob.get_photo_description(src)
+ if(!isnull(info))
+ desc += info
var/psize_x = (size_x * 2 + 1) * ICON_SIZE_X
var/psize_y = (size_y * 2 + 1) * ICON_SIZE_Y
diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm
index 8f850ab1cf2..2b0387d6728 100644
--- a/code/modules/photography/camera/camera_image_capturing.dm
+++ b/code/modules/photography/camera/camera_image_capturing.dm
@@ -44,10 +44,12 @@
lighting_overlay.underlays += backdrop
lighting_overlay.blend_mode = BLEND_MULTIPLY
lighting += lighting_overlay
- for(var/i in T.contents)
- var/atom/A = i
- if(!A.invisibility || (see_ghosts && isobserver(A)))
- atoms += new /obj/effect/appearance_clone(newT, A)
+ for(var/atom/found_atom as anything in T.contents)
+ if(HAS_TRAIT(found_atom, TRAIT_INVISIBLE_TO_CAMERA))
+ if(see_ghosts)
+ atoms += new /obj/effect/appearance_clone(newT, found_atom)
+ else if(!found_atom.invisibility || (see_ghosts && isobserver(found_atom)))
+ atoms += new /obj/effect/appearance_clone(newT, found_atom)
skip_normal = TRUE
wipe_atoms = TRUE
center = locate(cloned_center_x, cloned_center_y, bottom_left.z)
@@ -64,7 +66,7 @@
lighting += lighting_overlay
for(var/atom/movable/A in T)
if(A.invisibility)
- if(!(see_ghosts && isobserver(A)))
+ if(!(see_ghosts && (isobserver(A) || HAS_TRAIT(A, TRAIT_INVISIBLE_TO_CAMERA))))
continue
atoms += A
CHECK_TICK
diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm
index 7189a64dbcb..450746c71a2 100644
--- a/code/modules/photography/photos/photo.dm
+++ b/code/modules/photography/photos/photo.dm
@@ -53,7 +53,7 @@
if(!seen)
P.mobs_seen -= seen_ref
continue
- if(!isobserver(seen))
+ if(!isobserver(seen) && !isspirit(seen))
continue
set_custom_materials(list(/datum/material/hauntium =SHEET_MATERIAL_AMOUNT))
grind_results = list(/datum/reagent/hauntium = 20)
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
index bdf7897afaf..ff826883b26 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
@@ -117,7 +117,7 @@
/obj/projectile/bullet/arrow/holy/Initialize(mapload)
. = ..()
//50 damage to revenants
- AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 30)
+ AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 30)
/// plastic arrows
// completely dogshit quality and they break when they hit something.
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
index cc6bfbb6477..daa938015cc 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
@@ -37,7 +37,7 @@
on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune) \
)
- AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
+ AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
/obj/item/gun/ballistic/bow/divine/proc/on_cult_rune_removed(obj/effect/target, mob/living/user)
SIGNAL_HANDLER
diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm
index 446e794436d..6a608059c49 100644
--- a/code/modules/religion/burdened/psyker.dm
+++ b/code/modules/religion/burdened/psyker.dm
@@ -179,7 +179,7 @@
on_clear_callback = CALLBACK(src, PROC_REF(on_cult_rune_removed)), \
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \
)
- AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25)
+ AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25)
name = pick(possible_names)
desc = possible_names[name]
diff --git a/code/modules/religion/deaconize.dm b/code/modules/religion/deaconize.dm
index 20e8465ea5e..ea20e85054a 100644
--- a/code/modules/religion/deaconize.dm
+++ b/code/modules/religion/deaconize.dm
@@ -65,7 +65,7 @@
if(honor && (potential_deacon in honor.guilty))
honor.guilty -= potential_deacon
to_chat(user, span_notice("[GLOB.deity] has bound [potential_deacon] to the code! They are now a holy role! (albeit the lowest level of such)"))
- potential_deacon.mind.holy_role = HOLY_ROLE_DEACON
+ potential_deacon.mind.set_holy_role(HOLY_ROLE_DEACON)
GLOB.religious_sect.on_conversion(potential_deacon)
playsound(get_turf(religious_tool), 'sound/effects/pray.ogg', 50, TRUE)
return TRUE
diff --git a/code/modules/religion/honorbound/honorbound_trauma.dm b/code/modules/religion/honorbound/honorbound_trauma.dm
index c26eb39f3b6..7b36d75f518 100644
--- a/code/modules/religion/honorbound/honorbound_trauma.dm
+++ b/code/modules/religion/honorbound/honorbound_trauma.dm
@@ -206,7 +206,7 @@
if(SCHOOL_NECROMANCY, SCHOOL_FORBIDDEN, SCHOOL_SANGUINE)
to_chat(user, span_userdanger("[GLOB.deity] is enraged by your use of forbidden magic!"))
lightningbolt(user)
- user.mind.holy_role = NONE
+ user.mind.set_holy_role(NONE)
qdel(src)
owner.add_mood_event("honorbound", /datum/mood_event/banished) //add mood event after we already cleared our events
to_chat(user, span_userdanger("You have been excommunicated! You are no longer holy!"))
diff --git a/code/modules/religion/sparring/sparring_datum.dm b/code/modules/religion/sparring/sparring_datum.dm
index 1e8abfc9f69..fbe0fe93070 100644
--- a/code/modules/religion/sparring/sparring_datum.dm
+++ b/code/modules/religion/sparring/sparring_datum.dm
@@ -287,7 +287,7 @@
to_chat(loser, span_userdanger("[GLOB.deity] is enraged by your lackluster sparring record!"))
lightningbolt(loser)
loser.add_mood_event("sparring", /datum/mood_event/banished)
- loser.mind.holy_role = NONE
+ loser.mind.set_holy_role(NONE)
to_chat(loser, span_userdanger("You have been excommunicated! You are no longer holy!"))
if(STAKES_MONEY_MATCH)
to_chat(loser, span_userdanger("You've lost all your money to [winner]!"))
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 442ba1975bd..d0d9d1e6e83 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -389,7 +389,7 @@
/mob/living/carbon/proc/regenerate_limbs(list/excluded_zones = list())
SEND_SIGNAL(src, COMSIG_CARBON_REGENERATE_LIMBS, excluded_zones)
- var/list/zone_list = GLOB.all_body_zones.Copy()
+ var/list/zone_list = get_all_limbs()
var/list/dismembered_by_copy = body_zone_dismembered_by?.Copy()
@@ -403,30 +403,31 @@
if(get_bodypart(limb_zone))
return FALSE
limb = newBodyPart(limb_zone, 0, 0)
- if(limb)
- if(!limb.try_attach_limb(src, TRUE))
- qdel(limb)
- return FALSE
- limb.update_limb(is_creating = TRUE)
- if (LAZYLEN(dismembered_by_copy))
- var/datum/scar/scaries = new
- var/datum/wound/loss/phantom_loss = new // stolen valor, really
- phantom_loss.loss_wounding_type = dismembered_by_copy?[limb_zone]
- if (phantom_loss.loss_wounding_type)
- scaries.generate(limb, phantom_loss)
- LAZYREMOVE(dismembered_by_copy, limb_zone) // in case we're using a passed list
- else
- qdel(scaries)
- qdel(phantom_loss)
+ if(!limb)
+ return
+ if(!limb.try_attach_limb(src, TRUE))
+ qdel(limb)
+ return FALSE
+ limb.update_limb(is_creating = TRUE)
+ if (LAZYLEN(dismembered_by_copy))
+ var/datum/scar/scaries = new
+ var/datum/wound/loss/phantom_loss = new // stolen valor, really
+ phantom_loss.loss_wounding_type = dismembered_by_copy?[limb_zone]
+ if (phantom_loss.loss_wounding_type)
+ scaries.generate(limb, phantom_loss)
+ LAZYREMOVE(dismembered_by_copy, limb_zone) // in case we're using a passed list
+ else
+ qdel(scaries)
+ qdel(phantom_loss)
- //Copied from /datum/species/proc/on_species_gain()
- for(var/obj/item/organ/organ_path as anything in dna.species.mutant_organs)
- //Load a persons preferences from DNA
- var/zone = initial(organ_path.zone)
- if(zone != limb_zone)
- continue
- var/obj/item/organ/new_organ = SSwardrobe.provide_type(organ_path)
- new_organ.Insert(src)
+ //Copied from /datum/species/proc/on_species_gain()
+ for(var/obj/item/organ/organ_path as anything in dna.species.mutant_organs)
+ //Load a persons preferences from DNA
+ var/zone = initial(organ_path.zone)
+ if(zone != limb_zone)
+ continue
+ var/obj/item/organ/new_organ = SSwardrobe.provide_type(organ_path)
+ new_organ.Insert(src)
- update_body_parts()
- return TRUE
+ update_body_parts()
+ return TRUE
diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm
index 1e817454451..c5fc38a8b6c 100644
--- a/code/modules/surgery/bodyparts/helpers.dm
+++ b/code/modules/surgery/bodyparts/helpers.dm
@@ -11,6 +11,15 @@
if(bodypart.body_zone == zone)
return bodypart
+///Returns TRUE/FALSE on whether the mob should have a limb in a given zone, used for species-restrictions.
+/mob/living/carbon/proc/should_have_limb(zone)
+ if(!zone || !dna)
+ return TRUE
+ var/datum/species/carbon_species = dna.species
+ if(zone in carbon_species.bodypart_overrides)
+ return TRUE
+ return FALSE
+
/// Replaces a single limb and deletes the old one if there was one
/mob/living/carbon/proc/del_and_replace_bodypart(obj/item/bodypart/new_limb, special)
var/obj/item/bodypart/old_limb = get_bodypart(new_limb.body_zone)
@@ -112,9 +121,21 @@
/mob/living/carbon/proc/get_hand_of_item(obj/item/I)
return get_bodypart(get_hand_zone_of_item(I))
-/mob/living/carbon/proc/get_missing_limbs()
+///Returns a list of all limbs this mob should have.
+/mob/living/proc/get_all_limbs() as /list
RETURN_TYPE(/list)
- var/list/full = GLOB.all_body_zones.Copy()
+ return GLOB.all_body_zones.Copy()
+
+///Returns a list of all limbs this mob should have.
+/mob/living/carbon/get_all_limbs()
+ if(dna)
+ return dna.species.bodypart_overrides.Copy()
+ return ..()
+
+///Returns a list of all missing limbs this mob should have on them, but don't.
+/mob/living/carbon/proc/get_missing_limbs() as /list
+ RETURN_TYPE(/list)
+ var/list/full = get_all_limbs()
for(var/zone in full)
if(get_bodypart(zone))
full -= zone
@@ -131,7 +152,7 @@
return list()
/mob/living/carbon/get_disabled_limbs()
- var/list/full = GLOB.all_body_zones.Copy()
+ var/list/full = get_all_limbs()
var/list/disabled = list()
for(var/zone in full)
var/obj/item/bodypart/affecting = get_bodypart(zone)
@@ -172,6 +193,8 @@
// FUCK YOU AUGMENT CODE - With love, Kapu
/mob/living/carbon/proc/newBodyPart(zone)
var/path = dna.species.bodypart_overrides[zone]
+ if(isnull(path))
+ return null
var/obj/item/bodypart/new_bodypart = new path()
return new_bodypart
diff --git a/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm
new file mode 100644
index 00000000000..adeaa04067c
--- /dev/null
+++ b/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm
@@ -0,0 +1,51 @@
+/obj/item/bodypart/head/ghost
+ icon = 'icons/mob/human/species/ghost.dmi'
+ icon_static = 'icons/mob/human/species/ghost.dmi'
+ icon_state = "ghost_head"
+ biological_state = BIO_FLESH
+ bodytype = BODYTYPE_GHOST
+ limb_id = SPECIES_GHOST
+ is_dimorphic = FALSE
+ should_draw_greyscale = FALSE
+ dmg_overlay_type = null
+
+ head_flags = HEAD_HAIR | HEAD_FACIAL_HAIR | HEAD_DEBRAIN
+ teeth_count = 0
+
+/obj/item/bodypart/chest/ghost
+ icon = 'icons/mob/human/species/ghost.dmi'
+ icon_static = 'icons/mob/human/species/ghost.dmi'
+ icon_state = "ghost_chest"
+ biological_state = BIO_FLESH
+ acceptable_bodyshape = BODYTYPE_GHOST
+ bodytype = BODYTYPE_GHOST
+ limb_id = SPECIES_GHOST
+ is_dimorphic = FALSE
+ should_draw_greyscale = FALSE
+ dmg_overlay_type = null
+ wing_types = null
+
+//slightly different sprite meant to differentiate spirit from ghost.
+/obj/item/bodypart/chest/ghost/spirit
+ icon_state = "spirit_chest"
+ limb_id = SPECIES_SPIRIT
+
+/obj/item/bodypart/arm/left/ghost
+ icon = 'icons/mob/human/species/ghost.dmi'
+ icon_static = 'icons/mob/human/species/ghost.dmi'
+ icon_state = "ghost_l_arm"
+ biological_state = BIO_FLESH|BIO_JOINTED
+ bodytype = BODYTYPE_GHOST
+ limb_id = SPECIES_GHOST
+ should_draw_greyscale = FALSE
+ dmg_overlay_type = null
+
+/obj/item/bodypart/arm/right/ghost
+ icon = 'icons/mob/human/species/ghost.dmi'
+ icon_static = 'icons/mob/human/species/ghost.dmi'
+ icon_state = "ghost_r_arm"
+ biological_state = BIO_FLESH|BIO_JOINTED
+ bodytype = BODYTYPE_GHOST
+ limb_id = SPECIES_GHOST
+ should_draw_greyscale = FALSE
+ dmg_overlay_type = null
diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm
index 774dfb4478e..826851f5e17 100644
--- a/code/modules/surgery/organs/internal/ears/_ears.dm
+++ b/code/modules/surgery/organs/internal/ears/_ears.dm
@@ -205,6 +205,13 @@
/datum/bodypart_overlay/mutant/cat_ears/color_image(image/overlay, layer, obj/item/bodypart/limb)
return // We color base ears manually above in get_image
+/obj/item/organ/ears/ghost
+ name = "ghost ears"
+ desc = "All the more to hear you... though it can't hear through walls."
+ icon_state = "ears-ghost"
+ movement_type = PHASING
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
/obj/item/organ/ears/penguin
name = "penguin ears"
desc = "The source of a penguin's happy feet."
diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm
index 87c3e5fad6a..26ecc2d864e 100644
--- a/code/modules/surgery/organs/internal/eyes/_eyes.dm
+++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm
@@ -1133,6 +1133,14 @@
pupils_name = "sensor clusters"
penlight_message = "are two clustered hemispheres of thermal sensors"
+/obj/item/organ/eyes/ghost
+ name = "ghost eyes"
+ desc = "Despite lacking pupils, these can see pretty well."
+ icon_state = "eyes-ghost"
+ blink_animation = FALSE
+ movement_type = PHASING
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
/obj/item/organ/eyes/snail
name = "snail eyes"
desc = "These eyes seem to have a large range, but might be cumbersome with glasses."
diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm
index 1bd09e48ed8..d887ad3d36c 100644
--- a/code/modules/surgery/organs/internal/liver/_liver.dm
+++ b/code/modules/surgery/organs/internal/liver/_liver.dm
@@ -220,6 +220,13 @@
liver_resistance = 0.333 * LIVER_DEFAULT_TOX_RESISTANCE // -66%
toxTolerance = 15 // complete toxin immunity like xenos have would be too powerful
+/obj/item/organ/liver/ghost
+ name = "ghost liver"
+ desc = "Processes the last alcohol they drank in their time alive."
+ icon_state = "liver-ghost"
+ movement_type = PHASING
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
/obj/item/organ/liver/cybernetic
name = "basic cybernetic liver"
desc = "A very basic device designed to mimic the functions of a human liver. Handles toxins slightly worse than an organic liver."
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index 2204a9c8b8f..f74da8d786d 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -974,6 +974,13 @@
/obj/item/organ/lungs/cybernetic/surplus/hear_breath_noise(mob/living/hearer)
return span_danger("[owner.p_Their()] lungs emit [breath_noise].")
+/obj/item/organ/lungs/ghost
+ name = "ghost lungs"
+ desc = "No one knows what this is even supposed to breathe."
+ icon_state = "lungs-ghost"
+ movement_type = PHASING
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
/obj/item/organ/lungs/lavaland
name = "blackened frilled lungs" // blackened from necropolis exposure
desc = "Exposure to the necropolis has mutated these lungs to breathe the air of Indecipheres, the lava-covered moon."
diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm
index ee7947717fb..585f18ee4cb 100644
--- a/code/modules/surgery/organs/internal/stomach/_stomach.dm
+++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm
@@ -559,4 +559,11 @@
foodtype_flags = PODPERSON_ORGAN_FOODTYPES
color = COLOR_LIME
+/obj/item/organ/stomach/ghost
+ name = "ghost stomach"
+ desc = "Ghosts eat plenty, you know? And it's not just your life, I swear!"
+ icon_state = "stomach-ghost"
+ movement_type = PHASING
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
#undef STOMACH_METABOLISM_CONSTANT
diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm
index ff5af0562a9..ab0c34545bd 100644
--- a/code/modules/surgery/organs/internal/tongue/_tongue.dm
+++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm
@@ -373,6 +373,15 @@
bomb = 50
fire = 100
+/obj/item/organ/tongue/ghost
+ name = "ghost tongue"
+ desc = "You feel spooked even thinking about someone talking through this."
+ icon_state = "tongue-ghost"
+ movement_type = PHASING
+ say_mod = "boos"
+ sense_of_taste = FALSE
+ organ_flags = parent_type::organ_flags | ORGAN_GHOST
+
/obj/item/organ/tongue/abductor
name = "superlingual matrix"
desc = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something."
diff --git a/code/modules/surgery/prosthetic_replacement.dm b/code/modules/surgery/prosthetic_replacement.dm
index f4d8e6ed826..e2e9aec1587 100644
--- a/code/modules/surgery/prosthetic_replacement.dm
+++ b/code/modules/surgery/prosthetic_replacement.dm
@@ -22,7 +22,7 @@
if(!iscarbon(target))
return FALSE
var/mob/living/carbon/carbon_target = target
- if(!carbon_target.get_bodypart(user.zone_selected)) //can only start if limb is missing
+ if(!carbon_target.get_bodypart(user.zone_selected) && carbon_target.should_have_limb(user.zone_selected)) //can only start if limb is missing
return TRUE
return FALSE
diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit.png
new file mode 100644
index 00000000000..c43c67f12a8
Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit_ghost.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit_ghost.png
new file mode 100644
index 00000000000..c43c67f12a8
Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit_ghost.png differ
diff --git a/config/game_options.txt b/config/game_options.txt
index c261ea3d305..a71fc262b0b 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -385,6 +385,7 @@ ROUNDSTART_RACES ethereal
#ROUNDSTART_RACES skeleton
#ROUNDSTART_RACES zombie
#ROUNDSTART_RACES pod
+#ROUNDSTART_RACES ghost
## Races that are radical departures to what a species is, so while they technically can be roundstart - it's probably not wise
#ROUNDSTART_RACES android
diff --git a/icons/hud/actions.dmi b/icons/hud/actions.dmi
index 19f8a470dde..071ba38f726 100644
Binary files a/icons/hud/actions.dmi and b/icons/hud/actions.dmi differ
diff --git a/icons/mob/human/species/ghost.dmi b/icons/mob/human/species/ghost.dmi
new file mode 100644
index 00000000000..42185e3db54
Binary files /dev/null and b/icons/mob/human/species/ghost.dmi differ
diff --git a/icons/obj/medical/organs/organs.dmi b/icons/obj/medical/organs/organs.dmi
index ba48010d7da..ea3fe2f29df 100644
Binary files a/icons/obj/medical/organs/organs.dmi and b/icons/obj/medical/organs/organs.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index b674778eae3..d20d2f8ee91 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -5558,6 +5558,7 @@
#include "code\modules\mob\living\carbon\human\species_types\ethereal.dm"
#include "code\modules\mob\living\carbon\human\species_types\felinid.dm"
#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm"
+#include "code\modules\mob\living\carbon\human\species_types\ghost.dm"
#include "code\modules\mob\living\carbon\human\species_types\golems.dm"
#include "code\modules\mob\living\carbon\human\species_types\humans.dm"
#include "code\modules\mob\living\carbon\human\species_types\jellypeople.dm"
@@ -6438,6 +6439,7 @@
#include "code\modules\surgery\bodyparts\wounds.dm"
#include "code\modules\surgery\bodyparts\species_parts\android_parts.dm"
#include "code\modules\surgery\bodyparts\species_parts\ethereal_bodyparts.dm"
+#include "code\modules\surgery\bodyparts\species_parts\ghost_bodyparts.dm"
#include "code\modules\surgery\bodyparts\species_parts\lizard_bodyparts.dm"
#include "code\modules\surgery\bodyparts\species_parts\misc_bodyparts.dm"
#include "code\modules\surgery\bodyparts\species_parts\moth_bodyparts.dm"