From fefb4401c021c2a1a57a5e86d920e7424e05d05b Mon Sep 17 00:00:00 2001 From: Luc <89928798+lewcc@users.noreply.github.com> Date: Fri, 17 May 2024 08:58:30 -0400 Subject: [PATCH] Adds a new rock-paper-scissors emote (#25300) * adds rock paper scissors mechanics * better icon * Cleans up the last bit of the implementation * use barber scissors instead * minor cleanups * Apply suggestions from code review Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> Signed-off-by: Luc <89928798+lewcc@users.noreply.github.com> * Update code/datums/status_effects/neutral.dm Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com> Signed-off-by: Luc <89928798+lewcc@users.noreply.github.com> --------- Signed-off-by: Luc <89928798+lewcc@users.noreply.github.com> Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com> --- code/__DEFINES/emotes_defines.dm | 11 ++ code/__DEFINES/status_effects.dm | 1 + code/_onclick/hud/radial.dm | 2 + code/datums/emote.dm | 30 +++-- code/datums/status_effects/neutral.dm | 116 ++++++++++++++++-- .../mob/living/carbon/human/human_emote.dm | 45 ++++++- 6 files changed, 186 insertions(+), 19 deletions(-) diff --git a/code/__DEFINES/emotes_defines.dm b/code/__DEFINES/emotes_defines.dm index 9940edab859..f654bb678bb 100644 --- a/code/__DEFINES/emotes_defines.dm +++ b/code/__DEFINES/emotes_defines.dm @@ -57,6 +57,8 @@ #define EMOTE_TARGET_BHVR_RAW 4 /// The emote target should be just a number. Anything else will be rejected. #define EMOTE_TARGET_BHVR_NUM 5 +/// The emote target is used elsewhere, and processing should be skipped. +#define EMOTE_TARGET_BHVR_IGNORE 6 // This set determines the type of target that we want to check for. @@ -75,3 +77,12 @@ /// List of emotes useable by ghosties #define USABLE_DEAD_EMOTES list("*flip", "*spin") + +// Strings used for the rock paper scissors emote and status effect +#define RPS_EMOTE_ROCK "rock" +#define RPS_EMOTE_PAPER "paper" +#define RPS_EMOTE_SCISSORS "scissors" + +#define RPS_EMOTE_THEY_WIN "aww" +#define RPS_EMOTE_WE_WIN "yay" +#define RPS_EMOTE_TIE "tie" diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index d34fb486829..9f3b5bb8893 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -178,6 +178,7 @@ #define STATUS_EFFECT_HIGHFIVE /datum/status_effect/high_five #define STATUS_EFFECT_DAP /datum/status_effect/high_five/dap #define STATUS_EFFECT_HANDSHAKE /datum/status_effect/high_five/handshake +#define STATUS_EFFECT_RPS /datum/status_effect/high_five/rps #define STATUS_EFFECT_CHARGING /datum/status_effect/charging diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index 36082679a21..9a22a2b2891 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -239,6 +239,8 @@ GLOBAL_LIST_EMPTY(radial_menus) return else next_check = world.time + check_delay + // if you're wondering why your radial menus aren't clickable while debugging: + // it's probably the stoplag call here, try it again without any breakpoints stoplag(1) /datum/radial_menu/Destroy() diff --git a/code/datums/emote.dm b/code/datums/emote.dm index fb1dbb66722..50e70232a35 100644 --- a/code/datums/emote.dm +++ b/code/datums/emote.dm @@ -147,16 +147,16 @@ * * Arguments: * * user - Person that is trying to send the emote. - * * params - Parameters added after the emote. + * * emote_arg - String parameter added after the emote. * * type_override - Override to the current emote_type. * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). * * Returns TRUE if it was able to run the emote, FALSE otherwise. */ -/datum/emote/proc/run_emote(mob/user, params, type_override, intentional = FALSE) +/datum/emote/proc/run_emote(mob/user, emote_arg, type_override, intentional = FALSE) . = TRUE var/msg = select_message_type(user, message, intentional) - if(params && message_param) + if(emote_arg && message_param) // In this case, we did make some changes to the message that will be used, and we want to add the postfix on with the new parameters. // This is applicable to things like mimes, who this lets have a target on their canned emote responses. // Note that we only do this if we would otherwise have a message param, meaning there should be some target by default. @@ -164,17 +164,17 @@ if(message_param == EMOTE_PARAM_USE_POSTFIX || (msg != message && message_postfix)) if(!message_postfix) CRASH("Emote was specified to use postfix but message_postfix is empty.") - msg = select_param(user, params, "[remove_ending_punctuation(msg)] [message_postfix]", msg) + msg = select_param(user, emote_arg, "[remove_ending_punctuation(msg)] [message_postfix]", msg) else if(msg == message) // In this case, we're not making any substitutions in select_message_type, but we do have some params we want to sub in. - msg = select_param(user, params, message_param, message) + msg = select_param(user, emote_arg, message_param, message) // If this got propogated up, jump out. if(msg == EMOTE_ACT_STOP_EXECUTION) return TRUE if(isnull(msg)) - to_chat(user, "'[params]' isn't a valid parameter for [key].") + to_chat(user, "'[emote_arg]' isn't a valid parameter for [key].") return TRUE msg = replace_pronoun(user, msg) @@ -230,13 +230,13 @@ * Try to run an emote, checking can_run_emote once before executing the emote itself. * * * user - User of the emote - * * params - Params of the emote to be passed to run_emote + * * params - An optional extra argument included after the emote key. * * type_override - emote type to override the existing one with, if given. * * intentional - Whether or not the emote was triggered intentionally (if false, the emote was forced by code). * * Returns TRUE if the emote was able to be run (or failed successfully), or FALSE if the emote is unusable. */ -/datum/emote/proc/try_run_emote(mob/user, params, type_override, intentional = FALSE) +/datum/emote/proc/try_run_emote(mob/user, emote_arg, type_override, intentional = FALSE) // You can use this signal to block execution of emotes from components/other sources. var/sig_res = SEND_SIGNAL(user, COMSIG_MOB_PREEMOTE, key, intentional) switch(sig_res) @@ -245,9 +245,18 @@ if(COMPONENT_BLOCK_EMOTE_SILENT) return TRUE - . = run_emote(user, params, type_override, intentional) + . = run_emote(user, emote_arg, type_override, intentional) // safeguard in case these get modified + reset_emote() + +/** + * Reset the emote back to its original state. + * Necessary if you've made modifications to the emote itself over the course of its + * execution, as emotes are singletons, and changes would be reflected on every usage of the emote. + */ +/datum/emote/proc/reset_emote() + SHOULD_CALL_PARENT(TRUE) message = initial(message) message_param = initial(message_param) @@ -421,6 +430,9 @@ */ /datum/emote/proc/select_param(mob/user, params, substitution, base_message) + if(target_behavior == EMOTE_TARGET_BHVR_IGNORE) + return base_message + if(target_behavior == EMOTE_TARGET_BHVR_RAW) return replacetext(substitution, "%t", params) diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 275aa7bda3c..659dbf242dd 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -69,6 +69,20 @@ user.remove_status_effect(type) highfived.remove_status_effect(type) +/datum/status_effect/high_five/proc/wiz_effect(mob/living/carbon/user, mob/living/carbon/highfived) + user.status_flags |= GODMODE + highfived.status_flags |= GODMODE + explosion(get_turf(user), 5, 2, 1, 3, cause = id) + // explosions have a spawn so this makes sure that we don't get gibbed + addtimer(CALLBACK(src, PROC_REF(wiz_cleanup), user, highfived), 0.3 SECONDS) // I want to be sure this lasts long enough, with lag. + add_attack_logs(user, highfived, "caused a wizard [id] explosion") + +/datum/status_effect/high_five/proc/post_start() + return + +/datum/status_effect/high_five/proc/regular_effect(mob/living/carbon/user, mob/living/carbon/highfived) + user.visible_message("[user.name] and [highfived.name] [success]") + /datum/status_effect/high_five/on_apply() if(!iscarbon(owner)) return FALSE @@ -82,25 +96,23 @@ continue if(is_wiz && iswizard(C)) user.visible_message("[user.name] and [C.name] [critical_success]") - user.status_flags |= GODMODE - C.status_flags |= GODMODE - explosion(get_turf(user), 5, 2, 1, 3, cause = id) - // explosions have a spawn so this makes sure that we don't get gibbed - addtimer(CALLBACK(src, PROC_REF(wiz_cleanup), user, C), 0.3 SECONDS) //I want to be sure this lasts long enough, with lag. - add_attack_logs(user, C, "caused a wizard [id] explosion") + wiz_effect(user, C) both_wiz = TRUE user.do_attack_animation(C, no_effect = TRUE) C.do_attack_animation(user, no_effect = TRUE) playsound(user, sound_effect, 80) if(!both_wiz) - user.visible_message("[user.name] and [C.name] [success]") + regular_effect(user, C) user.remove_status_effect(type) C.remove_status_effect(type) return FALSE + // We can return to break out of the loop here so we don't auto-remove (which causes the timer on the wizard highfive to break) + // This is safe because we only pass the continue if we don't have the status effect return TRUE // DO NOT AUTOREMOVE owner.custom_emote(EMOTE_VISIBLE, request) owner.create_point_bubble_from_path(item_path, FALSE) + post_start() /datum/status_effect/high_five/on_timeout() owner.visible_message("[owner] [get_missed_message()]") @@ -144,6 +156,96 @@ return pick(missed_messages) +/datum/status_effect/high_five/rps + id = "rps" + critical_success = "both play rock -- THEY'RE GOING IN FOR THE FISTBUMP!" + success = "play rock-paper-scissors!" + sound_effect = 'sound/effects/glassknock.ogg' + request = "wants to play rock-paper-scissors!" + item_path = /obj/item/claymore // it's time to d-d-d-d-d-d-d-duel! + /// The move that you'll be making. + var/move + +/datum/status_effect/high_five/rps/get_missed_message() + var/list/missed_messages = list( + "just seems to be practicing against [owner.p_themselves()]. [owner.p_are(TRUE)] [owner.p_they()] losing?", + "seems more interested in a thumb war." + ) + + return pick(missed_messages) + +/datum/status_effect/high_five/rps/proc/get_move_status(my_move, their_move) + if(my_move == their_move) + return RPS_EMOTE_TIE + switch(my_move) + if(RPS_EMOTE_ROCK) + return their_move == RPS_EMOTE_SCISSORS ? RPS_EMOTE_WE_WIN : RPS_EMOTE_THEY_WIN + + if(RPS_EMOTE_PAPER) + return their_move == RPS_EMOTE_ROCK ? RPS_EMOTE_WE_WIN : RPS_EMOTE_THEY_WIN + + if(RPS_EMOTE_SCISSORS) + return their_move == RPS_EMOTE_PAPER ? RPS_EMOTE_WE_WIN : RPS_EMOTE_THEY_WIN + + else + CRASH("Unknown emote rock type") + +/datum/status_effect/high_five/rps/post_start() + playsound(owner, 'sound/effects/glassknock.ogg', 50, FALSE) + +/datum/status_effect/high_five/rps/regular_effect(mob/living/carbon/user, mob/living/carbon/highfived) + var/datum/status_effect/high_five/rps/their_status_effect = highfived.has_status_effect(type) + var/outcome = get_move_status(move, their_status_effect.move) + var/outcome_msg + switch(outcome) + if(RPS_EMOTE_TIE) + outcome_msg = "It's a tie!" + if(RPS_EMOTE_WE_WIN) + outcome_msg = "[user] wins!" + if(RPS_EMOTE_THEY_WIN) + outcome_msg = "[highfived] wins!" + + user.visible_message( + "[user] plays [move], and [highfived] plays [their_status_effect.move].", + "[highfived] plays [their_status_effect.move].", + "It sounds like rock-paper-scissors." + ) + + user.visible_message( + "[outcome_msg]", + blind_message = "It sounds like [pick(user, highfived)] won!" // you're blind how are you supposed to know + ) + +/datum/status_effect/high_five/rps/on_creation(mob/living/new_owner, made_move) + if(made_move) + if(!(made_move in list(RPS_EMOTE_ROCK, RPS_EMOTE_PAPER, RPS_EMOTE_SCISSORS))) + stack_trace("RPS emote was given an invalid move type on creation.") + else + move = made_move + + return ..() + +/datum/status_effect/high_five/rps/on_apply() + if(!isnull(move)) + to_chat(owner, "You prepare to play [move].") + return ..() // we already have the move, probably from the emote passing it in + + move = get_rock_paper_scissors_move(owner) + if(move == null) + return FALSE // make it auto-remove itself + + to_chat(owner, "You prepare to play [move].") + return ..() + + +/proc/get_rock_paper_scissors_move(mob/living/carbon/user) + var/list/move_icons = list( + RPS_EMOTE_SCISSORS = image(icon = 'icons/obj/items.dmi', icon_state = "bscissor"), + RPS_EMOTE_PAPER = image(icon = 'icons/obj/bureaucracy.dmi', icon_state = "paper"), + RPS_EMOTE_ROCK = image(icon = 'icons/obj/toy.dmi', icon_state = "pet_rock") + ) + return show_radial_menu(user, user, move_icons) + /// A status effect that can have a certain amount of "bonus" duration added, which extends the duration every tick, /// although there is a maximum amount of bonus time that can be active at any given time. /datum/status_effect/limited_bonus diff --git a/code/modules/mob/living/carbon/human/human_emote.dm b/code/modules/mob/living/carbon/human/human_emote.dm index a85c0158ccf..7cb251ab8a4 100644 --- a/code/modules/mob/living/carbon/human/human_emote.dm +++ b/code/modules/mob/living/carbon/human/human_emote.dm @@ -375,6 +375,8 @@ cooldown = 5 SECONDS /// Status effect to apply when this emote is used. Should be a subtype var/status = STATUS_EFFECT_HIGHFIVE + /// title override, used for the re-use message. + var/action_name /datum/emote/living/carbon/human/highfive/can_run_emote(mob/user, status_check, intentional) . = ..() @@ -382,12 +384,17 @@ if(user_carbon.restrained()) return FALSE +/datum/emote/living/carbon/human/highfive/proc/set_status(mob/living/carbon/user) + return user.apply_status_effect(status) + /datum/emote/living/carbon/human/highfive/run_emote(mob/user, params, type_override, intentional) var/mob/living/carbon/user_carbon = user if(user_carbon.has_status_effect(status)) - user.visible_message("[user.name] shakes [user.p_their()] hand around slightly, impatiently waiting for someone to [key].") + user.visible_message("[user.name] shakes [user.p_their()] hand around slightly, impatiently waiting for someone to [!isnull(action_name) ? action_name : key].") + return TRUE + var/datum/result = set_status(user) + if(QDELETED(result)) return TRUE - user_carbon.apply_status_effect(status) return ..() @@ -401,6 +408,39 @@ key_third_person = "handshakes" status = STATUS_EFFECT_HANDSHAKE +/datum/emote/living/carbon/human/highfive/rps + key = "rps" + param_desc = "r,p,s" + hands_use_check = TRUE + status = STATUS_EFFECT_RPS + action_name = "play rock-paper-scissors with" + target_behavior = EMOTE_TARGET_BHVR_IGNORE + /// If the user used parameters, the move that will be made. + var/move + +/datum/emote/living/carbon/human/highfive/rps/run_emote(mob/user, emote_arg, type_override, intentional) + switch(lowertext(emote_arg)) + if("r", "rock") + move = RPS_EMOTE_ROCK + if("p", "paper") + move = RPS_EMOTE_PAPER + if("s", "scissors") + move = RPS_EMOTE_SCISSORS + + // if it's an invalid emote param, just fall through and let them select + + return ..() + +/datum/emote/living/carbon/human/highfive/rps/set_status(mob/living/carbon/user) + if(!isnull(move)) + // if they supplied a valid parameter, use that for the move + return user.apply_status_effect(status, move) + return user.apply_status_effect(status) + +/datum/emote/living/carbon/human/highfive/rps/reset_emote() + ..() + move = initial(move) + /datum/emote/living/carbon/human/snap key = "snap" key_third_person = "snaps" @@ -453,7 +493,6 @@ mob_type_allowed_typecache = list(/mob/living/carbon/human) hands_use_check = TRUE - ///////// // Species-specific emotes