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 00000000000..c43c67f12a8 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_spirit_ghost.png differ diff --git a/icons/mob/human/species/ghost.dmi b/icons/mob/human/species/ghost.dmi index f22d3fc386c..42185e3db54 100644 Binary files a/icons/mob/human/species/ghost.dmi and b/icons/mob/human/species/ghost.dmi differ 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"