Files
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

279 lines
12 KiB
Plaintext

///Tail parent, it doesn't do very much.
/obj/item/organ/tail
name = "tail"
desc = "A severed tail. What did you cut this off of?"
icon_state = "severedtail"
zone = BODY_ZONE_PRECISE_GROIN
slot = ORGAN_SLOT_EXTERNAL_TAIL
//dna_block = /datum/dna_block/feature/accessory/tail // BUBBER EDIT REMOVAL - Customization - We have our own system to handle DNA.
restyle_flags = EXTERNAL_RESTYLE_FLESH
// defaults to cat, but the parent type shouldn't be created regardless
bodypart_overlay = /datum/bodypart_overlay/mutant/tail/cat
organ_flags = parent_type::organ_flags | ORGAN_EXTERNAL
///Does this tail have a wagging sprite, and is it currently wagging?
var/wag_flags = NONE
///The original owner of this tail
var/original_owner //Yay, snowflake code!
///The overlay for tail spines, if any
var/datum/bodypart_overlay/mutant/tail_spines/tail_spines_overlay
/obj/item/organ/tail/on_mob_insert(mob/living/carbon/receiver, special, movement_flags)
. = ..()
receiver.clear_mood_event("tail_lost")
receiver.clear_mood_event("tail_balance_lost")
if(!special) // if some admin wants to give someone tail moodles for tail shenanigans, they can spawn it and do it by hand
original_owner ||= WEAKREF(receiver)
// If it's your tail, an infinite debuff is replaced with a timed one
// If it's not your tail but of same species, I guess it works, but we are more sad
// If it's not your tail AND of different species, we are horrified
if(IS_WEAKREF_OF(receiver, original_owner))
receiver.add_mood_event("tail_regained", /datum/mood_event/tail_regained_right)
else if(type in receiver.dna.species.mutant_organs)
receiver.add_mood_event("tail_regained", /datum/mood_event/tail_regained_species)
else
receiver.add_mood_event("tail_regained", /datum/mood_event/tail_regained_wrong)
/obj/item/organ/tail/on_bodypart_insert(obj/item/bodypart/bodypart)
var/obj/item/organ/spines/our_spines = bodypart.owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_SPINES)
if(our_spines)
try_insert_tail_spines(bodypart)
return ..()
/obj/item/organ/tail/on_bodypart_remove(obj/item/bodypart/bodypart)
remove_tail_spines(bodypart)
return ..()
/// If the owner has spines and an appropriate overlay exists, add a tail spines overlay.
/obj/item/organ/tail/proc/try_insert_tail_spines(obj/item/bodypart/bodypart)
// Don't insert another overlay if there already is one.
if(tail_spines_overlay)
return
// If this tail doesn't have a valid set of tail spines, don't insert them
var/datum/sprite_accessory/tails/tail_sprite_datum = bodypart_overlay.sprite_datum
if(!istype(tail_sprite_datum))
return
var/tail_spine_key = tail_sprite_datum.spine_key
if(!tail_spine_key)
return
tail_spines_overlay = new
tail_spines_overlay.tail_spine_key = tail_spine_key
// SKYRAT EDIT ADDITION START
if(!bodypart.owner.dna.mutant_bodyparts[FEATURE_SPINES])
bodypart.owner.dna.mutant_bodyparts[FEATURE_SPINES] = list(MUTANT_INDEX_NAME = "None", MUTANT_INDEX_COLOR_LIST = list("#886600", "#886600", "#886600"))
bodypart.owner.dna.update_uf_block(/datum/dna_block/feature/mutant/spines)
// SKYRAT EDIT ADDITION END
var/feature_name = bodypart.owner.dna.mutant_bodyparts[FEATURE_SPINES][MUTANT_INDEX_NAME] // BUBBER EDIT CHANGE - ORIGINAL: var/feature_name = bodypart.owner.dna.features[FEATURE_SPINES] //tail spines don't live in DNA, but share feature names with regular spines
tail_spines_overlay.set_appearance_from_dna(bodypart.owner.dna, feature_name, feature_key = FEATURE_SPINES) // BUBBER EDIT CHANGE - ORIGINAL: tail_spines_overlay.set_appearance_from_name(feature_name)
bodypart.add_bodypart_overlay(tail_spines_overlay)
/// If we have a tail spines overlay, delete it
/obj/item/organ/tail/proc/remove_tail_spines(obj/item/bodypart/bodypart)
if(!tail_spines_overlay)
return
bodypart.remove_bodypart_overlay(tail_spines_overlay)
QDEL_NULL(tail_spines_overlay)
/obj/item/organ/tail/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
. = ..()
if(wag_flags & WAG_WAGGING)
stop_wag(organ_owner)
organ_owner.clear_mood_event("tail_regained")
if(type in organ_owner.dna?.species.mutant_organs)
organ_owner.add_mood_event("tail_lost", /datum/mood_event/tail_lost)
organ_owner.add_mood_event("tail_balance_lost", /datum/mood_event/tail_balance_lost)
///We need some special behaviour for accessories, wrapped here so we can easily add more interactions later
///Accepts an optional timeout after which we remove the tail wagging
///Returns false if the wag worked, true otherwise
/obj/item/organ/tail/proc/start_wag(mob/living/carbon/organ_owner, stop_after = INFINITY)
if(wag_flags & WAG_WAGGING || !(wag_flags & WAG_ABLE)) // we are already wagging
return FALSE
if(organ_owner.stat == DEAD || organ_owner != owner) // no wagging when owner is dead or tail has been disembodied
return FALSE
if(stop_after != INFINITY)
addtimer(CALLBACK(src, PROC_REF(stop_wag), organ_owner), stop_after, TIMER_STOPPABLE|TIMER_DELETE_ME)
var/datum/bodypart_overlay/mutant/tail/accessory = bodypart_overlay
wag_flags |= WAG_WAGGING
accessory.wagging = TRUE
if(tail_spines_overlay) //if there are spines, they should wag with the tail
tail_spines_overlay.wagging = TRUE
organ_owner.update_body_parts()
RegisterSignal(organ_owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died))
return TRUE
/obj/item/organ/tail/proc/owner_died(mob/living/carbon/organ_owner) // Resisting the urge to replace owner with daddy
SIGNAL_HANDLER
stop_wag(organ_owner)
///We need some special behaviour for accessories, wrapped here so we can easily add more interactions later
///Returns false if the wag stopping worked, true otherwise
/obj/item/organ/tail/proc/stop_wag(mob/living/carbon/organ_owner)
if(!(wag_flags & WAG_ABLE))
return FALSE
var/succeeded = FALSE
if(wag_flags & WAG_WAGGING)
wag_flags &= ~WAG_WAGGING
succeeded = TRUE
var/datum/bodypart_overlay/mutant/tail/tail_overlay = bodypart_overlay
tail_overlay.wagging = FALSE
if(tail_spines_overlay) //if there are spines, they should stop wagging with the tail
tail_spines_overlay.wagging = FALSE
if(isnull(organ_owner))
return succeeded
organ_owner.update_body_parts()
UnregisterSignal(organ_owner, COMSIG_LIVING_DEATH)
return succeeded
/obj/item/organ/tail/proc/get_butt_sprite()
return null
///Tail parent type, with wagging functionality
/datum/bodypart_overlay/mutant/tail
layers = EXTERNAL_FRONT|EXTERNAL_BEHIND
dyable = TRUE
feature_key = FEATURE_TAIL_GENERIC
var/wagging = FALSE
/datum/bodypart_overlay/mutant/tail/get_base_icon_state()
return "[wagging ? "wagging_" : ""][sprite_datum.icon_state]" //add the wagging tag if we be wagging
/datum/bodypart_overlay/mutant/tail/can_draw_on_bodypart(obj/item/bodypart/bodypart_owner, mob/living/carbon/owner, is_husked = FALSE)
return ..() && !(bodypart_owner.owner?.obscured_slots & HIDEJUMPSUIT)
/obj/item/organ/tail/cat
name = "tail"
bodypart_overlay = /datum/bodypart_overlay/mutant/tail/cat
restyle_flags = EXTERNAL_RESTYLE_FLESH
wag_flags = WAG_ABLE
/datum/bodypart_overlay/mutant/tail/get_global_feature_list()
return SSaccessories.sprite_accessories[FEATURE_TAIL_GENERIC] // BUBBER EDIT CHANGE - Customization - ORIGINAL: return SSaccessories.tails_list_human
/obj/item/organ/tail/cat/get_butt_sprite()
return icon('icons/mob/butts.dmi', BUTT_SPRITE_CAT)
///Cat tail bodypart overlay
/datum/bodypart_overlay/mutant/tail/cat
feature_key = FEATURE_TAIL_GENERIC // BUBBER EDIT CHANGE - Customization - ORIGINAL: feature_key = FEATURE_TAIL_CAT
// color_source = ORGAN_COLOR_HAIR // SKYRAT EDIT REMOVAL
/obj/item/organ/tail/monkey
name = "monkey tail"
icon_state = "severedmonkeytail"
dna_block = null
bodypart_overlay = /datum/bodypart_overlay/mutant/tail/monkey
///Monkey tail bodypart overlay
/datum/bodypart_overlay/mutant/tail/monkey
color_source = NONE
feature_key = FEATURE_TAIL_MONKEY
/obj/item/organ/tail/xeno
name = "alien tail"
desc = "A long and flexible tail slightly resembling a spine, used by its original owner as both weapon and balance aid."
icon_state = "severedxenotail"
dna_block = null
bodypart_overlay = /datum/bodypart_overlay/mutant/tail/xeno
organ_traits = list(TRAIT_TACKLING_TAILED_POUNCE, TRAIT_FREERUNNING)
/obj/item/organ/tail/xeno_queen
name = "alien queen's tail"
desc = "An enormous serrated tail, used to deadly effect by its original owner but perhaps too heavy for a human spine."
icon = 'icons/mob/human/species/alien/tail_xenomorph_queen.dmi'
icon_state = "severedqueentail"
w_class = WEIGHT_CLASS_BULKY
slowdown = 2
item_flags = SLOWS_WHILE_IN_HAND
dna_block = null
bodypart_overlay = /datum/bodypart_overlay/mutant/tail/xeno/queen
/// Our tail whip action
var/datum/action/cooldown/spell/aoe/repulse/xeno/tail_whip
/obj/item/organ/tail/xeno_queen/Initialize(mapload)
. = ..()
tail_whip = new(src)
/obj/item/organ/tail/xeno_queen/on_mob_insert(mob/living/carbon/receiver, special, movement_flags)
. = ..()
tail_whip.Grant(receiver)
receiver.add_movespeed_modifier(/datum/movespeed_modifier/tail_dragger)
/obj/item/organ/tail/xeno_queen/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
. = ..()
tail_whip.Remove(organ_owner)
organ_owner.remove_movespeed_modifier(/datum/movespeed_modifier/tail_dragger)
///Alien tail bodypart overlay
/datum/bodypart_overlay/mutant/tail/xeno
color_source = NONE
feature_key = FEATURE_TAIL_XENO
draw_on_husks = HUSK_OVERLAY_GRAYSCALE
imprint_on_next_insertion = FALSE
/// We don't want to bother writing this in DNA, just use this appearance
var/default_appearance = "Xeno"
/datum/bodypart_overlay/mutant/tail/xeno/New()
. = ..()
set_appearance_from_name(default_appearance)
/datum/bodypart_overlay/mutant/tail/xeno/randomize_appearance()
set_appearance_from_name(default_appearance)
/datum/bodypart_overlay/mutant/tail/xeno/queen
default_appearance = "Xeno Queen"
/obj/item/organ/tail/lizard
name = "lizard tail"
desc = "A severed lizard tail. Somewhere, no doubt, a lizard hater is very pleased with themselves."
bodypart_overlay = /datum/bodypart_overlay/mutant/tail/lizard
wag_flags = WAG_ABLE
//dna_block = /datum/dna_block/feature/accessory/tail_lizard // BUBBER EDIT REMOVAL - Customization - We have our own system to handle DNA.
///Lizard tail bodypart overlay datum
/datum/bodypart_overlay/mutant/tail/lizard
feature_key = FEATURE_TAIL_GENERIC // BUBBER EDIT CHANGE - Customization - ORIGINAL: feature_key = FEATURE_TAIL_LIZARD
draw_on_husks = HUSK_OVERLAY_GRAYSCALE
/obj/item/organ/tail/lizard/fake
name = "fabricated lizard tail"
desc = "A fabricated severed lizard tail. This one's made of synthflesh. Probably not usable for lizard wine."
///Bodypart overlay for tail spines. Handled by the tail - has no actual organ associated.
/datum/bodypart_overlay/mutant/tail_spines
layers = EXTERNAL_ADJACENT|EXTERNAL_BEHIND
feature_key = FEATURE_TAILSPINES
draw_on_husks = HUSK_OVERLAY_GRAYSCALE
///Spines wag when the tail does
var/wagging = FALSE
/// Key for tail spine states, depends on the shape of the tail. Defined in the tail sprite datum.
var/tail_spine_key = NONE
/datum/bodypart_overlay/mutant/tail_spines/get_base_icon_state()
return (!isnull(tail_spine_key) ? "[tail_spine_key]_" : "") + (wagging ? "wagging_" : "") + sprite_datum.icon_state // Select the wagging state if appropriate
/datum/bodypart_overlay/mutant/tail_spines/can_draw_on_bodypart(obj/item/bodypart/bodypart_owner, mob/living/carbon/owner, is_husked = FALSE)
return ..() && !(bodypart_owner.owner?.obscured_slots & HIDEJUMPSUIT)
/datum/bodypart_overlay/mutant/tail_spines/set_dye_color(new_color, obj/item/organ/organ)
dye_color = new_color //no update_body_parts() call, tail/set_dye_color will do it.