diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index e8e75c132a..3d2dae2076 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -74,6 +74,7 @@
#define ADMIN_PUNISHMENT_MAZING "Puzzle"
#define ADMIN_PUNISHMENT_PIE "Cream Pie"
#define ADMIN_PUNISHMENT_CUSTOM_PIE "Custom Cream Pie"
+#define ADMIN_PUNISHMENT_SHOES "Knot Shoes"
#define AHELP_ACTIVE 1
#define AHELP_CLOSED 2
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 42139cdeda..0801da6911 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -305,6 +305,7 @@
#define GRAB_PIXEL_SHIFT_NECK 16
#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
+#define INTERACTING_WITH(X, Y) (Y in X.do_afters)
/// Field of vision defines.
#define FOV_90_DEGREES 90
diff --git a/code/__HELPERS/do_after.dm b/code/__HELPERS/do_after.dm
index f1f483c345..b2202ec9ae 100644
--- a/code/__HELPERS/do_after.dm
+++ b/code/__HELPERS/do_after.dm
@@ -166,6 +166,9 @@
var/target_loc = target.loc
+ LAZYADD(user.do_afters, target)
+ LAZYADD(target.targeted_by, user)
+
var/holding = user.get_active_held_item()
var/datum/progressbar/progbar
if (progress)
@@ -184,6 +187,10 @@
if(uninterruptible)
continue
+ if(!(target in user.do_afters))
+ . = FALSE
+ break
+
if(drifting && !user.inertia_dir)
drifting = 0
user_loc = user.loc
@@ -194,6 +201,9 @@
if (progress)
qdel(progbar)
+ if(!QDELETED(target))
+ LAZYREMOVE(user.do_afters, target)
+ LAZYREMOVE(target.targeted_by, user)
//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action
/mob/proc/break_do_after_checks(list/checked_health, check_clicks)
@@ -216,6 +226,10 @@
if(target && !isturf(target))
Tloc = target.loc
+ if(target)
+ LAZYADD(user.do_afters, target)
+ LAZYADD(target.targeted_by, user)
+
var/atom/Uloc = user.loc
var/drifting = 0
@@ -260,6 +274,10 @@
. = 0
break
+ if(target && !(target in user.do_afters))
+ . = 0
+ break
+
if(needhand)
//This might seem like an odd check, but you can still need a hand even when it's empty
//i.e the hand is used to pull some item/tool out of the construction
@@ -273,6 +291,10 @@
if (progress)
qdel(progbar)
+ if(!QDELETED(target))
+ LAZYREMOVE(user.do_afters, target)
+ LAZYREMOVE(target.targeted_by, user)
+
/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
. = 1
return
@@ -291,6 +313,8 @@
var/list/originalloc = list()
for(var/atom/target in targets)
originalloc[target] = target.loc
+ LAZYADD(user.do_afters, target)
+ LAZYADD(target.targeted_by, user)
var/holding = user.get_active_held_item()
var/datum/progressbar/progbar
@@ -321,3 +345,9 @@
break mainloop
if(progbar)
qdel(progbar)
+
+ for(var/thing in targets)
+ var/atom/target = thing
+ if(!QDELETED(target))
+ LAZYREMOVE(user.do_afters, target)
+ LAZYREMOVE(target.targeted_by, user)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5d29ef1598..e8e98aed78 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -70,6 +70,9 @@
/// A luminescence-shifted value of the last color calculated for chatmessage overlays
var/chat_color_darkened
+ ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
+ var/list/targeted_by
+
/atom/New(loc, ...)
//atom creation method that preloads variables at creation
if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
@@ -142,6 +145,11 @@
LAZYCLEARLIST(overlays)
LAZYCLEARLIST(priority_overlays)
+ for(var/i in targeted_by)
+ var/mob/M = i
+ LAZYREMOVE(M.do_afters, src)
+ targeted_by = null
+
QDEL_NULL(light)
return ..()
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index d335cfb171..cd36d66841 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -1276,7 +1276,20 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
if(!check_rights(R_ADMIN) || !check_rights(R_FUN))
return
- var/list/punishment_list = list(ADMIN_PUNISHMENT_PIE, ADMIN_PUNISHMENT_CUSTOM_PIE, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING, ADMIN_PUNISHMENT_ROD)
+ var/list/punishment_list = list(
+ ADMIN_PUNISHMENT_PIE,
+ ADMIN_PUNISHMENT_CUSTOM_PIE,
+ ADMIN_PUNISHMENT_FIREBALL,
+ ADMIN_PUNISHMENT_LIGHTNING,
+ ADMIN_PUNISHMENT_BRAINDAMAGE,
+ ADMIN_PUNISHMENT_BSA,
+ ADMIN_PUNISHMENT_GIB,
+ ADMIN_PUNISHMENT_SUPPLYPOD_QUICK,
+ ADMIN_PUNISHMENT_SUPPLYPOD,
+ ADMIN_PUNISHMENT_MAZING,
+ ADMIN_PUNISHMENT_ROD,
+ ADMIN_PUNISHMENT_SHOES
+ )
var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in punishment_list
@@ -1355,6 +1368,17 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
A.reagents.add_reagent(chosen_id, amount)
A.splat(target)
+ if(ADMIN_PUNISHMENT_SHOES)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
+ return
+ var/mob/living/carbon/C = target
+ var/obj/item/clothing/shoes/sick_kicks = C.shoes
+ if(!sick_kicks?.can_be_tied)
+ to_chat(usr,"[C] does not have knottable shoes!", confidential = TRUE)
+ return
+ sick_kicks.adjust_laces(SHOES_KNOTTED)
+
punish_log(target, punishment)
/client/proc/punish_log(var/whom, var/punishment)
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 7eb5fab312..d0ce472137 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -125,7 +125,6 @@
/obj/item/proc/negates_gravity()
return FALSE
-/**
* adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted
*
* In addition to setting the state, it will deal with getting rid of alerts if they exist, as well as registering and unregistering the stepping signals
@@ -162,16 +161,23 @@
* *
* * user: who is the person interacting with the shoes?
*/
-/obj/item/clothing/shoes/proc/handle_tying(mob/living/carbon/human/user)
+/obj/item/clothing/shoes/proc/handle_tying(mob/user)
///our_guy here is the wearer, if one exists (and he must exist, or we don't care)
var/mob/living/carbon/human/our_guy = loc
if(!istype(our_guy))
return
+ if(!in_range(user, our_guy))
+ to_chat(user, "You aren't close enough to interact with [src]'s laces!")
+ return
+
if(user == loc && tied != SHOES_TIED) // if they're our own shoes, go tie-wards
+ if(INTERACTING_WITH(user, our_guy))
+ to_chat(user, "You're already interacting with [src]!")
+ return
user.visible_message("[user] begins [tied ? "unknotting" : "tying"] the laces of [user.p_their()] [src.name].", "You begin [tied ? "unknotting" : "tying"] the laces of your [src.name]...")
- if(do_after(user, lace_time, needhand=TRUE, target=src))
+ if(do_after(user, lace_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy)))
to_chat(user, "You [tied ? "unknot" : "tie"] the laces of your [src.name].")
if(tied == SHOES_UNTIED)
adjust_laces(SHOES_TIED, user)
@@ -179,36 +185,43 @@
adjust_laces(SHOES_UNTIED, user)
else // if they're someone else's shoes, go knot-wards
- if(user.mobility_flags & MOBILITY_STAND)
+ var/mob/living/L = user
+ if(istype(L) && (L.mobility_flags & MOBILITY_STAND))
to_chat(user, "You must be on the floor to interact with [src]!")
return
if(tied == SHOES_KNOTTED)
to_chat(user, "The laces on [loc]'s [src.name] are already a hopelessly tangled mess!")
return
+ if(INTERACTING_WITH(user, our_guy))
+ to_chat(user, "You're already interacting with [src]!")
+ return
var/mod_time = lace_time
to_chat(user, "You quietly set to work [tied ? "untying" : "knotting"] [loc]'s [src.name]...")
if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this
mod_time *= 0.75
- if(do_after(user, mod_time, needhand=TRUE, target=src))
+ if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy)))
to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].")
if(tied == SHOES_UNTIED)
adjust_laces(SHOES_KNOTTED, user)
else
adjust_laces(SHOES_UNTIED, user)
else // if one of us moved
- user.visible_message("[our_guy] stamps on [user]'s hand, mid-shoelace [tied ? "knotting" : "untying"]!", "Ow! [our_guy] stamps on your hand!", user)
- to_chat(our_guy, "You stamp on [user]'s hand! What the- they were [tied ? "knotting" : "untying"] your shoelaces!")
+ user.visible_message("[our_guy] stamps on [user]'s hand, mid-shoelace [tied ? "knotting" : "untying"]!", "Ow! [our_guy] stamps on your hand!", list(our_guy))
+ to_chat(our_guy, "You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!")
user.emote("scream")
- var/obj/item/bodypart/ouchie = user.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- if(ouchie)
- ouchie.receive_damage(15)
- user.Paralyze(5)
+ if(istype(L))
+ var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ if(ouchie)
+ ouchie.receive_damage(brute = 10, stamina = 40)
+ L.Paralyze(10)
-/**
- * check_trip runs on each step to see if we fall over as a result of our lace status. Knotted laces are a guaranteed trip, while untied shoes are just a chance to stumble
- */
+///checking to make sure we're still on the person we're supposed to be, for lacing do_after's
+/obj/item/clothing/shoes/proc/still_shoed(mob/living/carbon/our_guy)
+ return (loc == our_guy)
+
+///check_trip runs on each step to see if we fall over as a result of our lace status. Knotted laces are a guaranteed trip, while untied shoes are just a chance to stumble
/obj/item/clothing/shoes/proc/check_trip()
var/mob/living/carbon/human/our_guy = loc
if(!istype(our_guy)) // are they REALLY /our guy/?
@@ -220,30 +233,35 @@
our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] knotted shoelaces and falls! What a klutz!", "You trip on your knotted shoelaces and fall over!")
SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now!
our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/knotted)
+
else if(tied == SHOES_UNTIED)
var/wiser = TRUE // did we stumble and realize our laces are undone?
switch(rand(1, 1000))
if(1) // .1% chance to trip and fall over (note these are per step while our laces are undone)
our_guy.Paralyze(5)
our_guy.Knockdown(10)
+ SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now!
our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] untied shoelaces and falls! What a klutz!", "You trip on your untied shoelaces and fall over!")
+
if(2 to 5) // .4% chance to stumble and lurch forward
our_guy.throw_at(get_step(our_guy, our_guy.dir), 3, 2)
to_chat(our_guy, "You stumble on your untied shoelaces and lurch forward!")
+
if(6 to 13) // .7% chance to stumble and fling what we're holding
var/have_anything = FALSE
for(var/obj/item/I in our_guy.held_items)
have_anything = TRUE
our_guy.accident(I)
to_chat(our_guy, "You trip on your shoelaces a bit[have_anything ? ", flinging what you were holding" : ""]!")
+
if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed)
to_chat(our_guy, "You stumble a bit on your untied shoelaces!")
- if(!our_guy.has_movespeed_modifier(MOVESPEED_ID_SHOVE))
- our_guy.add_movespeed_modifier(MOVESPEED_ID_SHOVE, multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH)
+ if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove))
+ our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove)
addtimer(CALLBACK(our_guy, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH)
+
if(26 to 1000)
wiser = FALSE
-
if(wiser)
SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "untied", /datum/mood_event/untied) // well we realized they're untied now!
our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied)
@@ -252,7 +270,7 @@
/obj/item/clothing/shoes/attack_hand(mob/living/carbon/human/user)
if(!istype(user))
return ..()
- if(loc == user && tied != SHOES_TIED)
+ if(loc == user && tied != SHOES_TIED && (user.mobility_flags & MOBILITY_USE))
handle_tying(user)
return
..()
@@ -260,10 +278,12 @@
/obj/item/clothing/shoes/attack_self(mob/user)
. = ..()
+ if(INTERACTING_WITH(user, src))
+ to_chat(user, "You're already interacting with [src]!")
+ return
+
to_chat(user, "You begin [tied ? "untying" : "tying"] the laces on [src]...")
- if(do_after(user, lace_time, needhand=TRUE, target=src))
+
+ if(do_after(user, lace_time, needhand=TRUE, target=src,extra_checks=CALLBACK(src, .proc/still_shoed, user)))
to_chat(user, "You [tied ? "untie" : "tie"] the laces on [src].")
- if(tied == SHOES_UNTIED)
- adjust_laces(SHOES_TIED, user)
- else
- adjust_laces(SHOES_UNTIED, user)
+ adjust_laces(tied ? SHOES_TIED : SHOES_UNTIED, user)
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 75f4193edc..3cb61f1240 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -296,7 +296,7 @@
if (!strip_silence)
to_chat(src, "You feel your [pocket_side] pocket being fumbled with!")
- if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY, null, FALSE)
+ if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY, null, FALSE))
// separate from first canusetopic
var/mob/living/user = usr
if(istype(user) && href_list["shoes"] && (user.mobility_flags & MOBILITY_USE)) // we need to be on the ground, so we'll be a bit looser
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index dbf7b7f350..de69afbe7e 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -156,3 +156,6 @@
var/typing_indicator_timerid
/// Current state of our typing indicator. Used for cut overlay, DO NOT RUNTIME ASSIGN OTHER THAN FROM SHOW/CLEAR. Used to absolutely ensure we do not get stuck overlays.
var/typing_indicator_current
+
+ ///For storing what do_after's someone has, in case we want to restrict them to only one of a certain do_after at a time
+ var/list/do_afters