From 9e92c0aa3dc9131b9dd5f44e4618398ffac1d8ea Mon Sep 17 00:00:00 2001 From: Y0SH1M4S73R Date: Mon, 4 May 2026 16:37:11 -0400 Subject: [PATCH] Reverts "Merges ghosts into spirits & a rework", but removes ghosts from Pride's mirror. (#95779) --- code/__DEFINES/is_helpers.dm | 2 +- code/__DEFINES/mobs.dm | 1 + .../equipment/spellbook_entries/defensive.dm | 2 +- .../equipment/spellbook_entries/mobility.dm | 9 + .../equipment/spellbook_entries/offensive.dm | 2 +- .../lavalandruin_code/elephantgraveyard.dm | 8 - code/modules/mob/living/carbon/human/human.dm | 5 +- .../carbon/human/species_types/ghost.dm | 247 +++++++++++------- code/modules/photography/photos/photo.dm | 2 +- .../chemistry/reagents/other_reagents.dm | 4 +- .../spells/spell_types/self/ghostliness.dm | 57 ++++ .../species_parts/ghost_bodyparts.dm | 5 + ...nshot_humanoids__datum_species_spirit.png} | Bin ..._humanoids__datum_species_spirit_ghost.png | Bin 0 -> 2011 bytes icons/mob/human/species/ghost.dmi | Bin 2127 -> 2324 bytes tgstation.dme | 1 + 16 files changed, 237 insertions(+), 108 deletions(-) create mode 100644 code/modules/spells/spell_types/self/ghostliness.dm rename code/modules/unit_tests/screenshots/{screenshot_humanoids__datum_species_ghost.png => screenshot_humanoids__datum_species_spirit.png} (100%) create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit_ghost.png diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index b8c485eb776..28300319502 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -117,7 +117,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( //Human sub-species #define isabductor(A) (is_species(A, /datum/species/abductor)) -#define isghostspecies(A) (is_species(A, /datum/species/ghost)) +#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 64f26c02b0a..d51f29f4072 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -181,6 +181,7 @@ #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/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm index 609f626bb05..382e8963feb 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm @@ -55,7 +55,7 @@ it will become easier for others to find your item of power." spell_type = /datum/action/cooldown/spell/lichdom category = SPELLBOOK_CATEGORY_DEFENSIVE - no_coexistence_typecache = list(/datum/action/cooldown/spell/splattercasting, /datum/spellbook_entry/perks/wormborn) + no_coexistence_typecache = list(/datum/action/cooldown/spell/splattercasting, /datum/spellbook_entry/perks/wormborn, /datum/spellbook_entry/ghostliness) /datum/spellbook_entry/chuunibyou name = "Chuuni Invocations" diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm index bc09b092f6c..eb0c841c105 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm @@ -59,4 +59,13 @@ cost = 2 // Puts it at 3 cost if you go for safety instant summons, but teleporting anywhere on screen is pretty good. category = SPELLBOOK_CATEGORY_MOBILITY +/datum/spellbook_entry/ghostliness + name = "Forsake Body" + desc = "A necromantic spell which permanently severs your soul from your body, and partially anchors it to the material plane. \ + In this state, you can enter a state of incorporeality, allowing you to pass through solid matter. This, however, includes \ + most such matter on or inside of you." + spell_type = /datum/action/cooldown/spell/ghostliness + category = SPELLBOOK_CATEGORY_MOBILITY + no_coexistence_typecache = list(/datum/spellbook_entry/lichdom, /datum/spellbook_entry/splattercasting) + #undef SPELLBOOK_CATEGORY_MOBILITY diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm index 48653ea7ced..8cd1c5074e7 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm @@ -99,7 +99,7 @@ draining from you over time. You can replenish it from your victims, specifically their necks." spell_type = /datum/action/cooldown/spell/splattercasting category = SPELLBOOK_CATEGORY_OFFENSIVE - no_coexistence_typecache = list(/datum/action/cooldown/spell/lichdom) + no_coexistence_typecache = list(/datum/action/cooldown/spell/lichdom, /datum/spellbook_entry/ghostliness) /datum/spellbook_entry/sanguine_strike name = "Exsanguinating Strike" diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm index 0674ff97cb8..ffcd8c07a49 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm @@ -325,14 +325,6 @@ new /obj/effect/decal/cleanable/blood/gibs/old(src) new /obj/item/book/granter/crafting_recipe/boneyard_notes(src) -/obj/structure/closet/crate/grave/skeleton - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - affect_mood = TRUE - -/obj/structure/closet/crate/grave/skeleton/PopulateContents() - . = ..() - new /mob/living/carbon/human/species/skeleton(src) - //***Fluff items for lore/intrigue /obj/item/paper/crumpled/muddy/fluff/elephant_graveyard name = "posted warning" diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 9d78211d91f..0a3ab890fd8 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1108,8 +1108,11 @@ /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/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/species_types/ghost.dm b/code/modules/mob/living/carbon/human/species_types/ghost.dm index 0a53c7b41ba..d1da78e3870 100644 --- a/code/modules/mob/living/carbon/human/species_types/ghost.dm +++ b/code/modules/mob/living/carbon/human/species_types/ghost.dm @@ -1,23 +1,22 @@ ///Spirit mob that lacks legs but still roams the station as part of the unliving. -/datum/species/ghost - name = "Ghost" - id = SPECIES_GHOST +/datum/species/spirit + name = "Spirit" + id = SPECIES_SPIRIT inherent_traits = list( TRAIT_GENELESS, - TRAIT_MOVE_FLYING, TRAIT_NEVER_WOUNDED, TRAIT_NOBLOOD, TRAIT_NODISMEMBER, TRAIT_NO_DNA_COPY, - TRAIT_NO_FLOATING_ANIM, TRAIT_NO_PLASMA_TRANSFORM, TRAIT_NO_UNDERWEAR, - TRAIT_RESISTLOWPRESSURE, TRAIT_UNHUSKABLE, + TRAIT_NO_FLOATING_ANIM, + TRAIT_MOVE_FLYING, ) - inherent_biotypes = MOB_HUMANOID | MOB_SPIRIT | MOB_UNDEAD + inherent_biotypes = MOB_SPIRIT | MOB_UNDEAD no_equip_flags = ITEM_SLOT_FEET - changesource_flags = MIRROR_BADMIN | WABBAJACK | SLIME_EXTRACT | MIRROR_PRIDE | MIRROR_MAGIC + changesource_flags = MIRROR_BADMIN | MIRROR_PRIDE | MIRROR_MAGIC | WABBAJACK | SLIME_EXTRACT sexes = FALSE meat = /obj/item/ectoplasm @@ -33,45 +32,34 @@ bodypart_overrides = list( BODY_ZONE_HEAD = /obj/item/bodypart/head/ghost, - BODY_ZONE_CHEST = /obj/item/bodypart/chest/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, ) - ///Innate passthrough ability given to ghosts that allows them to phase but drops their stuff. - var/datum/action/innate/toggle_passthrough/passthrough_ability + ///Boolean on whether this species type is available at roundstart during halloween, used to deny subtypes. + var/halloween_exclusive = TRUE -/datum/species/ghost/check_roundstart_eligible() - if(check_holidays(HALLOWEEN)) +/datum/species/spirit/check_roundstart_eligible() + if(check_holidays(HALLOWEEN) && halloween_exclusive) return TRUE return ..() -/datum/species/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) +/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/ghost/on_species_loss(mob/living/carbon/human/former_ghost, datum/species/new_species, pref_load) - QDEL_NULL(passthrough_ability) - return ..() - -/datum/species/ghost/get_physical_attributes() - return "Ghosts are the spiritual remains of long-passed entities. They lack legs, can fly, and phase through walls, \ - but still eat, breathe, hear and see." - -/datum/species/ghost/get_species_description() +/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/ghost/get_species_lore() +/datum/species/spirit/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.", + "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/ghost/create_pref_unique_perks() +/datum/species/spirit/create_pref_unique_perks() var/list/to_add = list() to_add += list(list( @@ -81,14 +69,6 @@ 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_NEUTRAL_PERK, - SPECIES_PERK_ICON = "ghost", - SPECIES_PERK_NAME = "Incorporeal", - SPECIES_PERK_DESC = "Ghost carry their tombstones with them and are directly tied to it. \ - dropping the tombstone will allow you to phase through solid matter, but leaves you vulnerable.", - )) - to_add += list(list( SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK, SPECIES_PERK_ICON = "shoe-prints", @@ -98,35 +78,102 @@ 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 | WABBAJACK + 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 unable to use anything, - * and they will be tied to a tombstone that, if dug up, will kill them and turn them - * into a skeleton. + * 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 phasing through everything, including your hands. You are tied to your tombstone while this is active. \ - At least you know how to keep your clothes on." + 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" - ///Grave that appears when we're passing through, which we are also tied to. - var/obj/structure/closet/crate/grave/skeleton/grave - /datum/action/innate/toggle_passthrough/Grant(mob/grant_to) . = ..() - grave = new() - //contents are initialized when the grave is robbed as that's when the crate is opened for the first time. - RegisterSignal(grave, COMSIG_CLOSET_CONTENTS_INITIALIZED, PROC_REF(on_grave_robbed)) - RegisterSignal(grave, COMSIG_CLOSET_POST_OPEN, PROC_REF(post_grave_robbed)) + RegisterSignal(grant_to, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_new_limb)) /datum/action/innate/toggle_passthrough/Remove(mob/remove_from) - if(!QDELING(remove_from)) - swap_mode(force_off = TRUE) - QDEL_NULL(grave) + swap_mode(force_off = TRUE) + UnregisterSignal(remove_from, COMSIG_CARBON_POST_ATTACH_LIMB) return ..() /datum/action/innate/toggle_passthrough/Activate() @@ -135,61 +182,75 @@ 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 - if(HAS_TRAIT_FROM(owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT)) - grave.forceMove(get_turf(owner)) - owner.AddComponent(/datum/component/leash, grave, distance = 7) - REMOVE_TRAIT(owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT) - owner.add_traits(list( + 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) - RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_owner_died)) + ), 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 - qdel(owner.GetComponent(/datum/component/leash)) - owner.forceMove(get_turf(grave)) - grave.moveToNullspace() - ADD_TRAIT(owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT) - owner.remove_traits(list( + 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) - UnregisterSignal(owner, COMSIG_LIVING_DEATH) + ), 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) -/datum/action/innate/toggle_passthrough/proc/on_owner_died(mob/living/owner) +///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 - swap_mode(force_off = TRUE) - -///Called when the contents are made, which means the grave has been 'opened', therefore robbed. -/datum/action/innate/toggle_passthrough/proc/on_grave_robbed(obj/structure/closet/crate/grave/skeleton/source) - SIGNAL_HANDLER - var/mob/living/carbon/human/species/skeleton/skeletons_in_the_closet = locate() in source.contents - skeletons_in_the_closet.real_name = owner.real_name - skeletons_in_the_closet.name = owner.name - - if (owner.mind?.active) - owner.mind?.transfer_to(skeletons_in_the_closet, force_key_move = TRUE) - skeletons_in_the_closet.death(gibbed = TRUE) // This is a lie, but it stops the skeleton from deathgasping as if it only just died - - if (isliving(owner)) // You know just in case we give an observer the crazy ability to walk through walls - var/mob/living/living_owner = owner - living_owner.forceMove(source) - living_owner.unequip_everything() - -///Called AFTER the contents have been spit out, which means the owner is now in the skeleton. Let's clean up. -/datum/action/innate/toggle_passthrough/proc/post_grave_robbed(obj/structure/closet/crate/grave/skeleton/source) - SIGNAL_HANDLER - qdel(owner) + 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/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm index b68c2f9c400..1d98d728b29 100644 --- a/code/modules/photography/photos/photo.dm +++ b/code/modules/photography/photos/photo.dm @@ -55,7 +55,7 @@ if(!seen) P.mobs_seen -= seen_ref continue - if(!isobserver(seen) && !isghostspecies(seen)) + if(!isobserver(seen) && !isspirit(seen)) continue set_custom_materials(list(/datum/material/hauntium =SHEET_MATERIAL_AMOUNT)) break diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 78f39dc9ee5..f8d821b0b84 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -859,10 +859,10 @@ randomized_spawns = REAGENT_SPAWN_ALL_RANDOM_SPAWNS /datum/reagent/mutationtoxin/ghost - name = "Ghost Mutation Toxin" + name = "Spirit Mutation Toxin" description = "A spiritual toxin." color = "#5EFF3B" //RGB: 94, 255, 59 - race = /datum/species/ghost + race = /datum/species/spirit taste_description = "ectoplasm" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE randomized_spawns = REAGENT_SPAWN_ALL_RANDOM_SPAWNS diff --git a/code/modules/spells/spell_types/self/ghostliness.dm b/code/modules/spells/spell_types/self/ghostliness.dm new file mode 100644 index 00000000000..878114fa23f --- /dev/null +++ b/code/modules/spells/spell_types/self/ghostliness.dm @@ -0,0 +1,57 @@ +/datum/action/cooldown/spell/ghostliness + name = "Forsake Body" + desc = "A spell that severs your soul from your body, loosely binding it to the material plane." + button_icon = 'icons/mob/simple/mob.dmi' + button_icon_state = "ghost" + + school = SCHOOL_NECROMANCY + cooldown_time = 1 SECONDS + + invocation = "GHO'AN GHO'AST!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_STATION|SPELL_REQUIRES_MIND + spell_max_level = 1 + +/datum/action/cooldown/spell/ghostliness/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + + if(!is_valid_target(owner)) + if(feedback) + owner.balloon_alert(owner, "no soul!") + return FALSE + + return TRUE + +/datum/action/cooldown/spell/ghostliness/is_valid_target(atom/cast_on) + return ishuman(cast_on) && !HAS_TRAIT(owner, TRAIT_NO_SOUL) + +/datum/action/cooldown/spell/ghostliness/cast(mob/living/carbon/human/cast_on) + . = ..() + + if(isspirit(cast_on)) + to_chat(cast_on, span_green("You begin to focus on loosening the bonds holding you to the material plane.")) + else + to_chat(cast_on, span_green("You begin to focus on your very being, drawing it out of its corporeal vessel...")) + if(!do_after(cast_on, 5 SECONDS)) + if(isspirit(cast_on)) + to_chat(cast_on, span_warning("Your focus is broken, and you feel your material bindings snap tight once more.")) + else + to_chat(cast_on, span_warning("Your focus is broken, and your soul snaps back into place.")) + return + if(isspirit(cast_on)) + to_chat(cast_on, span_green("You successfully loosen your bonds to the material plane, and can now slip partially out of it.")) + else + to_chat(cast_on, span_danger("As the last trailing filament of your essence ceases intersection with your body, \ + your perspective abruptly snaps to your new, ghostly figure! Your former vessel falls to the ground, vacant and devoid of volition!")) + var/mob/living/carbon/human/soulless_husk = new(cast_on.drop_location()) + soulless_husk.setDir(cast_on.dir) + cast_on.dna.copy_dna(soulless_husk.dna, ALL) + soulless_husk.real_name = cast_on.real_name + soulless_husk.updateappearance(icon_update = TRUE, mutcolor_update = TRUE, mutations_overlay_update = TRUE) + soulless_husk.domutcheck() + ADD_TRAIT(soulless_husk, TRAIT_NO_SOUL, MAGIC_TRAIT) + ADD_TRAIT(soulless_husk, TRAIT_FLOORED, MAGIC_TRAIT) + cast_on.set_species(/datum/species/spirit/ghost) + qdel(src) diff --git a/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm index f3247ab84dc..8fd9913f6d3 100644 --- a/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ghost_bodyparts.dm @@ -27,6 +27,11 @@ wing_types = null butcher_replacement = 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' diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_ghost.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit.png similarity index 100% rename from code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_ghost.png rename to code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit.png 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 0000000000000000000000000000000000000000..c43c67f12a8b01a144cee7919749a370690ce131 GIT binary patch literal 2011 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 literal 2127 zcmb_eSx}Q%8vO$pkY*hsAU2%_MwXsw5XG=cK%#*G!=h|zfC#AUA`nCp01VUs9m_U+_GxIPr-PKR?aKEq4t$S~sI;T#3H^&Qw zR8`hf1^__S%@yGz>onQAs|1$Sqo$!~St~hzBp?wHo)CIA;!I*hTr2>bBbIcxDARXA zvc_#+(3RO<(&O=ye5Fvy2o+SRE0={!3bLB%9Dg!7SeMBd1|*x`ea&ZUIh+FU); zWpLnwbQWmBTIr?w#QH3(aW}ScErG#krHlcAI=o{8PPyh!Z<@Q_D)v~i)44h$l~+EH zQ?cj4v=)EQ96Sv|tAd`j1{gU2tsZJZO=uP`ub!G2y>R+HeGa~`FL{v@Am+S-yW(%x zt3Me@TN>p$;4jUE_g}n1mAE`hD_4!M!$?HIh>AeCa2;AD`FiN?FWn}F6D`z_?qMFn zdH6goALdZs5*{v4w2VJNAzA!GUsKy{ zd-$4^gs8(6GCh~Y*|qRA)!zQCMcCF&!3vgcbd9IbluWPS5*VV%0=bgmfEas>=WWC3 zOa_`7i(D+CZeA(;tLY~bMB63mNlaN-);+Oi5k(jgYSjg${tSxt^dM7Qt?J$d? zB>Muagp=)D)QCAClHYZI#Wxd6=LqbH^j4Z#J?0HKPEHlwTi1GpBN(0|(#coWC0QCC zgZk->?rL^c(tQldnMF}Ri*r=EclLJs0;Xq4vm-0--948)oUsl2)DGr$fG@U~n#_-G z;)3xfQd+XA99sx#zS}fEs*DoNf|@k14kkoFBYGY%MjM-8l2py|=?t9@w?5!CS-45T zV^Glb!X`tXm>`be)v3uA>b`T*<2rZ?FJ^P(o;Rrk1H)i>W@l~^*vXfAqp)y-6W9}# zr#8#ES>*`h!rRC=P7b3N$gNsb8-*TBAvdhtfGsyTbxA7Yjfu}*8zoXex!td*@^hT( zQB(k$(x-JRsAD5p_RJva%=T7)&xSlK|KuS2K6}&FX1{Nf0r{6HE=yZwkgCNQU6Onj zH+v0!a^Sa%8-}?$2U9GG>ypF8J*2S+!i8XIA6b7(r0qcIc$Ti_8?61bNo8F7BesTg zkm^&jrD}>|NL(+C6k&QpyM~Q|RFc2?+u9MHy(&gxo^rCk;DkEXSsIz%ph)&xoq0wH>!^ausuKS08?B4eK;epD22v2sP;fw5vx zRa9{KdV`~1=-W*o=C)^r?j5@~G_SzbBT9fLTC>VTyqv&r|KLb2{abe1# zK!muq_kmK_q6m!~KMMtu2Eg44q0blh#x1su6|tTE%{PSDS{__iSTI#fwfh%&?PUjG z!K*SmYoL?+kO;lkADvB*EXUS>I!#f#1!_Ha-k>LnLfF!4zajy84i}W5_@q_kkLtl)BOFdL@$xYNyOW%3KGOc6U za)BU@N)Jzx{9{v7Pubi#f2VTOrqMdpaszm1qwP_4gqJwMnMX8S|0`>`aQVduC}5gG z*vUK!@TM)K3gWm~y&rpqEDnFagQ6JG9w_M(L9?APdXsXmtysXWd>MZ^;8!KU!|4A2 zVum&WllQ@BF$-5)bz_2)Qo9xss7(cX`F75Bh2^QI+pOQx2&tM<1WLaNCzn2DYHX$w zxrEF>R1B#tM7l3%+0}l@&1l2Ix}f% zLSbbccy`SP(Vj{uYc?=KUt$b$@&n}LdY%6n`@F9L3h!;#p;>j-{5%Qq$wjzI=_PT^ z=t6z6>zr0pKX0gUIL_Opc_aqIMscx&npD4kK-)m}oc+y|)3HX1a^#}g`K7mR< z({fmP27aiZ012@rW$|Z?FQ66((llj8FH=chXA-8Kp|@Phh+W+5m5MI diff --git a/tgstation.dme b/tgstation.dme index 9d6ca244378..38fd2c07cab 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6451,6 +6451,7 @@ #include "code\modules\spells\spell_types\self\charge.dm" #include "code\modules\spells\spell_types\self\disable_tech.dm" #include "code\modules\spells\spell_types\self\forcewall.dm" +#include "code\modules\spells\spell_types\self\ghostliness.dm" #include "code\modules\spells\spell_types\self\lichdom.dm" #include "code\modules\spells\spell_types\self\mime_vow.dm" #include "code\modules\spells\spell_types\self\mutate.dm"