Merges ghosts into spirits & a rework (#92879)

## About The Pull Request

Having 2 different species with similar names, looks, attributes etc.
seemed like it would be confusing so I thought we should probably trim
it down to 1, however since I'm doing that I thought of other changes to
go along with it:

- The passthrough ability was replaced, now while you're passing through
things you no longer drop stuff, but you put a tombstone where you start
phasing, and are pulled back to it while done. You still can't interact
with anything in this form. Having the tombstone dug out will be raiding
your grave, so you'll be pulled out as a dead skeleton.

- Removes holy water interactions

- Ghosts now have organic limbs & are a Humanoid, so they can have
surgery and other forms of healing used on them.


https://github.com/user-attachments/assets/5db0c204-0070-466b-8b6c-1702ff0fd94e

## Why It's Good For The Game

Ghosts being available to Wizards is okayish but is a rather big buff
for one you get free, yes you can still be caught up to if you're in
hallways and such but it's way easier to get away with than like, your
default teleport scroll even. Being available to Miners though was a big
mistake because it opened them to be quite an annoyance with it. The
only counter was the fact no healing worked on them, which I don't think
is how it should be balanced.
I thought I could trust players with such tools but apparently I had too
high hopes.

Anyway this makes it more bearable for it to be given out at the low
cost it currently is at, it's still good as flavor, to spook people or
ghost around, and I think while it doesn't make 100% sense why you lose
access to your hands, inventory, and UI when you supposedly "drop" your
tombstone, I think it's still a good advantage for "scouting", sorta
like a lesser spirit realm rune.

Closes https://github.com/tgstation/tgstation/issues/91849

## Changelog

🆑
del: Merged ghosts and spirits into one species.
balance: Spirit's passthrough ability is now tied down 7 tiles from a
tombstone, but is no longer affected by holy water nor does it make them
drop their equipped gear.
balance: Ghosts can now be healed.
/🆑
This commit is contained in:
John Willard
2025-09-12 16:55:47 +02:00
committed by GitHub
parent 372a826571
commit 3cb0b239b7
10 changed files with 88 additions and 164 deletions
+1 -1
View File
@@ -115,7 +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 isghostspecies(A) (is_species(A, /datum/species/ghost))
#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))
-1
View File
@@ -177,7 +177,6 @@
#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"
@@ -333,6 +333,14 @@
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"
@@ -1115,11 +1115,8 @@
/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
race = /datum/species/ghost
/mob/living/carbon/human/species/ethereal
race = /datum/species/ethereal
@@ -1,22 +1,23 @@
///Spirit mob that lacks legs but still roams the station as part of the unliving.
/datum/species/spirit
name = "Spirit"
id = SPECIES_SPIRIT
/datum/species/ghost
name = "Ghost"
id = SPECIES_GHOST
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_SPIRIT | MOB_UNDEAD
inherent_biotypes = MOB_HUMANOID | MOB_SPIRIT | MOB_UNDEAD
no_equip_flags = ITEM_SLOT_FEET
changesource_flags = MIRROR_BADMIN | WABBAJACK | SLIME_EXTRACT
changesource_flags = MIRROR_BADMIN | WABBAJACK | SLIME_EXTRACT | MIRROR_PRIDE | MIRROR_MAGIC
sexes = FALSE
meat = /obj/item/ectoplasm
@@ -32,34 +33,45 @@
bodypart_overrides = list(
BODY_ZONE_HEAD = /obj/item/bodypart/head/ghost,
BODY_ZONE_CHEST = /obj/item/bodypart/chest/ghost/spirit,
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,
)
///Boolean on whether this species type is available at roundstart during halloween, used to deny subtypes.
var/halloween_exclusive = TRUE
///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/check_roundstart_eligible()
if(check_holidays(HALLOWEEN) && halloween_exclusive)
/datum/species/ghost/check_roundstart_eligible()
if(check_holidays(HALLOWEEN))
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/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_species_description()
/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()
return "Spirits are spirits of long-dead creatures whom, for one reason or another, still roam around."
/datum/species/spirit/get_species_lore()
/datum/species/ghost/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.",
"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/create_pref_unique_perks()
/datum/species/ghost/create_pref_unique_perks()
var/list/to_add = list()
to_add += list(list(
@@ -69,6 +81,14 @@
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",
@@ -78,102 +98,35 @@
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.
* 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.
*/
/datum/action/innate/toggle_passthrough
name = "Toggle passthrough"
desc = "Toggles your ability to phase through everything, including your gear and any incompatible organs/limbs."
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."
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)
. = ..()
RegisterSignal(grant_to, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_new_limb))
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))
/datum/action/innate/toggle_passthrough/Remove(mob/remove_from)
swap_mode(force_off = TRUE)
UnregisterSignal(remove_from, COMSIG_CARBON_POST_ATTACH_LIMB)
if(!QDELING(remove_from))
swap_mode(force_off = TRUE)
QDEL_NULL(grave)
return ..()
/datum/action/innate/toggle_passthrough/Activate()
@@ -182,30 +135,6 @@
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.
@@ -213,17 +142,9 @@
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))
grave.forceMove(get_turf(carbon_owner))
carbon_owner.AddComponent(/datum/component/leash, grave, distance = 7)
REMOVE_TRAIT(carbon_owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT)
carbon_owner.add_traits(list(
TRAIT_MOVE_PHASING,
@@ -232,10 +153,11 @@
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))
), SPECIES_TRAIT)
else
qdel(carbon_owner.GetComponent(/datum/component/leash))
carbon_owner.forceMove(get_turf(grave))
grave.moveToNullspace()
ADD_TRAIT(carbon_owner, TRAIT_NO_FLOATING_ANIM, SPECIES_TRAIT)
carbon_owner.remove_traits(list(
TRAIT_MOVE_PHASING,
@@ -244,13 +166,16 @@
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)
), SPECIES_TRAIT)
///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)
///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
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
var/mob/living/carbon/human/species/skeleton/skeletons_in_the_closet = locate() in source.contents
owner.mind.transfer_to(skeletons_in_the_closet, force_key_move = TRUE)
skeletons_in_the_closet.death(gibbed = FALSE)
///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)
+1 -1
View File
@@ -53,7 +53,7 @@
if(!seen)
P.mobs_seen -= seen_ref
continue
if(!isobserver(seen) && !isspirit(seen))
if(!isobserver(seen) && !isghostspecies(seen))
continue
set_custom_materials(list(/datum/material/hauntium =SHEET_MATERIAL_AMOUNT))
grind_results = list(/datum/reagent/hauntium = 20)
@@ -3,7 +3,7 @@
icon_static = 'icons/mob/human/species/ghost.dmi'
icon_state = "ghost_head"
biological_state = BIO_FLESH
bodytype = BODYTYPE_GHOST
bodytype = BODYTYPE_ORGANIC|BODYTYPE_GHOST
limb_id = SPECIES_GHOST
is_dimorphic = FALSE
should_draw_greyscale = FALSE
@@ -18,24 +18,19 @@
icon_state = "ghost_chest"
biological_state = BIO_FLESH
acceptable_bodyshape = BODYTYPE_GHOST
bodytype = BODYTYPE_GHOST
bodytype = BODYTYPE_ORGANIC|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
bodytype = BODYTYPE_ORGANIC|BODYTYPE_GHOST
limb_id = SPECIES_GHOST
should_draw_greyscale = FALSE
dmg_overlay_type = null
@@ -45,7 +40,7 @@
icon_static = 'icons/mob/human/species/ghost.dmi'
icon_state = "ghost_r_arm"
biological_state = BIO_FLESH|BIO_JOINTED
bodytype = BODYTYPE_GHOST
bodytype = BODYTYPE_ORGANIC|BODYTYPE_GHOST
limb_id = SPECIES_GHOST
should_draw_greyscale = FALSE
dmg_overlay_type = null
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB