Skip to content

Commit

Permalink
Dullahan Partial Refactor: They Work Again Edition (#63696)
Browse files Browse the repository at this point in the history
So, a few months ago I was like "hmm there's something weird going on with party pods...", which got me looking into important_recursive_hearers or something like that. I spoke about it in the coding channel and Kyler actually fixed it before I did. But I also caught a similar glitch with Dullahans, so I decided to investigate...

Two months later...

I present to you a partial unfuckening of the Dullahans, in that I made them fully functional once again:

They only hear speech through their head (not sounds, sadly, someone else would have to tell me how to do that because I otherwise really wouldn't know how to do it in a sane way), they speak through their head, runechat-included.
When you spawn a Dullahan, you're set to look through the Dullahan's eyes (so from their head), and that doesn't reset when you log off and back in, or admin-ghost and come back in your body.
When you're looking through your head, your view will no longer be reset to your body upon entering a locker, which is nice to avoid not being blind while looking through your body.
Dullahan heads no longer look completely lifeless and without organs. They have eyes that don't look dead and that even match the player's intended eye color.
Dullahan can now properly examine things from their head, which was intended and 100% not functional.
Dullahan heads now speak with the proper name of their owner, instead of having a random name attached to it at round-start.
Dullahan heads are also now properly named too.
Dullahans can now properly whisper, sing and do all these funny things that they were unable to do before.
Dullahan whispers will now properly respect the range of the whisper.
Dullahans can now succumb in hardcrit by whispering, as intended. This potentially fixes other species that worked similarly not being able to succumb, like abductors, although I didn't test if they normally could, I just know they absolutely will be able to now.
When switching from Dullahans to a different species, your old head will no longer stay behind.
I also added a proc for species to do some code when we get a ckey login in our mob, which could potentially be useful for other stuff in the future, but it was necessary here as the view is reliant on the client, which we want to ensure doesn't get weird view glitches like having their head's vision overlay while actually being centered on their body.

I also made it so say() now takes a range argument, which is 7 by default, just so things that aren't humans can also whisper and do all those kinds of things. Going with that, there's probably a few more things that will be able to be done better thanks to this, although I haven't tested every edge case with this, but I doubt it will make much of a difference in the future.
  • Loading branch information
GoldenAlpharex authored Jan 6, 2022
1 parent 366ac21 commit 413fd90
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 86 deletions.
1 change: 1 addition & 0 deletions code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
#define SPEECH_LANGUAGE 5
/* #define SPEECH_IGNORE_SPAM 6
#define SPEECH_FORCED 7 */
#define SPEECH_RANGE 8

///from /mob/say_dead(): (mob/speaker, message)
#define COMSIG_MOB_DEADSAY "mob_deadsay"
Expand Down
8 changes: 4 additions & 4 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
#define MOB_PLANT (1 << 10)

//Organ defines for carbon mobs
#define ORGAN_ORGANIC 1
#define ORGAN_ROBOTIC 2
#define ORGAN_ORGANIC 1
#define ORGAN_ROBOTIC 2

#define BODYPART_ORGANIC 1
#define BODYPART_ROBOTIC 2
#define BODYPART_ORGANIC 1
#define BODYPART_ROBOTIC 2

#define DEFAULT_BODYPART_ICON_ORGANIC 'icons/mob/human_parts_greyscale.dmi'
#define DEFAULT_BODYPART_ICON_ROBOTIC 'icons/mob/augmentation/augments.dmi'
Expand Down
2 changes: 1 addition & 1 deletion code/_onclick/click.dm
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@
return

/atom/proc/ShiftClick(mob/user)
var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user)
var/flags = SEND_SIGNAL(user, COMSIG_CLICK_SHIFT, src)
if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
return
Expand Down
4 changes: 2 additions & 2 deletions code/game/say.dm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
"[FREQ_CTF_YELLOW]" = "yellowteamradio"
))

/atom/movable/proc/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
/atom/movable/proc/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, range = 7)
if(!can_speak())
return
if(sanitize)
Expand All @@ -30,7 +30,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
spans |= speech_span
if(!language)
language = get_selected_language()
send_speech(message, 7, src, , spans, message_language=language)
send_speech(message, range, src, , spans, message_language=language)

/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args)
Expand Down
5 changes: 3 additions & 2 deletions code/modules/mob/living/brain/brain_item.dm
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@

var/suicided = FALSE
var/mob/living/brain/brainmob = null
var/decoy_override = FALSE //if it's a fake brain with no brainmob assigned. Feedback messages will be faked as if it does have a brainmob. See changelings & dullahans.
//two variables necessary for calculating whether we get a brain trauma or not
/// If it's a fake brain with no brainmob assigned. Feedback messages will be faked as if it does have a brainmob. See changelings & dullahans.
var/decoy_override = FALSE
/// Two variables necessary for calculating whether we get a brain trauma or not
var/damage_delta = 0


Expand Down
7 changes: 7 additions & 0 deletions code/modules/mob/living/carbon/human/human.dm
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@
SIGNAL_HANDLER
spreadFire(AM)

/mob/living/carbon/human/reset_perspective(atom/new_eye, force_reset = FALSE)
if(dna?.species?.prevent_perspective_change && !force_reset) // This is in case a species needs to prevent perspective changes in certain cases, like Dullahans preventing perspective changes when they're looking through their head.
update_fullscreen()
return
return ..()


/mob/living/carbon/human/Topic(href, href_list)
if(href_list["item"]) //canUseTopic check for this is handled by mob/Topic()
var/slot = text2num(href_list["item"])
Expand Down
3 changes: 3 additions & 0 deletions code/modules/mob/living/carbon/human/login.dm
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/mob/living/carbon/human/Login()
. = ..()

dna?.species?.on_owner_login(src)

if(!LAZYLEN(afk_thefts))
return

Expand Down
19 changes: 18 additions & 1 deletion code/modules/mob/living/carbon/human/species.dm
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,12 @@ GLOBAL_LIST_EMPTY(features_by_species)
///List of visual overlays created by handle_body()
var/list/body_vis_overlays = list()

//Should we preload this species's organs?
/// Should we preload this species's organs?
var/preload = TRUE

/// Do we try to prevent reset_perspective() from working? Useful for Dullahans to stop perspective changes when they're looking through their head.
var/prevent_perspective_change = FALSE

///////////
// PROCS //
///////////
Expand Down Expand Up @@ -2165,3 +2168,17 @@ GLOBAL_LIST_EMPTY(features_by_species)
to_store += mutantappendix
//We don't cache mutant hands because it's not constrained enough, too high a potential for failure
return to_store


/**
* Owner login
*/

/**
* A simple proc to be overwritten if something needs to be done when a mob logs in. Does nothing by default.
*
* Arguments:
* * owner - The owner of our species.
*/
/datum/species/proc/on_owner_login(mob/living/carbon/human/owner)
return
171 changes: 108 additions & 63 deletions code/modules/mob/living/carbon/human/species_types/dullahan.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "Dullahan"
id = SPECIES_DULLAHAN
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS, HAS_FLESH, HAS_BONE)
species_traits = list(EYECOLOR, HAIR, FACEHAIR, LIPS, HAS_FLESH, HAS_BONE)
inherent_traits = list(
TRAIT_ADVANCEDTOOLUSER,
TRAIT_CAN_STRIP,
Expand All @@ -20,57 +20,96 @@
skinned_type = /obj/item/stack/sheet/animalhide/human
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | ERT_SPAWN

var/obj/item/dullahan_relay/myhead
/// The dullahan relay that's associated with the owner, used to handle many things such as talking and hearing.
var/obj/item/dullahan_relay/my_head

/// Did our owner's first client connection get handled yet? Useful for when some proc needs to be called once we're sure that a client has moved into our owner, like for Dullahans.
var/owner_first_client_connection_handled = FALSE


/datum/species/dullahan/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
return TRUE
return ..()

/datum/species/dullahan/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
/datum/species/dullahan/on_species_gain(mob/living/carbon/human/human, datum/species/old_species)
. = ..()
H.lose_hearing_sensitivity(TRAIT_GENERIC)
var/obj/item/bodypart/head/head = H.get_bodypart(BODY_ZONE_HEAD)
human.lose_hearing_sensitivity(TRAIT_GENERIC)
var/obj/item/bodypart/head/head = human.get_bodypart(BODY_ZONE_HEAD)

if(head)
head.no_update = TRUE
head.drop_limb()

if(!QDELETED(head)) //drop_limb() deletes the limb if no drop location exists and character setup dummies are located in nullspace.
head.throwforce = 25
myhead = new /obj/item/dullahan_relay (head, H)
H.put_in_hands(head)
var/obj/item/organ/eyes/E = H.getorganslot(ORGAN_SLOT_EYES)
var/datum/action/item_action/organ_action/dullahan/D = locate() in E?.actions
D?.Trigger()
H.set_safe_hunger_level()

/datum/species/dullahan/on_species_loss(mob/living/carbon/human/H)
H.become_hearing_sensitive()
H.reset_perspective(H)
if(myhead)
var/obj/item/dullahan_relay/DR = myhead
myhead = null
DR.owner = null
qdel(DR)
H.regenerate_limb(BODY_ZONE_HEAD,FALSE)
..()

/datum/species/dullahan/spec_life(mob/living/carbon/human/H, delta_time, times_fired)
if(QDELETED(myhead))
myhead = null
H.gib()
var/obj/item/bodypart/head/head2 = H.get_bodypart(BODY_ZONE_HEAD)
if(head2)
myhead = null
H.gib()

/datum/species/dullahan/proc/update_vision_perspective(mob/living/carbon/human/H)
var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
my_head = new /obj/item/dullahan_relay(head, human)
human.put_in_hands(head)
head.show_organs_on_examine = FALSE

// We want to give the head some boring old eyes just so it doesn't look too jank on the head sprite.
head.eyes = new /obj/item/organ/eyes(head)
head.eyes.eye_color = human.eye_color
head.update_icon_dropped()

human.set_safe_hunger_level()

/datum/species/dullahan/on_species_loss(mob/living/carbon/human/human)
. = ..()

if(my_head)
var/obj/item/bodypart/head/detached_head = my_head.loc
my_head.owner = null
QDEL_NULL(my_head)
if(detached_head)
qdel(detached_head)

human.regenerate_limb(BODY_ZONE_HEAD, FALSE)
human.become_hearing_sensitive()
prevent_perspective_change = FALSE
human.reset_perspective(human)

/datum/species/dullahan/spec_life(mob/living/carbon/human/human, delta_time, times_fired)
if(QDELETED(my_head))
my_head = null
human.gib()
return

if(my_head.loc.name != human.real_name && istype(my_head.loc, /obj/item/bodypart/head))
var/obj/item/bodypart/head/detached_head = my_head.loc
detached_head.real_name = human.real_name
detached_head.name = human.real_name
detached_head.brain.name = "[human.name]'s brain"

var/obj/item/bodypart/head/illegal_head = human.get_bodypart(BODY_ZONE_HEAD)
if(illegal_head)
my_head = null
human.gib() // Yeah so giving them a head on their body is really not a good idea, so their original head will remain but uh, good luck fixing it after that.

/datum/species/dullahan/proc/update_vision_perspective(mob/living/carbon/human/human)
var/obj/item/organ/eyes/eyes = human.getorganslot(ORGAN_SLOT_EYES)
if(eyes)
H.update_tint()
human.update_tint()
if(eyes.tint)
H.reset_perspective(H)
prevent_perspective_change = FALSE
human.reset_perspective(human, TRUE)
else
H.reset_perspective(myhead)
human.reset_perspective(my_head, TRUE)
prevent_perspective_change = TRUE

/datum/species/dullahan/on_owner_login(mob/living/carbon/human/owner)
var/obj/item/organ/eyes/eyes = owner.getorganslot(ORGAN_SLOT_EYES)
if(owner_first_client_connection_handled)
if(!eyes.tint)
owner.reset_perspective(my_head, TRUE)
prevent_perspective_change = TRUE
return

// As it's the first time there's a client in our mob, we can finally update its vision to place it in the head instead!
var/datum/action/item_action/organ_action/dullahan/eyes_toggle_perspective_action = locate() in eyes?.actions

eyes_toggle_perspective_action?.Trigger()
owner_first_client_connection_handled = TRUE

/obj/item/organ/brain/dullahan
decoy_override = TRUE
Expand All @@ -82,12 +121,12 @@

/obj/item/organ/tongue/dullahan/handle_speech(datum/source, list/speech_args)
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(isdullahan(H))
var/datum/species/dullahan/D = H.dna.species
if(isobj(D.myhead.loc))
var/obj/O = D.myhead.loc
O.say(speech_args[SPEECH_MESSAGE])
var/mob/living/carbon/human/human = owner
if(isdullahan(human))
var/datum/species/dullahan/dullahan_species = human.dna.species
if(isobj(dullahan_species.my_head.loc))
var/obj/head = dullahan_species.my_head.loc
head.say(speech_args[SPEECH_MESSAGE], spans = speech_args[SPEECH_SPANS], sanitize = FALSE, language = speech_args[SPEECH_LANGUAGE], range = speech_args[SPEECH_RANGE])
speech_args[SPEECH_MESSAGE] = ""

/obj/item/organ/ears/dullahan
Expand All @@ -106,20 +145,19 @@

/datum/action/item_action/organ_action/dullahan/Trigger()
. = ..()
var/obj/item/organ/eyes/dullahan/DE = target
if(DE.tint)
DE.tint = 0
else
DE.tint = INFINITY
var/obj/item/organ/eyes/dullahan/dullahan_eyes = target
dullahan_eyes.tint = dullahan_eyes.tint ? NONE : INFINITY

if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(isdullahan(H))
var/datum/species/dullahan/D = H.dna.species
D.update_vision_perspective(H)
var/mob/living/carbon/human/human = owner
if(isdullahan(human))
var/datum/species/dullahan/dullahan_species = human.dna.species
dullahan_species.update_vision_perspective(human)


/obj/item/dullahan_relay
name = "dullahan relay"
/// The mob (a dullahan) that owns this relay.
var/mob/living/owner

/obj/item/dullahan_relay/Initialize(mapload, mob/living/carbon/human/new_owner)
Expand All @@ -133,19 +171,26 @@
RegisterSignal(owner, COMSIG_LIVING_REVIVE, .proc/retrieve_head)
become_hearing_sensitive(ROUNDSTART_TRAIT)

/obj/item/dullahan_relay/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
owner.Hear(arglist(args))
/obj/item/dullahan_relay/Destroy()
lose_hearing_sensitivity(ROUNDSTART_TRAIT)
owner = null
return ..()

/obj/item/dullahan_relay/process()
if(!istype(loc, /obj/item/bodypart/head) || QDELETED(owner))
. = PROCESS_KILL
qdel(src)

/obj/item/dullahan_relay/proc/examinate_check(atom/source, mob/user)
/obj/item/dullahan_relay/proc/examinate_check(mob/user, atom/source)
SIGNAL_HANDLER
if(user.client.eye == src)
return COMPONENT_ALLOW_EXAMINATE

/obj/item/dullahan_relay/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(owner)
owner.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)

///Adds the owner to the list of hearers in hearers_in_view(), for visible/hearable on top of say messages
/obj/item/dullahan_relay/proc/include_owner(datum/source, list/hearers)
SIGNAL_HANDLER
Expand All @@ -161,17 +206,17 @@
/obj/item/dullahan_relay/proc/retrieve_head(datum/source, full_heal, admin_revive)
SIGNAL_HANDLER
if(admin_revive)
var/obj/item/bodypart/head/H = loc
var/turf/T = get_turf(owner)
if(H && istype(H) && T && !(H in owner.get_all_contents()))
H.forceMove(T)
var/obj/item/bodypart/head/head = loc
var/turf/body_turf = get_turf(owner)
if(head && istype(head) && body_turf && !(head in owner.get_all_contents()))
head.forceMove(body_turf)

/obj/item/dullahan_relay/Destroy()
if(!QDELETED(owner))
var/mob/living/carbon/human/H = owner
if(isdullahan(H))
var/datum/species/dullahan/D = H.dna.species
D.myhead = null
var/mob/living/carbon/human/human = owner
if(isdullahan(human))
var/datum/species/dullahan/dullahan_species = human.dna.species
dullahan_species.my_head = null
owner.gib()
owner = null
return ..()
14 changes: 9 additions & 5 deletions code/modules/mob/living/living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1530,13 +1530,17 @@
/mob/living/reset_perspective(atom/A)
if(..())
update_sight()
if(client.eye && client.eye != src)
var/atom/AT = client.eye
AT.get_remote_view_fullscreens(src)
else
clear_fullscreen("remote_view", 0)
update_fullscreen()
update_pipe_vision()

/// Proc used to handle the fullscreen overlay updates, realistically meant for the reset_perspective() proc.
/mob/living/proc/update_fullscreen()
if(client.eye && client.eye != src)
var/atom/client_eye = client.eye
client_eye.get_remote_view_fullscreens(src)
else
clear_fullscreen("remote_view", 0)

/mob/living/update_mouse_pointer()
..()
if (client && ranged_ability?.ranged_mousepointer)
Expand Down
Loading

0 comments on commit 413fd90

Please sign in to comment.