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