diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index db3e273c46293..bde20b4ddb8b8 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -1,11 +1,15 @@
/atom/movable
+ /// Whether the atom allows mobs to be buckled to it. Can be ignored in [/atom/movable/proc/buckle_mob()] if force = TRUE
var/can_buckle = FALSE
/// Bed-like behaviour, forces mob.lying = buckle_lying if not set to [NO_BUCKLE_LYING].
var/buckle_lying = NO_BUCKLE_LYING
/// Require people to be handcuffed before being able to buckle. eg: pipes
var/buckle_requires_restraints = FALSE
+ // The mobs currently buckled to this atom
var/list/mob/living/buckled_mobs = null //list()
+ /// The maximum number of mob/livings allowed to be buckled to this atom at once
var/max_buckled_mobs = 1
+ /// Whether things buckled to this atom can be pulled while they're buckled
var/buckle_prevents_pull = FALSE
var/can_be_unanchored = FALSE
@@ -15,7 +19,7 @@
if(.)
return
if(can_buckle && has_buckled_mobs())
- if(buckled_mobs.len > 1)
+ if(length(buckled_mobs) > 1)
var/mob/living/unbuckled = tgui_input_list(user, "Who do you wish to unbuckle?", "Unbuckle", sort_names(buckled_mobs))
if(isnull(unbuckled))
return
@@ -25,11 +29,11 @@
if(user_unbuckle_mob(buckled_mobs[1],user))
return TRUE
-/atom/movable/attackby(obj/item/W, mob/user, params)
- if(!can_buckle || !istype(W, /obj/item/riding_offhand) || !user.Adjacent(src))
+/atom/movable/attackby(obj/item/attacking_item, mob/user, params)
+ if(!can_buckle || !istype(attacking_item, /obj/item/riding_offhand) || !user.Adjacent(src))
return ..()
- var/obj/item/riding_offhand/riding_item = W
+ var/obj/item/riding_offhand/riding_item = attacking_item
var/mob/living/carried_mob = riding_item.rider
if(carried_mob == user) //Piggyback user.
return
@@ -43,32 +47,47 @@
if(.)
return
if(Adjacent(user) && can_buckle && has_buckled_mobs())
- if(buckled_mobs.len > 1)
+ if(length(buckled_mobs) > 1)
var/mob/living/unbuckled = tgui_input_list(user, "Who do you wish to unbuckle?", "Unbuckle", sort_names(buckled_mobs))
if(isnull(unbuckled))
return
- if(user_unbuckle_mob(unbuckled,user))
- return TRUE
+ return user_unbuckle_mob(unbuckled,user)
else
- if(user_unbuckle_mob(buckled_mobs[1],user))
- return TRUE
+ return user_unbuckle_mob(buckled_mobs[1], user)
/atom/movable/MouseDrop_T(mob/living/M, mob/living/user)
. = ..()
return mouse_buckle_handling(M, user)
+/**
+ * Does some typechecks and then calls user_buckle_mob
+ *
+ * Arguments:
+ * M - The mob being buckled to src
+ * user - The mob buckling M to src
+ */
/atom/movable/proc/mouse_buckle_handling(mob/living/M, mob/living/user)
if(can_buckle && istype(M) && istype(user))
- if(user_buckle_mob(M, user, check_loc = FALSE))
- return TRUE
+ return user_buckle_mob(M, user)
+/**
+ * Returns TRUE if there are mobs buckled to this atom and FALSE otherwise
+ */
/atom/movable/proc/has_buckled_mobs()
if(!buckled_mobs)
return FALSE
if(buckled_mobs.len)
return TRUE
-//procs that handle the actual buckling and unbuckling
+/**
+ * Set a mob as buckled to src
+ *
+ * If you want to have a mob buckling another mob to something, or you want a chat message sent, use user_buckle_mob instead.
+ * Arguments:
+ * M - The mob to be buckled to src
+ * force - Set to TRUE to ignore src's can_buckle and M's can_buckle_to
+ * check_loc - Set to FALSE to allow buckling from adjacent turfs, or TRUE if buckling is only allowed with src and M on the same turf.
+ */
/atom/movable/proc/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
if(!buckled_mobs)
buckled_mobs = list()
@@ -76,15 +95,6 @@
if(!is_buckle_possible(M, force, check_loc))
return FALSE
- M.buckling = src
- if(!M.can_buckle() && !force)
- if(M == usr)
- to_chat(M, "You are unable to buckle yourself to [src]!")
- else
- to_chat(usr, "You are unable to buckle [M] to [src]!")
- M.buckling = null
- return FALSE
-
if(M.pulledby)
if(buckle_prevents_pull)
M.pulledby.stop_pulling()
@@ -98,7 +108,6 @@
if (!check_loc && M.loc != loc)
M.forceMove(loc)
- M.buckling = null
M.set_buckled(src)
M.setDir(dir)
buckled_mobs |= M
@@ -123,13 +132,20 @@
M.adjust_fire_stacks(1)
M.IgniteMob()
-
+/**
+ * Set a mob as unbuckled from src
+ *
+ * The mob must actually be buckled to src or else bad things will happen.
+ * Arguments:
+ * buckled_mob - The mob to be unbuckled
+ * force - TRUE if we should ignore buckled_mob.can_buckle_to
+ */
/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force = FALSE)
if(!isliving(buckled_mob))
CRASH("Non-living [buckled_mob] thing called unbuckle_mob() for source.")
if(buckled_mob.buckled != src)
CRASH("[buckled_mob] called unbuckle_mob() for source while having buckled as [buckled_mob.buckled].")
- if(!force && !buckled_mob.can_unbuckle())
+ if(!force && !buckled_mob.can_buckle_to)
return
. = buckled_mob
buckled_mob.set_buckled(null)
@@ -160,9 +176,11 @@
* Simple helper proc that runs a suite of checks to test whether it is possible or not to buckle the target mob to src.
*
* Returns FALSE if any conditions that should prevent buckling are satisfied. Returns TRUE otherwise.
+ * Called from [/atom/movable/proc/buckle_mob] and [/atom/movable/proc/is_user_buckle_possible].
* Arguments:
* * target - Target mob to check against buckling to src.
- * * force - Whether or not the buckle should be forced. If TRUE, ignores src's can_buckle var.
+ * * force - Whether or not the buckle should be forced. If TRUE, ignores src's can_buckle var and target's can_buckle_to
+ * * check_loc - TRUE if target and src have to be on the same tile, FALSE if they are allowed to just be adjacent
* * check_loc - Whether to do a proximity check or not. The proximity check looks for target.loc == src.loc.
*/
/atom/movable/proc/is_buckle_possible(mob/living/target, force = FALSE, check_loc = TRUE)
@@ -178,10 +196,6 @@
if(!can_buckle && !force)
return FALSE
- // Check if this atom can buckle, proc wise.
- if(!target.can_buckle() && !force)
- return FALSE
-
// If we're checking the loc, make sure the target is on the thing we're bucking them to.
if(check_loc && target.loc != loc)
return FALSE
@@ -198,16 +212,21 @@
if(buckle_requires_restraints && !HAS_TRAIT(target, TRAIT_RESTRAINED))
return FALSE
+ //If buckling is forbidden for the target, cancel
+ if(!target.can_buckle_to && !force)
+ return FALSE
+
return TRUE
/**
* Simple helper proc that runs a suite of checks to test whether it is possible or not for user to buckle target mob to src.
*
* Returns FALSE if any conditions that should prevent buckling are satisfied. Returns TRUE otherwise.
+ * Called from [/atom/movable/proc/user_buckle_mob].
* Arguments:
* * target - Target mob to check against buckling to src.
* * user - The mob who is attempting to buckle the target to src.
- * * check_loc - Whether to do a proximity check or not when calling is_buckle_possible().
+ * * check_loc - TRUE if target and src have to be on the same tile, FALSE if buckling is allowed from adjacent tiles
*/
/atom/movable/proc/is_user_buckle_possible(mob/living/target, mob/user, check_loc = TRUE)
// Standard adjacency and other checks.
@@ -218,15 +237,20 @@
if(!is_buckle_possible(target, FALSE, check_loc))
return FALSE
- // If the person attempting to buckle is stood on this atom's turf and they're not buckling themselves,
- // buckling shouldn't be possible as they're blocking it.
- if((target != user) && (get_turf(user) == get_turf(src)))
- to_chat(target, "You are unable to buckle [target] to [src] while it is blocked!")
- return FALSE
-
return TRUE
-//Wrapper procs that handle sanity and user feedback
+/**
+ * Handles a mob buckling another mob to src and sends a visible_message
+ *
+ * Basically exists to do some checks on the user and then call buckle_mob where the real buckling happens.
+ * First, checks if the buckle is valid and cancels if it isn't.
+ * Second, checks if src is on a different turf from the target; if it is, does a do_after and another check for sanity
+ * Finally, calls [/atom/movable/proc/buckle_mob] to buckle the target to src then gives chat feedback
+ * Arguments:
+ * * M - The target mob/living being buckled to src
+ * * user - The other mob that's buckling M to src
+ * * check_loc - TRUE if src and M have to be on the same turf, false otherwise
+ */
/atom/movable/proc/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
// Is buckling even possible? Do a full suite of checks.
if(!is_user_buckle_possible(M, user, check_loc))
@@ -254,12 +278,21 @@
M.visible_message(\
"[M] buckles [M.p_them()]self to [src].",\
"You buckle yourself to [src].",\
- "You hear metal clanking.")
+ "You hear metal clanking.")
else
M.visible_message("[user] buckles [M] to [src]!",\
"[user] buckles you to [src]!",\
"You hear metal clanking.")
+/**
+ * Handles a user unbuckling a mob from src and sends a visible_message
+ *
+ * Basically just calls unbuckle_mob, sets fingerprint, and sends a visible_message
+ * about the user unbuckling the mob
+ * Arguments:
+ * buckled_mob - The mob/living to unbuckle
+ * user - The mob unbuckling buckled_mob
+ */
/atom/movable/proc/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
var/mob/living/M = unbuckle_mob(buckled_mob)
if(M)
@@ -267,12 +300,12 @@
M.visible_message(\
"[user] unbuckles [M] from [src].",\
"[user] unbuckles you from [src].",\
- "You hear metal clanking.")
+ "You hear metal clanking.")
else
M.visible_message(\
"[M] unbuckles [M.p_them()]self from [src].",\
"You unbuckle yourself from [src].",\
- "You hear metal clanking.")
+ "You hear metal clanking.")
add_fingerprint(user)
if(isliving(M.pulledby))
var/mob/living/L = M.pulledby
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index 78965daaba484..edcc7d6ec0560 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -127,9 +127,7 @@
var/mob/living/L = thing
if(L.movement_type & FLYING)
continue //YOU'RE FLYING OVER IT
- var/buckle_check = L.buckling
- if(!buckle_check)
- buckle_check = L.buckled
+ var/buckle_check = L.buckled
if(isobj(buckle_check))
var/obj/O = buckle_check
if(O.resistance_flags & LAVA_PROOF)
diff --git a/code/modules/awaymissions/mission_code/snowdin.dm b/code/modules/awaymissions/mission_code/snowdin.dm
index 065eb1c286158..0ff32fdc3e236 100644
--- a/code/modules/awaymissions/mission_code/snowdin.dm
+++ b/code/modules/awaymissions/mission_code/snowdin.dm
@@ -187,9 +187,7 @@
if("snow" in L.weather_immunities)
continue
- var/buckle_check = L.buckling
- if(!buckle_check)
- buckle_check = L.buckled
+ var/buckle_check = L.buckled
if(isobj(buckle_check))
var/obj/O = buckle_check
if(O.resistance_flags & FREEZE_PROOF)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 82ca66a8cb04d..4f8d8ccc3c804 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -218,7 +218,7 @@
if(HAS_TRAIT(src, TRAIT_RESTRAINED))
changeNext_move(CLICK_CD_BREAKOUT)
last_special = world.time + CLICK_CD_BREAKOUT
- var/buckle_cd = 600
+ var/buckle_cd = 60 SECONDS
if(handcuffed)
var/obj/item/restraints/O = src.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
buckle_cd = O.breakouttime
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 5cbc07f08120c..b8c67e9328cfe 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -157,6 +157,9 @@
var/icon/head_icon = 'icons/mob/pets_held.dmi'//what it looks like on your head
var/held_state = ""//icon state for the above
+ /// Is this mob allowed to be buckled/unbuckled to/from things?
+ var/can_buckle_to = TRUE
+
//is mob player controllable
var/playable = FALSE
var/flavor_text = FLAVOR_TEXT_NONE
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 710b0f1cdea03..5b2a65d116483 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -29,6 +29,7 @@
d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED
mob_size = MOB_SIZE_LARGE
radio = /obj/item/radio/headset/silicon/ai
+ can_buckle_to = FALSE
var/battery = 200 //emergency power if the AI's APC is off
var/list/network = list("ss13")
var/list/connected_robots = list()
@@ -790,8 +791,6 @@
to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.")
to_chat(user, "Transfer successful: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.")
-/mob/living/silicon/ai/can_buckle()
- return 0
/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE)
if(control_disabled || incapacitated())
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 5814bbcc9d661..776db4f3d618a 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -14,6 +14,7 @@
layer = BELOW_MOB_LAYER
can_be_held = TRUE
radio = /obj/item/radio/headset/silicon/pai
+ can_buckle_to = FALSE
move_force = 0
pull_force = 0
move_resist = 0
@@ -108,12 +109,6 @@
var/atom/movable/screen/ai/modpc/interface_button
-/mob/living/silicon/pai/can_unbuckle()
- return FALSE
-
-/mob/living/silicon/pai/can_buckle()
- return FALSE
-
/mob/living/silicon/pai/handle_atom_del(atom/A)
if(A == hacking_cable)
hacking_cable = null
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 676b9b4add05c..5f2e5e36e95b8 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1229,9 +1229,9 @@
cell = null
/mob/living/silicon/robot/mouse_buckle_handling(mob/living/M, mob/living/user)
+ //Don't try buckling on INTENT_HARM so that silicons can search people's inventories without loading them
if(can_buckle && istype(M) && !(M in buckled_mobs) && ((user!=src)||(a_intent != INTENT_HARM)))
- if(buckle_mob(M))
- return TRUE
+ return user_buckle_mob(M, user, check_loc = FALSE)
/mob/living/silicon/robot/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
if(!is_type_in_typecache(M, can_ride_typecache))
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index 1573e7404b8c6..628b9a7163c7c 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -510,12 +510,6 @@
/mob/living/simple_animal/slime/pet
docile = 1
-/mob/living/simple_animal/slime/can_unbuckle()
- return 0
-
-/mob/living/simple_animal/slime/can_buckle()
- return 0
-
/mob/living/simple_animal/slime/get_mob_buckling_height(mob/seat)
if(..())
return 3
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 0350d6ee576a1..2fcac5d963f08 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -914,7 +914,7 @@
return src
/**
- * Buckle to another mob
+ * Buckle a living mob to this mob
*
* You can buckle on mobs if you're next to them since most are dense
*
@@ -952,14 +952,6 @@
return 0
return 9
-///can the mob be buckled to something by default?
-/mob/proc/can_buckle()
- return TRUE
-
-///can the mob be unbuckled from something by default?
-/mob/proc/can_unbuckle()
- return 1
-
///Can the mob interact() with an atom?
/mob/proc/can_interact_with(atom/A, treat_mob_as_adjacent)
if(IsAdminGhost(src))
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 0a39af6912142..97f9b47330cce 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -122,8 +122,6 @@
/// The atom that this mob is currently buckled to
var/atom/movable/buckled = null//Living
- /// The movable atom that we are currently in the process of buckling to, but haven't buckled with yet.
- var/atom/movable/buckling
//Hands
///What hand is the active hand
diff --git a/code/modules/surgery/organs/wings.dm b/code/modules/surgery/organs/wings.dm
index a7019ce1f0e23..6ce219b437b40 100644
--- a/code/modules/surgery/organs/wings.dm
+++ b/code/modules/surgery/organs/wings.dm
@@ -186,7 +186,7 @@
var/obj/item/organ/wings/bee/wings = locate(/obj/item/organ/wings/bee) in L.internal_organs
var/jumpdistance = wings.jumpdist
- if(L.stat != CONSCIOUS || L.buckling) // Has to be conscious and unbuckled
+ if(L.stat != CONSCIOUS || L.buckled) // Has to be conscious and unbuckled
return
if(recharging_time > world.time)
to_chat(L, "The wings aren't ready to dash yet!")