From 00e7d5d7465211ccf0e3d604e566e2c08775cd20 Mon Sep 17 00:00:00 2001 From: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Date: Sat, 17 Dec 2022 23:30:14 -0500 Subject: [PATCH] *hand, or That /One/ Emote You Always Felt Was Missing (#71600) ## About The Pull Request It's happened to me _repeatedly_ that I'd see someone down on the floor, and wanted to just, give them a hand, so they could take it and get up that way, without just, directly clicking on them, since that's a little bland. I've also wanted to just, offer my hand to someone so they could grab it, so that I could pull them alongside me, rather than just targeting one of their arms and ctrl-clicking them. I've had this idea for a _long_ time, and only just decided to do this today. Now, I know what you might say. "Golden, that's a lot of code for something this simple!" You're not wrong. _However_. I decided to go along and to give some more love to the `/datum/status_effect/offering` status effect and the offering-related alerts, to make them a lot more versatile and a lot less hardcoded. Hence the whole "refactoring" part of this. Of course, when I add something, I don't do it half-way. So, the way the emote works is much like the `*slap` emote, except that: - When you click on someone, it does the exact same as if you were offering the item to them, except that it's targeted (much like ctrl-shift-click). - If there's nobody directly adjacent to you, it won't do anything. - If there's at least one person lying down around you, you will offer them your help to get up. Should they take your hand and let you help them up, you will both receive a simple memory about being helped up (or helping up), as well as a 45-seconds-long small mood buff, because it feels nice to be on either end of such a friendly gesture. If they get up, they automatically get disqualified from being offered some help standing up, and likewise, if you lie down, that offer goes away as well. - If there's at least one person around you, you will instead extend your hand in their direction, for them to grab onto it. Should they do so, you will then grab them by their arms and pull them. I reworked the offering status effect to no longer have a hardcoded `can_hold_items()` check, so that kisses and the hand offering would no longer need you to have free hands to complete. The logic here is that you can still pull someone even with both hands filled, so I figured I'd leave it this way. Note: If anyone would like to give the item a better sprite, by all means, go ahead, that'd be amazing. I'm just not really a great spriter and couldn't be bothered to waste hours making a very _meh_ hand. ## Why It's Good For The Game It's fluff, and nice fluff at that. It makes it easier for people to be nice to one-another without having to necessarily spend so long writing up an emote that the person on the floor will already have gotten back up. I'm sure the MRP folks will like it, and I'm certain the HRP downstreams will love it too ;) ## Changelog :cl: add: Added the *hand emote, which you can offer to someone standing up in order to give them the possibility to grab onto your hand and let you drag them away, or to someone lying down to help them back up, which always makes everyone involved a little happier! refactor: De-hardcoded and genericized a lot of the offering status effect and alert code, to make it require a lot less copy-paste to handle new cases. fix: Offering a kiss no longer requires the receiver to have free hands to accept said kiss! /:cl: --- code/__DEFINES/memory_defines.dm | 2 + code/_onclick/hud/alert.dm | 52 ++++++++- .../mood_events/generic_positive_events.dm | 14 +++ code/datums/status_effects/neutral.dm | 86 +++++++++++++- code/game/objects/items.dm | 5 +- code/game/objects/items/hand_items.dm | 109 +++++++++++++++++- code/modules/mob/living/carbon/emote.dm | 20 ++++ strings/memories.json | 7 ++ 8 files changed, 283 insertions(+), 12 deletions(-) diff --git a/code/__DEFINES/memory_defines.dm b/code/__DEFINES/memory_defines.dm index 30de201dc58..77c6f151ef7 100644 --- a/code/__DEFINES/memory_defines.dm +++ b/code/__DEFINES/memory_defines.dm @@ -60,6 +60,8 @@ #define MEMORY_BOMB_PRIMED "bomb" /// A memory of pulling off either a high five or a high ten #define MEMORY_HIGH_FIVE "highfive" +/// A memory of being elegantly helped up! +#define MEMORY_HELPED_UP "helped_up" /// A memory of getting borged #define MEMORY_BORGED "borged" /// A memory of dying! includes time of death diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index c441690962f..9d0cbc60b08 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -302,6 +302,8 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." icon_state = "default" var/mob/living/carbon/offerer var/obj/item/receiving + /// Additional text displayed in the description of the alert. + var/additional_desc_text = "Click this alert to take it." /atom/movable/screen/alert/give/Destroy() offerer = null @@ -312,21 +314,42 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." * Handles assigning most of the variables for the alert that pops up when an item is offered * * Handles setting the name, description and icon of the alert and tracking the person giving - * and the item being offered, also registers a signal that removes the alert from anyone who moves away from the offerer + * and the item being offered. * Arguments: * * taker - The person receiving the alert * * offerer - The person giving the alert and item * * receiving - The item being given by the offerer */ /atom/movable/screen/alert/give/proc/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving) - name = "[offerer] is offering [receiving]" - desc = "[offerer] is offering [receiving]. Click this alert to take it." + var/receiving_name = get_receiving_name(taker, offerer, receiving) + name = "[offerer] is offering [receiving_name]" + desc = "[offerer] is offering [receiving_name]. [additional_desc_text]" icon_state = "template" cut_overlays() add_overlay(receiving) src.receiving = receiving src.offerer = offerer + +/** + * Called right before `setup()`, to do any sort of logic to change the name of + * what's displayed as the name of what's being offered in the alert. Use this to + * add pronouns and the like, or to totally override the displayed name! + * Also the best place to make changes to `additional_desc_text` before `setup()` + * without having to override `setup()` entirely. + * + * Arguments: + * * taker - The person receiving the alert + * * offerer - The person giving the alert and item + * * receiving - The item being given by the offerer + * + * Returns a string that will be displayed in the alert, which is `receiving.name` + * by default. + */ +/atom/movable/screen/alert/give/proc/get_receiving_name(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving) + return receiving.name + + /atom/movable/screen/alert/give/Click(location, control, params) . = ..() if(!.) @@ -343,12 +366,20 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." taker.take(offerer, receiving) SEND_SIGNAL(offerer, COMSIG_CARBON_ITEM_GIVEN, taker, receiving) + +/atom/movable/screen/alert/give/highfive + additional_desc_text = "Click this alert to slap it." + + +/atom/movable/screen/alert/give/highfive/get_receiving_name(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving) + return "a high-five" + + /atom/movable/screen/alert/give/highfive/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving) . = ..() - name = "[offerer] is offering a high-five!" - desc = "[offerer] is offering a high-five! Click this alert to slap it." RegisterSignal(offerer, COMSIG_PARENT_EXAMINE_MORE, PROC_REF(check_fake_out)) + /atom/movable/screen/alert/give/highfive/handle_transfer() var/mob/living/carbon/taker = owner if(receiving && (receiving in offerer.held_items)) @@ -391,6 +422,17 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." if(!receiving) examine_list += "[span_warning("[offerer]'s arm appears tensed up, as if [offerer.p_they()] plan on pulling it back suddenly...")]\n" + +/atom/movable/screen/alert/give/hand/get_receiving_name(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving) + additional_desc_text = "Click this alert to take it and let [offerer.p_them()] pull you around!" + return "[offerer.p_their()] [receiving]" + + +/atom/movable/screen/alert/give/hand/helping/get_receiving_name(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving) + . = ..() + additional_desc_text = "Click this alert to let them help you up!" + + /atom/movable/screen/alert/give/secret_handshake icon_state = "default" diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm index 06bd8209f94..7cd06ea3855 100644 --- a/code/datums/mood_events/generic_positive_events.dm +++ b/code/datums/mood_events/generic_positive_events.dm @@ -211,6 +211,20 @@ mood_change = 2 timeout = 45 SECONDS +/datum/mood_event/helped_up + description = "Helping them up felt good!" + mood_change = 2 + timeout = 45 SECONDS + +/datum/mood_event/helped_up/add_effects(mob/other_person, helper) + if(!other_person) + return + + if(helper) + description = "Helping [other_person] up felt good!" + else + description = "[other_person] helped me up, how nice of [other_person.p_them()]!" + /datum/mood_event/high_ten description = "AMAZING! A HIGH-TEN!" mood_change = 3 diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 312346ebcab..3fe5d94e548 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -171,12 +171,13 @@ if(give_alert_override) give_alert_type = give_alert_override - if(offered && owner.CanReach(offered) && !IS_DEAD_OR_INCAP(offered) && offered.can_hold_items()) + if(offered && is_taker_elligible(offered)) register_candidate(offered) else for(var/mob/living/carbon/possible_taker in orange(1, owner)) - if(!owner.CanReach(possible_taker) || IS_DEAD_OR_INCAP(possible_taker) || !possible_taker.can_hold_items()) + if(!is_taker_elligible(possible_taker)) continue + register_candidate(possible_taker) if(!possible_takers) // no one around @@ -234,6 +235,87 @@ SIGNAL_HANDLER qdel(src) +/** + * Is our taker valid as a target for the offering? Meant to be used when registering + * takers in `on_creation()`. You should override `additional_taker_check()` instead of this. + * + * Returns `TRUE` if the taker is valid as a target for the offering. + */ +/datum/status_effect/offering/proc/is_taker_elligible(mob/living/carbon/taker) + return owner.CanReach(taker) && !IS_DEAD_OR_INCAP(taker) && additional_taker_check(taker) + + +/** + * Additional checks added to `CanReach()` and `IS_DEAD_OR_INCAP()` in `is_taker_elligible()`. + * Should be what you override instead of `is_taker_elligible()`. By default, checks if the + * taker can hold items. + * + * Returns `TRUE` if the taker is valid as a target for the offering based on these + * additional checks. + */ +/datum/status_effect/offering/proc/additional_taker_check(mob/living/carbon/taker) + return taker.can_hold_items() + + +/** + * This status effect is meant only for items that you don't actually receive + * when offered, mostly useful for `/obj/item/hand_item` subtypes. + */ +/datum/status_effect/offering/no_item_received + + +/datum/status_effect/offering/no_item_received/additional_taker_check(mob/living/carbon/taker) + return TRUE + + +/** + * This status effect is meant only to be used for offerings that require the target to + * be resting (like when you're trying to give them a hand to help them up). + * Also doesn't require them to have their hands free (since you're not giving them + * anything). + */ +/datum/status_effect/offering/no_item_received/needs_resting + + +/datum/status_effect/offering/no_item_received/needs_resting/additional_taker_check(mob/living/carbon/taker) + return taker.body_position == LYING_DOWN + + +/datum/status_effect/offering/no_item_received/needs_resting/on_creation(mob/living/new_owner, obj/item/offer, give_alert_override, mob/living/carbon/offered) + . = ..() + RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(check_owner_standing)) + + +/datum/status_effect/offering/no_item_received/needs_resting/register_candidate(mob/living/carbon/possible_candidate) + . = ..() + RegisterSignal(possible_candidate, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(check_candidate_resting)) + + +/datum/status_effect/offering/no_item_received/needs_resting/remove_candidate(mob/living/carbon/removed_candidate) + UnregisterSignal(removed_candidate, COMSIG_LIVING_SET_BODY_POSITION) + return ..() + + +/// Simple signal handler that ensures that, if the owner stops standing, the offer no longer stands either! +/datum/status_effect/offering/no_item_received/needs_resting/proc/check_owner_standing(mob/living/carbon/owner) + if(src.owner.body_position == STANDING_UP) + return + + // This doesn't work anymore if the owner is no longer standing up, sorry! + qdel(src) + + +/// Simple signal handler that ensures that, should a candidate now be standing up, the offer won't be standing for them anymore! +/datum/status_effect/offering/no_item_received/needs_resting/proc/check_candidate_resting(mob/living/carbon/candidate) + SIGNAL_HANDLER + + if(candidate.body_position == LYING_DOWN) + return + + // No longer lying down? You're no longer eligible to take the offer, sorry! + remove_candidate(candidate) + + /datum/status_effect/offering/secret_handshake id = "secret_handshake" give_alert_type = /atom/movable/screen/alert/give/secret_handshake diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index bfb2b93a3e8..824e2eac4f7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1330,9 +1330,10 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e * * Return TRUE if you want to interrupt the offer. * * * Arguments: - * * offerer - the person offering the item + * * offerer - The person offering the item. + * * offered - The person being offered the item. */ -/obj/item/proc/on_offered(mob/living/carbon/offerer) +/obj/item/proc/on_offered(mob/living/carbon/offerer, mob/living/carbon/offered) if(SEND_SIGNAL(src, COMSIG_ITEM_OFFERING, offerer) & COMPONENT_OFFER_INTERRUPT) return TRUE diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 37939e70b1f..55576bfc609 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -313,7 +313,7 @@ user.visible_message("[span_danger("[user] slams [user.p_their()] fist down on [table]!")]", "[span_danger("You slam your fist down on [table]!")]") qdel(src) -/obj/item/hand_item/slapper/on_offered(mob/living/carbon/offerer) +/obj/item/hand_item/slapper/on_offered(mob/living/carbon/offerer, mob/living/carbon/offered) . = TRUE if(!(locate(/mob/living/carbon) in orange(1, offerer))) @@ -363,6 +363,109 @@ taker.add_mood_event("high_five", /datum/mood_event/high_five) qdel(src) + +/obj/item/hand_item/hand + name = "hand" + desc = "Sometimes, you just want to act gentlemanly." + icon_state = "latexballon" + inhand_icon_state = "nothing" + + +/obj/item/hand_item/hand/pre_attack(mob/living/carbon/help_target, mob/living/carbon/helper, params) + if(!loc.Adjacent(help_target) || !istype(helper) || !istype(help_target)) + return ..() + + if(helper.resting) + to_chat(helper, span_warning("You can't act gentlemanly when you're lying down!")) + return TRUE + + +/obj/item/hand_item/hand/pre_attack_secondary(mob/living/carbon/help_target, mob/living/carbon/helper, params) + if(!loc.Adjacent(help_target) || !istype(helper) || !istype(help_target)) + return ..() + + if(helper.resting) + to_chat(helper, span_warning("You can't act gentlemanly when you're lying down!")) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + return SECONDARY_ATTACK_CALL_NORMAL + + +/obj/item/hand_item/hand/attack(mob/living/carbon/target_mob, mob/living/carbon/user, params) + if(!loc.Adjacent(target_mob) || !istype(user) || !istype(target_mob)) + return TRUE + + user.give(target_mob) + return TRUE + + +/obj/item/hand_item/hand/on_offered(mob/living/carbon/offerer, mob/living/carbon/offered) + . = TRUE + + if(!istype(offerer)) + return + + if(offerer.body_position == LYING_DOWN) + to_chat(offerer, span_warning("You can't act gentlemanly when you're lying down!")) + return + + if(!offered) + offered = locate(/mob/living/carbon) in orange(1, offerer) + + if(offered && istype(offered) && offered.body_position == LYING_DOWN) + offerer.visible_message(span_notice("[offerer] offers [offerer.p_their()] hand to [offered], looking to help them up!"), + span_notice("You offer [offered] your hand, to try to help them up!"), null, 2) + + offerer.apply_status_effect(/datum/status_effect/offering/no_item_received/needs_resting, src, /atom/movable/screen/alert/give/hand/helping, offered) + return + + offerer.visible_message(span_notice("[offerer] extends out [offerer.p_their()] hand."), + span_notice("You extend out your hand."), null, 2) + + offerer.apply_status_effect(/datum/status_effect/offering/no_item_received, src, /atom/movable/screen/alert/give/hand) + return + + +/obj/item/hand_item/hand/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker) + . = TRUE + + if(taker.body_position == LYING_DOWN) + taker.help_shake_act(offerer) + + if(taker.body_position == LYING_DOWN) + return // That didn't help them. Awkwaaaaard. + + offerer.visible_message(span_notice("[offerer] helps [taker] up!"), span_nicegreen("You help [taker] up!"), span_hear("You hear someone helping someone else up!"), ignored_mobs = taker) + to_chat(taker, span_nicegreen("You take [offerer]'s hand, letting [offerer.p_them()] help your up! How nice of them!")) + + offerer.mind.add_memory(MEMORY_HELPED_UP, list(DETAIL_DEUTERAGONIST = taker), story_value = STORY_VALUE_OKAY) + taker.mind.add_memory(MEMORY_HELPED_UP, list(DETAIL_DEUTERAGONIST = offerer), story_value = STORY_VALUE_OKAY) + + offerer.add_mood_event("helping_up", /datum/mood_event/helped_up, taker, TRUE) // Different IDs because you could be helped up and then help someone else up. + taker.add_mood_event("helped_up", /datum/mood_event/helped_up, offerer, FALSE) + + qdel(src) + return + + if(taker.buckled?.buckle_prevents_pull) + return // Can't start pulling them if they're buckled and that prevents pulls. + + // We do a little switcheroo to ensure that it displays the pulling message that mentions + // taking taker by their hands. + var/offerer_zone_selected = offerer.zone_selected + offerer.zone_selected = "r_arm" + var/did_we_pull = offerer.start_pulling(taker) // Will return either null or FALSE. We only want to silence FALSE. + offerer.zone_selected = offerer_zone_selected + + if(did_we_pull == FALSE) + return // That didn't work for one reason or the other. No need to display anything. + + to_chat(offerer, span_notice("[taker] takes your hand, allowing you to pull [taker.p_them()] along.")) + to_chat(taker, span_notice("You take [offerer]'s hand, which allows [offerer.p_them()] to pull you along. How polite!")) + + qdel(src) + + /obj/item/hand_item/stealer name = "steal" desc = "Your filthy little fingers are ready to commit crimes." @@ -427,14 +530,14 @@ blown_kiss.fire() qdel(src) -/obj/item/hand_item/kisser/on_offered(mob/living/carbon/offerer) +/obj/item/hand_item/kisser/on_offered(mob/living/carbon/offerer, mob/living/carbon/offered) if(!(locate(/mob/living/carbon) in orange(1, offerer))) return TRUE cheek_kiss = (offerer.zone_selected != BODY_ZONE_PRECISE_MOUTH) offerer.visible_message(span_notice("[offerer] leans in slightly, offering a kiss[cheek_kiss ? " on the cheek" : ""]!"), span_notice("You lean in slightly, indicating you'd like to offer a kiss[cheek_kiss ? " on the cheek" : ""]!"), null, 2) - offerer.apply_status_effect(/datum/status_effect/offering, src) + offerer.apply_status_effect(/datum/status_effect/offering/no_item_received, src) return TRUE /obj/item/hand_item/kisser/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker) diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm index 3b6e962115a..bc9384de23b 100644 --- a/code/modules/mob/living/carbon/emote.dm +++ b/code/modules/mob/living/carbon/emote.dm @@ -134,6 +134,26 @@ qdel(N) to_chat(user, span_warning("You're incapable of slapping in your current state.")) + +/datum/emote/living/carbon/hand + key = "hand" + key_third_person = "hands" + hands_use_check = TRUE + + +/datum/emote/living/carbon/hand/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return + + var/obj/item/hand_item/hand/hand = new(user) + if(user.put_in_hands(hand)) + to_chat(user, span_notice("You ready your hand.")) + else + qdel(hand) + to_chat(user, span_warning("You're incapable of using your hand in your current state.")) + + /datum/emote/living/carbon/snap key = "snap" key_third_person = "snaps" diff --git a/strings/memories.json b/strings/memories.json index 7ebbe0b4d87..c09ff61cbc7 100644 --- a/strings/memories.json +++ b/strings/memories.json @@ -191,6 +191,13 @@ "What a %HIGHFIVE_TYPE! %PROTAGONIST %MOOD.", "Wow! %PROTAGONIST %MOOD!" ], + "helped_up_names":[ + "%PROTAGONIST gentlemanly helping up %DEUTERAGONIST." + ], + "helped_up_starts":[ + "%PROTAGONIST helping up %DEUTERAGONIST", + "%DEUTERAGONIST taking the hand offered graciously by %PROTAGONIST to get up" + ], "borged_names":[ "The borging of %PROTAGONIST." ],