diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index b6324d75f0..1ea69c7462 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -40,7 +40,7 @@
thealert.override_alerts = override
if(override)
thealert.timeout = null
- thealert.mob_viewer = src
+ thealert.owner = src
if(new_master)
var/old_layer = new_master.layer
@@ -95,7 +95,6 @@
var/severity = 0
var/alerttooltipstyle = ""
var/override_alerts = FALSE //If it is overriding other alerts of the same type
- var/mob/mob_viewer //the mob viewing this alert
var/mob/owner //Alert owner
@@ -250,6 +249,8 @@ or something covering your eyes."
/atom/movable/screen/alert/mind_control/Click()
var/mob/living/L = usr
+ if(L != owner)
+ return
to_chat(L, "[command]")
/atom/movable/screen/alert/hypnosis
@@ -270,7 +271,7 @@ If you're feeling frisky, examine yourself and click the underlined item to pull
icon_state = "embeddedobject"
/atom/movable/screen/alert/embeddedobject/Click()
- if(isliving(usr))
+ if(isliving(usr) && usr == owner)
var/mob/living/carbon/M = usr
return M.help_shake_act(M)
@@ -299,7 +300,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/atom/movable/screen/alert/fire/Click()
var/mob/living/L = usr
- if(!istype(L) || !L.can_resist())
+ if(!istype(L) || !L.can_resist() || L != owner)
return
L.MarkResistTime()
if(CHECK_MOBILITY(L, MOBILITY_MOVE))
@@ -334,10 +335,8 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
. = ..()
if(!.)
return
-
if(!iscarbon(usr))
CRASH("User for [src] is of type \[[usr.type]\]. This should never happen.")
-
handle_transfer()
/// An overrideable proc used simply to hand over the item when claimed, this is a proc so that high-fives can override them since nothing is actually transferred
@@ -353,6 +352,54 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
to_chat(owner, span_warning("You moved out of range of [offerer]!"))
owner.clear_alert("[offerer]")
+/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/check_fake_out)
+
+/atom/movable/screen/alert/give/highfive/handle_transfer()
+ var/mob/living/carbon/taker = owner
+ if(receiving && (receiving in offerer.held_items))
+ receiving.on_offer_taken(offerer, taker)
+ return
+ too_slow_p1()
+
+/// If the person who offered the high five no longer has it when we try to accept it, we get pranked hard
+/atom/movable/screen/alert/give/highfive/proc/too_slow_p1()
+ var/mob/living/carbon/rube = owner
+ if(!rube || !offerer)
+ qdel(src)
+ return
+
+ offerer.visible_message(span_notice("[rube] rushes in to high-five [offerer], but-"), span_nicegreen("[rube] falls for your trick just as planned, lunging for a high-five that no longer exists! Classic!"), ignored_mobs=rube)
+ to_chat(rube, span_nicegreen("You go in for [offerer]'s high-five, but-"))
+ addtimer(CALLBACK(src, .proc/too_slow_p2, offerer, rube), 0.5 SECONDS)
+
+/// Part two of the ultimate prank
+/atom/movable/screen/alert/give/highfive/proc/too_slow_p2()
+ var/mob/living/carbon/rube = owner
+ if(!rube || !offerer)
+ qdel(src)
+ return
+
+ offerer.visible_message(span_danger("[offerer] pulls away from [rube]'s slap at the last second, dodging the high-five entirely!"), span_nicegreen("[rube] fails to make contact with your hand, making an utter fool of [rube.p_them()]self!"), span_hear("You hear a disappointing sound of flesh not hitting flesh!"), ignored_mobs=rube)
+ var/all_caps_for_emphasis = uppertext("NO! [offerer] PULLS [offerer.p_their()] HAND AWAY FROM YOURS! YOU'RE TOO SLOW!")
+ to_chat(rube, span_userdanger("[all_caps_for_emphasis]"))
+ playsound(offerer, 'sound/weapons/thudswoosh.ogg', 100, TRUE, 1)
+ rube.Knockdown(1 SECONDS)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/down_low)
+ SEND_SIGNAL(rube, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/too_slow)
+ qdel(src)
+
+/// If someone examine_more's the offerer while they're trying to pull a too-slow, it'll tip them off to the offerer's trickster ways
+/atom/movable/screen/alert/give/highfive/proc/check_fake_out(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(!receiving)
+ examine_list += "[span_warning("[offerer]'s arm appears tensed up, as if [offerer.p_they()] plan on pulling it back suddenly...")]\n"
+
+/// Families handshakes
/atom/movable/screen/alert/give/secret_handshake
icon_state = "default"
@@ -419,10 +466,10 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/atom/movable/screen/alert/bloodsense/process()
var/atom/blood_target
- if(!mob_viewer.mind)
+ if(!owner.mind)
return
- var/datum/antagonist/cult/antag = mob_viewer.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
+ var/datum/antagonist/cult/antag = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
if(!antag?.cult_team)
return
var/datum/objective/sacrifice/sac_objective = locate() in antag.cult_team.objectives
@@ -459,7 +506,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
add_overlay(narnar)
return
var/turf/P = get_turf(blood_target)
- var/turf/Q = get_turf(mob_viewer)
+ var/turf/Q = get_turf(owner)
if(!P || !Q || (P.z != Q.z)) //The target is on a different Z level, we cannot sense that far.
icon_state = "runed_sense2"
desc = "You can no longer sense your target's presence."
@@ -523,7 +570,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
for(var/mob/living/L in GLOB.alive_mob_list)
if(is_servant_of_ratvar(L))
servants++
- var/datum/antagonist/clockcult/C = mob_viewer.mind.has_antag_datum(/datum/antagonist/clockcult,TRUE)
+ var/datum/antagonist/clockcult/C = owner.mind.has_antag_datum(/datum/antagonist/clockcult,TRUE)
if(C && C.clock_team)
textlist += "[C.clock_team.eminence ? "There is an Eminence." : "There is no Eminence! Get one ASAP!"]
"
textlist += "There are currently [servants] servant[servants > 1 ? "s" : ""] of Ratvar.
"
@@ -621,7 +668,7 @@ so as to remain in compliance with the most up-to-date laws."
var/atom/target = null
/atom/movable/screen/alert/hackingapc/Click()
- if(!usr || !usr.client)
+ if(!usr || !usr.client || usr != owner)
return
if(!target)
return
@@ -647,7 +694,7 @@ so as to remain in compliance with the most up-to-date laws."
timeout = 300
/atom/movable/screen/alert/notify_cloning/Click()
- if(!usr || !usr.client)
+ if(!usr || !usr.client || usr != owner)
return
var/mob/dead/observer/G = usr
G.reenter_corpse()
@@ -661,7 +708,7 @@ so as to remain in compliance with the most up-to-date laws."
var/action = NOTIFY_JUMP
/atom/movable/screen/alert/notify_action/Click()
- if(!usr || !usr.client)
+ if(!usr || !usr.client || usr != owner)
return
if(!target)
return
@@ -695,14 +742,14 @@ so as to remain in compliance with the most up-to-date laws."
/atom/movable/screen/alert/restrained/Click()
var/mob/living/L = usr
- if(!istype(L) || !L.can_resist())
+ if(!istype(L) || !L.can_resist() || L != owner)
return
L.MarkResistTime()
return L.resist_restraints()
/atom/movable/screen/alert/restrained/buckled/Click()
var/mob/living/L = usr
- if(!istype(L) || !L.can_resist())
+ if(!istype(L) || !L.can_resist() || L != owner)
return
L.MarkResistTime()
return L.resist_buckle()
@@ -719,7 +766,7 @@ so as to remain in compliance with the most up-to-date laws."
/atom/movable/screen/alert/shoes/Click()
var/mob/living/carbon/C = usr
- if(!istype(C) || !C.can_resist() || C != mob_viewer || !C.shoes)
+ if(!istype(C) || !C.can_resist() || C != owner || !C.shoes)
return
C.MarkResistTime()
C.shoes.handle_tying(C)
@@ -727,11 +774,14 @@ so as to remain in compliance with the most up-to-date laws."
// PRIVATE = only edit, use, or override these if you're editing the system as a whole
// Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there
-/datum/hud/proc/reorganize_alerts()
+/datum/hud/proc/reorganize_alerts(mob/viewmob)
+ var/mob/screenmob = viewmob || mymob
+ if(!screenmob.client)
+ return
var/list/alerts = mymob.alerts
if(!hud_shown)
for(var/i = 1, i <= alerts.len, i++)
- mymob.client.screen -= alerts[alerts[i]]
+ screenmob.client.screen -= alerts[alerts[i]]
return 1
for(var/i = 1, i <= alerts.len, i++)
var/atom/movable/screen/alert/alert = alerts[alerts[i]]
@@ -751,7 +801,10 @@ so as to remain in compliance with the most up-to-date laws."
else
. = ""
alert.screen_loc = .
- mymob.client.screen |= alert
+ screenmob.client.screen |= alert
+ if(!viewmob)
+ for(var/M in mymob.observers)
+ reorganize_alerts(M)
return 1
/atom/movable/screen/alert/Click(location, control, params)
@@ -761,6 +814,8 @@ so as to remain in compliance with the most up-to-date laws."
if(paramslist["shift"]) // screen objects don't do the normal Click() stuff so we'll cheat
to_chat(usr, "[name] - [desc]")
return
+ if(usr != owner)
+ return
if(master)
return usr.client.Click(master, location, control, params)
@@ -768,5 +823,5 @@ so as to remain in compliance with the most up-to-date laws."
. = ..()
severity = 0
master = null
- mob_viewer = null
+ owner = null
screen_loc = ""
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index b9514bff46..bb637ed445 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -93,3 +93,10 @@
screenmob.client.screen -= static_inventory
else
screenmob.client.screen += static_inventory
+
+//We should only see observed mob alerts.
+/datum/hud/ghost/reorganize_alerts(mob/viewmob)
+ var/mob/dead/observer/O = mymob
+ if (istype(O) && O.observetarget)
+ return
+ . = ..()
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 1e728d1a39..fb5cf206d4 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -193,7 +193,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
hud_version = display_hud_version
persistent_inventory_update(screenmob)
screenmob.update_action_buttons(1)
- reorganize_alerts()
+ reorganize_alerts(screenmob)
screenmob.reload_fullscreen()
update_parallax_pref(screenmob)
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 90982a00ec..4536422e08 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -294,3 +294,34 @@
description = "I hate when my shoes come untied!\n"
mood_change = -3
timeout = 1 MINUTES
+
+/datum/mood_event/high_five_alone
+ description = "I tried getting a high-five with no one around, how embarassing!\n"
+ mood_change = -2
+ timeout = 1 MINUTES
+
+/datum/mood_event/high_five_full_hand
+ description = "Oh God, I don't even know how to high-five correctly...\n"
+ mood_change = -1
+ timeout = 45 SECONDS
+
+/datum/mood_event/left_hanging
+ description = "But everyone loves high fives! Maybe people just... hate me?\n"
+ mood_change = -2
+ timeout = 1.5 MINUTES
+
+/datum/mood_event/too_slow
+ description = "NO! HOW COULD I BE.... TOO SLOW???\n"
+ mood_change = -2 // multiplied by how many people saw it happen, up to 8, so potentially massive. the ULTIMATE prank carries a lot of weight
+ timeout = 2 MINUTES
+
+/datum/mood_event/too_slow/add_effects(param)
+ var/people_laughing_at_you = 1 // start with 1 in case they're on the same tile or something
+ for(var/mob/living/carbon/iter_carbon in oview(owner, 7))
+ if(iter_carbon.stat == CONSCIOUS)
+ people_laughing_at_you++
+ if(people_laughing_at_you > 7)
+ break
+
+ mood_change *= people_laughing_at_you
+ return ..()
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index b73756c2b1..e240f4ec6d 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -205,3 +205,18 @@
/datum/mood_event/cleared_stomach
description = "Feels nice to get that out of the way!\n"
mood_change = 3
+
+/datum/mood_event/high_five
+ description = "I love getting high fives!\n"
+ mood_change = 2
+ timeout = 45 SECONDS
+
+/datum/mood_event/high_ten
+ description = "AMAZING! A HIGH-TEN!\n"
+ mood_change = 3
+ timeout = 45 SECONDS
+
+/datum/mood_event/down_low
+ description = "HA! What a rube, they never stood a chance...\n"
+ mood_change = 4
+ timeout = 1.5 MINUTES
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index b75676dbd2..b3e3d15c65 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -929,11 +929,13 @@
/atom/movable/screen/alert/status_effect/strandling/Click(location, control, params)
. = ..()
- to_chat(mob_viewer, "You attempt to remove the durathread strand from around your neck.")
- if(do_after(mob_viewer, 35, null, mob_viewer))
- if(isliving(mob_viewer))
- var/mob/living/L = mob_viewer
- to_chat(mob_viewer, "You successfully remove the durathread strand.")
+ if(usr != owner)
+ return
+ to_chat(owner, "You attempt to remove the durathread strand from around your neck.")
+ if(do_after(owner, 35, null, owner))
+ if(isliving(owner))
+ var/mob/living/L = owner
+ to_chat(owner, "You successfully remove the durathread strand.")
L.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND)
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index f5ceff09aa..f3171f6197 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -35,8 +35,9 @@
next_tick = world.time + tick_interval
if(alert_type)
var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
- A.attached_effect = src //so the alert can reference us, if it needs to
- linked_alert = A //so we can reference the alert, if we need to
+ if(istype(A))
+ A?.attached_effect = src //so the alert can reference us, if it needs to
+ linked_alert = A //so we can reference the alert, if we need to
START_PROCESSING(SSstatus_effects, src)
return TRUE
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index c89c0ce735..3fd31941f3 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1229,10 +1229,50 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/slapper/on_offered(mob/living/carbon/offerer)
. = TRUE
+
+ if(!(locate(/mob/living/carbon) in orange(1, offerer)))
+ visible_message(span_danger("[offerer] raises [offerer.p_their()] arm, looking around for a high-five, but there's no one around!"), \
+ span_warning("You post up, looking for a high-five, but finding no one within range!"), null, 2)
+ return
+ offerer.visible_message(span_notice("[offerer] raises [offerer.p_their()] arm, looking for a high-five!"), \
+ span_notice("You post up, looking for a high-five!"), null, 2)
+ offerer.apply_status_effect(STATUS_EFFECT_OFFERING, src, /atom/movable/screen/alert/give/highfive)
+
+/// Yeah broh! This is where we do the high-fiving (or high-tenning :o)
/obj/item/slapper/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
. = TRUE
+ var/open_hands_taker
+ var/slappers_giver
+ for(var/i in taker.held_items) // see how many hands the taker has open for high'ing
+ if(isnull(i))
+ open_hands_taker++
+
+ if(!open_hands_taker)
+ to_chat(taker, span_warning("You can't high-five [offerer] with no open hands!"))
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five_full_hand) // not so successful now!
+ return
+
+ for(var/i in offerer.held_items)
+ var/obj/item/slapper/slap_check = i
+ if(istype(slap_check))
+ slappers_giver++
+
+ if(slappers_giver >= 2) // we only check this if it's already established the taker has 2+ hands free
+ offerer.visible_message(span_notice("[taker] enthusiastically high-tens [offerer]!"), span_nicegreen("Wow! You're high-tenned [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You give high-tenning [offerer] your all!"))
+ playsound(offerer, 'sound/weapons/slap.ogg', 100, TRUE, 1)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
+ else
+ offerer.visible_message(span_notice("[taker] high-fives [offerer]!"), span_nicegreen("All right! You're high-fived by [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You high-five [offerer]!"))
+ playsound(offerer, 'sound/weapons/slap.ogg', 50, TRUE, -1)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
+ qdel(src)
+
/// Gangster secret handshakes.
/obj/item/slapper/secret_handshake
name = "Secret Handshake"
diff --git a/code/modules/antagonists/gang/gang.dm b/code/modules/antagonists/gang/gang.dm
index d827ce6bde..ea81c8b1af 100644
--- a/code/modules/antagonists/gang/gang.dm
+++ b/code/modules/antagonists/gang/gang.dm
@@ -241,12 +241,12 @@
qdel(secret_handshake_item)
to_chat(owner, span_warning("You're incapable of performing a handshake in your current state."))
return FALSE
- owner.visible_message(span_notice("[src] is offering to induct people into the Family."),
+ owner.visible_message(span_notice("[owner] is offering to induct people into the Family."),
span_notice("You offer to induct people into the Family."), null, 2)
if(H.has_status_effect(STATUS_EFFECT_HANDSHAKE))
return FALSE
if(!(locate(/mob/living/carbon) in orange(1, owner)))
- owner.visible_message(span_danger("[src] offers to induct people into the Family, but nobody was around."), \
+ owner.visible_message(span_danger("[owner] offers to induct people into the Family, but nobody was around."), \
span_warning("You offer to induct people into the Family, but nobody is around."), null, 2)
return FALSE
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 7a1e6b14f3..c98824fa7f 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -102,7 +102,7 @@
worn_y_dimension = world.icon_size
/obj/item/clothing/shoes/dropped(mob/user)
- if(our_alert && (our_alert.mob_viewer == user))
+ if(our_alert && (our_alert.owner == user))
user.clear_alert("shoealert")
if(offset && equipped_before_drop)
restore_offsets(user)