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"