Repository: Citadel-Station-13/Citadel-Station-13-RP
Pull request #7120: Riding touchup (offset fixed)
Author: login: MarsM0nd
Labels: name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
Created: 2025-04-29T08:52:59Z | Merged: 2025-05-09T03:57:14Z

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull request process. -->

## About The Pull Request

Gives people the option to be directed by someone buckled to them.
Offset gets handled nicer now, resize affects the riding offset, taurs actually have their center set to it, and not their tail end if 200%, or infront of them if 25% size.
The code to saddle up actual horses, shanks, and stormdrifter bulls is fixed, too, as it didn't switch the riding handler, which didn't get deleted, and such not switching to the controllable part.
A few typos got fixed, stumbled by typoing my own search for the 3 mobs for their var.
Small rendering tweak to potentially draw bags ontop of tails (required for saddlebags.)
Saddlebags now include the saddle option, and such makes it easier for one to hold onto

## Why It's Good For The Game

People do look more like they are carried by the person they are buckled to. The new option could allow some RP options, synth having a controller or being just directed by holding some part of the body, or maybe way to put the jousting compontent to use? The bucking gives an option to remove someone riding one, as one otherwise has no control over it. The saddlebag also even works as a saddle now, although that can be removed if the freeded up hand is deemed too strong.

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely observed by players or admins you should add a changelog. If your change does NOT meet this description, remove this section. Be sure to properly mark your PRs to prevent unnecessary GBP loss. You can read up on GBP and it's effects on PRs in the tgstation guides for contributors. Please note that maintainers freely reserve the right to remove and add tags should they deem it appropriate. You can attempt to finagle the system all you want, but it's best to shoot for clear communication right off the bat. -->

:cl:
fix: taurs actually get their center calculated correctly, noticeable if buckled sideways.
tweak: offsets get resized for riding.
fix: horse, shank, stomrdrifter bull saddeling
fix: taur bags actually get drawn over the taur body
add: ability to buck someone off your back
tweak: no riding hand needed if the taur is wearing a saddle.
/:cl:

<!-- Both :cl:'s are required for the changelog to work! You can put your name to the right of the first :cl: if you want to overwrite your GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the icon ingame) and delete the unneeded ones. Despite some of the tags, changelogs should generally represent how a player might be affected by the changes rather than a summary of the PR's contents. -->



Diff:
diff --git a/code/__DEFINES/mobs/rendering.dm b/code/__DEFINES/mobs/rendering.dm
index 41b27fa9ae6e..1318adfa827d 100644
--- a/code/__DEFINES/mobs/rendering.dm
+++ b/code/__DEFINES/mobs/rendering.dm
@@ -51,7 +51,8 @@
 #define HUMAN_LAYER_SPRITEACC_HAIR_FRONT (FLOAT_LAYER - 477)
 #define HUMAN_LAYER_SPRITEACC_FACEHAIR_FRONT (FLOAT_LAYER - 478)
 #define HUMAN_LAYER_SPRITEACC_WINGS_FRONT (FLOAT_LAYER - 479)
-#define HUMAN_LAYER_SPRITEACC_TAIL_FRONT (FLOAT_LAYER - 480)
+#define HUMAN_LAYER_SLOT_BACKPACK_ALT (FLOAT_LAYER - 480)
+#define HUMAN_LAYER_SPRITEACC_TAIL_FRONT (FLOAT_LAYER - 481)
 
 #define HUMAN_LAYER_SLOT_BACKPACK (FLOAT_LAYER - 500)
 #define HUMAN_LAYER_SLOT_SUITSTORE (FLOAT_LAYER - 525)
diff --git a/code/datums/components/riding/mob/human.dm b/code/datums/components/riding/mob/human.dm
index 55be88d074be..577e1116cbe4 100644
--- a/code/datums/components/riding/mob/human.dm
+++ b/code/datums/components/riding/mob/human.dm
@@ -11,6 +11,12 @@
 /datum/component/riding_filter/mob/human/check_mount_attempt(mob/M, buckle_flags, mob/user, semantic)
 	if(!ishuman(M))
 		return FALSE		// nah
+	var/mob/living/carbon/human/H = parent
+	var/saddle = H.item_by_slot_id(SLOT_ID_BACK)
+	if(saddle && istype(saddle, /obj/item/storage/backpack/saddlebag_common))
+		rider_offhands_needed_piggyback = FALSE
+	else
+		rider_offhands_needed_piggyback = TRUE
 	return ..()
 
 /datum/component/riding_filter/mob/human/rider_offhands_needed(mob/rider, semantic)
@@ -39,16 +45,35 @@
 		list(8, 6, -1, null)
 	)
 	rider_offset_format = CF_RIDING_OFFSETS_DIRECTIONAL
+	var/taur_handling = FALSE
+
+/datum/component/riding_handler/mob/human/Initialize()
+	. = ..()
+	if(. == COMPONENT_INCOMPATIBLE)
+		return
+	var/mob/living/carbon/human/H = parent
+	taur_handling = isTaurTail(H.tail_style)
+
 
 /datum/component/riding_handler/mob/human/rider_offsets(mob/rider, pos, semantic, list/default, dir)
+	. = ..()
+	if(taur_handling)//Already centering on it
+		.[1] = 0
+		.[2] = 3 //Also don't climb too high
 	if(semantic == BUCKLE_SEMANTIC_HUMAN_FIREMAN)
-		. = default.Copy()
 		.[1] = -2
 		.[2] = 6
 		switch(dir)
 			if(NORTH)
 				.[3] = -1
-			else
+			if(EAST)
+				if(taur_handling)
+					.[1] = 6 //8 to remove the taur offset, the -2 of the fireman carry
+			if(SOUTH)
 				.[3] = 1
-	else
-		return ..()
+			if(WEST)
+				if(taur_handling)
+					.[1] = -10
+
+/datum/component/riding_handler/mob/human/controllable
+	riding_handler_flags = CF_RIDING_HANDLER_EPHEMERAL|CF_RIDING_HANDLER_IS_CONTROLLABLE
diff --git a/code/datums/components/riding/mob/robot.dm b/code/datums/components/riding/mob/robot.dm
index ac153e51beb9..6a8da00a7dff 100644
--- a/code/datums/components/riding/mob/robot.dm
+++ b/code/datums/components/riding/mob/robot.dm
@@ -20,3 +20,28 @@
 		list(8, 6, 1, null)
 	)
 	rider_offset_format = CF_RIDING_OFFSETS_DIRECTIONAL
+
+/datum/component/riding_handler/mob/robot/controllable
+	riding_handler_flags = CF_RIDING_HANDLER_EPHEMERAL|CF_RIDING_HANDLER_IS_CONTROLLABLE
+
+/mob/living/silicon/robot/verb/toggle_rider_control()
+
+	set name = "Give Reins"
+	set desc = "Give or take the person riding on you control of your movement."
+	set category = VERB_CATEGORY_IC
+	var/datum/component/riding_filter/mob/robot/riding_filter = GetComponent(/datum/component/riding_filter/mob/robot)
+	if(!riding_filter)
+		to_chat(src, "<span class='warning'>Your form is incompatible with being ridden</warning>")
+		return
+	if(riding_filter.handler_typepath == /datum/component/riding_handler/mob/robot)
+		riding_filter.handler_typepath = /datum/component/riding_handler/mob/robot/controllable
+		to_chat(src, "<span class='notice'>You can now be controlled")
+	else
+		riding_filter.handler_typepath = /datum/component/riding_handler/mob/robot
+	var/datum/component/riding_handler/mob/robot/riding_handler = GetComponent(/datum/component/riding_handler/mob/robot)
+	to_chat(src, "<span class='notice'>You can no longer be controlled")
+	if(!riding_handler)
+		//No need to update the handler if it doesn't exist.
+		return
+	riding_handler.riding_handler_flags ^= CF_RIDING_HANDLER_IS_CONTROLLABLE
+
diff --git a/code/datums/components/riding/riding_handler.dm b/code/datums/components/riding/riding_handler.dm
index 0b260810dbbe..86a7e0917f1b 100644
--- a/code/datums/components/riding/riding_handler.dm
+++ b/code/datums/components/riding/riding_handler.dm
@@ -191,6 +191,14 @@
 	_last_dir = dir
 	var/ppx = AM.get_centering_pixel_x_offset(dir)
 	var/ppy = AM.get_centering_pixel_y_offset(dir)
+	var/resizeX = 1
+	var/resizeY = 1
+	if(isliving(AM))
+		var/mob/living/mounted = AM
+		resizeX *= mounted.size_multiplier * mounted.icon_scale_x
+		resizeY *= mounted.size_multiplier * mounted.icon_scale_y
+		ppx = 2*mounted.shift_pixel_x - ppx //Jank to negate the offset, while following the shift
+		ppy = 2*mounted.shift_pixel_y - ppy
 	var/list/offsets
 	var/i
 	var/mob/M
@@ -208,8 +216,8 @@
 					M.setDir(offsets[4])
 				M.reset_pixel_shifting()
 				M.set_base_layer(offsets[3] + AM.layer)
-				M.pixel_x = ppx + M.get_standard_pixel_x_offset() - M.get_centering_pixel_x_offset(dir) + offsets[1]
-				M.pixel_y = ppy + M.get_standard_pixel_y_offset() - M.get_centering_pixel_y_offset(dir) + offsets[2]
+				M.pixel_x = ppx + M.get_standard_pixel_x_offset() + M.get_centering_pixel_x_offset(dir) + resizeX * offsets[1]
+				M.pixel_y = ppy + M.get_standard_pixel_y_offset() + M.get_centering_pixel_y_offset(dir) + resizeY * offsets[2]
 		if(CF_RIDING_OFFSETS_DIRECTIONAL)
 			var/list/relevant
 			switch(dir)
@@ -230,8 +238,8 @@
 					M.setDir(offsets[4])
 				M.reset_pixel_shifting()
 				M.set_base_layer(offsets[3] + AM.layer)
-				M.pixel_x = ppx + M.get_standard_pixel_x_offset() - M.get_centering_pixel_x_offset(dir) + offsets[1]
-				M.pixel_y = ppy + M.get_standard_pixel_y_offset() - M.get_centering_pixel_y_offset(dir) + offsets[2]
+				M.pixel_x = ppx + M.get_standard_pixel_x_offset() + M.get_centering_pixel_x_offset(dir) + resizeX * offsets[1]
+				M.pixel_y = ppy + M.get_standard_pixel_y_offset() + M.get_centering_pixel_y_offset(dir) + resizeY * offsets[2]
 		if(CF_RIDING_OFFSETS_ENUMERATED)
 			var/list/relevant
 			var/rider_offsets_len = length(rider_offsets)
@@ -254,16 +262,17 @@
 					M.setDir(offsets[4])
 				M.reset_pixel_shifting()
 				M.set_base_layer(offsets[3] + AM.layer)
-				M.pixel_x = ppx + M.get_standard_pixel_x_offset() - M.get_centering_pixel_x_offset(dir) + offsets[1]
-				M.pixel_y = ppy + M.get_standard_pixel_y_offset() - M.get_centering_pixel_y_offset(dir) + offsets[2]
+				M.pixel_x = ppx + M.get_standard_pixel_x_offset() + M.get_centering_pixel_x_offset(dir) + resizeX * offsets[1]
+				M.pixel_y = ppy + M.get_standard_pixel_y_offset() + M.get_centering_pixel_y_offset(dir) + resizeY * offsets[2]
 
 /**
  * returns transformed rider offset list
  *
  * DO NOT CHANGE DEFAULT FOR THE LOVE OF GOD, COPY IT IF YOU ARE CHANGING IT!!
+ * You just need to call the parent to get it, even.
  */
 /datum/component/riding_handler/proc/rider_offsets(mob/rider, pos, semantic, list/default, dir)
-	return default
+	return default.Copy()
 
 /datum/component/riding_handler/proc/signal_hook_handle_relaymove(datum/source, mob/M, dir)
 	attempt_drive(M, dir)
@@ -475,5 +484,4 @@
 
 /datum/component/riding_handler/proc/process_spacemove()
 	var/atom/movable/AM = parent
-	// todo: process_spacemove() on atom movable instead of hasgrav.
-	return (riding_handler_flags & CF_RIDING_HANDLER_FORCED_SPACEMOVE) || AM.has_gravity()
+	return (riding_handler_flags & CF_RIDING_HANDLER_FORCED_SPACEMOVE) || AM.Process_Spacemove()
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 0ad96126f933..45fc979879e8 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -15,6 +15,7 @@
 	max_combined_volume = STORAGE_VOLUME_BACKPACK
 	var/flippable = 0
 	var/side = 0 //0 = right, 1 = left
+	var/hide_under_tail = 1
 	drop_sound = 'sound/items/drop/backpack.ogg'
 	pickup_sound = 'sound/items/pickup/backpack.ogg'
 
@@ -584,6 +585,7 @@
 	encumbrance = ITEM_ENCUMBRANCE_STORAGE_DUFFLEBAG
 	max_combined_volume = STORAGE_VOLUME_DUFFLEBAG //Saddlebags can hold more, like dufflebags
 	var/no_message = "You aren't the appropriate taur type to wear this!"
+	hide_under_tail = -1
 
 /obj/item/storage/backpack/saddlebag_common/can_equip(mob/M, slot, mob/user, flags)
 	. = ..()
diff --git a/code/modules/mob/inventory/inventory_slot.dm b/code/modules/mob/inventory/inventory_slot.dm
index a4afb326839e..2108ea1fc34f 100644
--- a/code/modules/mob/inventory/inventory_slot.dm
+++ b/code/modules/mob/inventory/inventory_slot.dm
@@ -255,9 +255,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
 	if(istype(equipped, /obj/item/clothing/shoes))
 		var/obj/item/clothing/shoes/S = equipped
 		index = (S.shoes_under_pants == 1)? 2 : 1
-	else if(istype(equipped, /obj/item/storage/belt))
-		var/obj/item/storage/belt/B = equipped
-		index = (B.show_above_suit == 1)? 2 : 1
+	else if(istype(equipped, /obj/item/storage/backpack))
+		var/obj/item/storage/backpack/B = equipped
+		index = (B.hide_under_tail == 1)? 1 : 2
+	else if(istype(equipped, /obj/item/clothing/shoes))
+		var/obj/item/clothing/shoes/S = equipped
+		index = (S.shoes_under_pants == 1)? 2 : 1
 	return render_layer[clamp(index, 1, length(render_layer))]
 
 /datum/inventory_slot/proc/handle_worn_fallback(bodytype, list/worn_data)
@@ -304,7 +307,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
 	render_fallback = list(
 		BODYTYPE_STRING_TESHARI = "_fallback_"
 	)
-	render_layer = HUMAN_LAYER_SLOT_BACKPACK
+	render_layer = list(HUMAN_LAYER_SLOT_BACKPACK, HUMAN_LAYER_SLOT_BACKPACK_ALT)
 
 /datum/inventory_slot/inventory/uniform
 	name = "uniform"
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index d03243aceeb4..89a7eb4ed6fe 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1624,11 +1624,9 @@
 //! Pixel Offsets
 /mob/living/carbon/human/get_centering_pixel_x_offset(dir)
 	. = ..()
-	// uh oh stinky
 	if(!isTaurTail(tail_style) || !(dir & (EAST|WEST)))
 		return
-	// groan
-	. += ((size_multiplier * icon_scale_x) - 1) * ((dir & EAST)? -16 : 16)
+	. += (size_multiplier * icon_scale_x) * ((dir & EAST)? 8 : -8)
 
 /mob/living/carbon/human/ClickOn(var/atom/A)
 	if(ab_handler?.process_click(src, A))
diff --git a/code/modules/mob/living/carbon/human/riding.dm b/code/modules/mob/living/carbon/human/riding.dm
index 85be5fa0122e..68fa0679f2dd 100644
--- a/code/modules/mob/living/carbon/human/riding.dm
+++ b/code/modules/mob/living/carbon/human/riding.dm
@@ -49,6 +49,40 @@
 		return FALSE
 	return ..()
 
+/mob/living/carbon/human/verb/toggle_rider_control()
+
+	set name = "Give Reins"
+	set desc = "Give or take the person riding on you control of your movement."
+	set category = VERB_CATEGORY_IC
+	var/datum/component/riding_filter/mob/human/riding_filter = GetComponent(/datum/component/riding_filter/mob/human)
+	if(!riding_filter)
+		to_chat(src, "<span class='warning'>Your form is incompatible with being ridden</warning>")
+		return
+	if(riding_filter.handler_typepath == /datum/component/riding_handler/mob/human)
+		riding_filter.handler_typepath = /datum/component/riding_handler/mob/human/controllable
+		to_chat(src, "<span class='notice'>You can now be controlled")
+	else
+		riding_filter.handler_typepath = /datum/component/riding_handler/mob/human
+	var/datum/component/riding_handler/mob/human/riding_handler = GetComponent(/datum/component/riding_handler/mob/human)
+	to_chat(src, "<span class='notice'>You can no longer be controlled")
+	if(!riding_handler)
+		//No need to update the handler if it doesn't exist.
+		return
+	riding_handler.riding_handler_flags ^= CF_RIDING_HANDLER_IS_CONTROLLABLE
+
+/mob/living/carbon/human/verb/buck_rider()
+
+	set name = "Buck Rider"
+	set desc = "Throw someone riding you off."
+	set category = VERB_CATEGORY_IC
+	for(var/mob/M in buckled_mobs)
+		if(buckled_mobs[M] == BUCKLE_SEMANTIC_HUMAN_PIGGYBACK)
+			M.visible_message(
+				SPAN_NOTICE("[src] bucks and throws [M] of their back."),
+				SPAN_WARNING("[src] bucks and throws you of their back!")
+				)
+			unbuckle_mob(M, BUCKLE_OP_FORCE, src, buckled_mobs[M])
+
 /mob/living/carbon/human/proc/carry_piggyback(mob/living/carbon/other, instant = FALSE, delay_mod = 1, loc_check = TRUE)
 	if(loc_check && !Adjacent(other))
 		return FALSE
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index f7e11c117a14..5dbdea95b4e8 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -715,7 +715,7 @@ default behaviour is:
 /mob/living/get_centering_pixel_y_offset(dir)
 	. = ..()
 	// since we're shifted up by transforms..
-	. -= ((size_multiplier * icon_scale_y) - 1) * 16
+	. -= (size_multiplier * icon_scale_y - 1) * 16
 
 /mob/living/get_managed_pixel_y()
 	. = ..()
diff --git a/code/modules/mob/living/simple_mob/subtypes/lavaland/gutshank.dm b/code/modules/mob/living/simple_mob/subtypes/lavaland/gutshank.dm
index e1c506702eaf..55284be422c8 100644
--- a/code/modules/mob/living/simple_mob/subtypes/lavaland/gutshank.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/lavaland/gutshank.dm
@@ -200,22 +200,26 @@
 	buckle_allowed = TRUE
 	buckle_flags = BUCKLING_NO_USER_BUCKLE_OTHER_TO_SELF|BUCKLING_GROUND_HOIST
 
-	var/rideable = 0
+	var/obj/item/saddle/saddled = null
 
 /mob/living/simple_mob/animal/shank/attackby(var/obj/item/O as obj, var/mob/user as mob)
 	. = ..()
-	if(istype(O, /obj/item/saddle/shank) && !rideable)
+	if(istype(O, /obj/item/saddle/shank) && !saddled)
 		to_chat(user, "<span class='danger'>You sling the [O] onto the [src]! It may now be ridden safely!</span>")
-		rideable = 1
-		AddComponent(/datum/component/riding_handler/shank)
-		qdel(O)
-	if(istype(O, /obj/item/tool/wirecutters) && rideable)
-		to_chat(user, "<span class='danger'>You nip the straps of the [O]! It falls off of the [src].</span>")
-		rideable = 0
-		DelComponent(/datum/component/riding_handler, /datum/component/riding_handler/shank)
+		saddled = O
+		var/datum/component/riding_filter/mob/animal/filter_component = LoadComponent(/datum/component/riding_filter/mob/animal)
+		filter_component.handler_typepath = /datum/component/riding_handler/shank
+		DelComponent(/datum/component/riding_handler) //Delete to let it recreate as required
+		saddled.forceMove(src)
+	if(O.is_wirecutter() && saddled)
+		to_chat(user, "<span class='danger'>You nip the straps of the [saddled]! It falls off of the [src].</span>")
+		var/datum/component/riding_filter/mob/animal/filter_component = LoadComponent(/datum/component/riding_filter/mob/animal)
+		filter_component.handler_typepath = initial(filter_component.handler_typepath)
+		DelComponent(/datum/component/riding_handler)
 		var/turf/T = get_turf(src)
-		new /obj/item/saddle/shank(T)
-	if(istype(O, /obj/item/pen/charcoal) && rideable)
+		saddled.forceMove(T)
+		saddled = null
+	if(istype(O, /obj/item/pen/charcoal) && saddled)
 		RenameMount()
 	update_icon()
 
@@ -268,7 +272,7 @@
 		M.take_blood_mixture(rand(20, 25))
 
 /mob/living/simple_mob/animal/shank/update_icon()
-	if(rideable)
+	if(saddled)
 		add_overlay("shank_saddled")
-	else if(!rideable)
+	else if(!saddled)
 		cut_overlays()
diff --git a/code/modules/mob/living/simple_mob/subtypes/lavaland/stormdrifter.dm b/code/modules/mob/living/simple_mob/subtypes/lavaland/stormdrifter.dm
index 7538a59ae6af..25417fa9b75e 100644
--- a/code/modules/mob/living/simple_mob/subtypes/lavaland/stormdrifter.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/lavaland/stormdrifter.dm
@@ -126,7 +126,7 @@
 	buckle_flags = BUCKLING_NO_USER_BUCKLE_OTHER_TO_SELF|BUCKLING_GROUND_HOIST
 
 	var/neutered = 0
-	var/rideable = 0
+	var/obj/item/saddle/saddled = null
 
 /datum/ai_holder/polaris/simple_mob/stormdrifter/bull
 	hostile = TRUE
@@ -139,35 +139,39 @@
 	can_flee = FALSE
 
 /mob/living/simple_mob/animal/stormdrifter/bull/attackby(var/obj/item/O as obj, var/mob/user as mob)
-	if(istype(O, /obj/item/tool/wirecutters) || is_sharp(O))
+	if(O.is_wirecutter() || is_sharp(O))
 		if(!neutered)
 			to_chat(user, "<span class='danger'>You amputate the [src]'s stingers! It may now be domesticated!</span>")
 			neutered = 1
 			legacy_melee_damage_lower = 5
 			legacy_melee_damage_upper = 10
 			ai_holder_type = /datum/ai_holder/polaris/simple_mob/stormdrifter/bull_neutered
+		else if(saddled)
+			to_chat(user, "<span class='danger'>You nip the straps of the [saddled]! It falls off of the [src].</span>")
+			var/datum/component/riding_filter/mob/animal/filter_component = LoadComponent(/datum/component/riding_filter/mob/animal)
+			filter_component.handler_typepath = initial(filter_component.handler_typepath)
+			DelComponent(/datum/component/riding_handler)
+			buckle_allowed = FALSE
+			var/turf/T = get_turf(src)
+			saddled.forceMove(T)
+			saddled = null
 		else
 			return ..()
 
-	if(istype(O, /obj/item/saddle/stormdrifter) && !rideable)
+	if(istype(O, /obj/item/saddle/stormdrifter) && !saddled)
 		if(!neutered)
 			to_chat(user, "<span class='danger'>You decide against approaching the [src] with the [O] while its stingers are intact!</span>")
 			return
 		else
 			to_chat(user, "<span class='danger'>You hang the [O] on the [src]! It may now be ridden safely!</span>")
-			rideable = 1
+			saddled = O
+			var/datum/component/riding_filter/mob/animal/filter_component = LoadComponent(/datum/component/riding_filter/mob/animal)
+			filter_component.handler_typepath = /datum/component/riding_handler/shank
 			buckle_allowed = TRUE
-			AddComponent(/datum/component/riding_handler/stormdrifter_bull)
-			qdel(O)
-
-	if(istype(O, /obj/item/tool/wirecutters || is_sharp(O)) && rideable)
-		to_chat(user, "<span class='danger'>You nip the straps of the [O]! It falls off of the [src].</span>")
-		rideable = 0
-		buckle_allowed = FALSE
-		DelComponent(/datum/component/riding_handler, /datum/component/riding_handler/stormdrifter_bull)
-		var/turf/T = get_turf(src)
-		new /obj/item/saddle/stormdrifter(T)
-	if(istype(O, /obj/item/pen/charcoal) && rideable)
+			DelComponent(/datum/component/riding_handler) //Delete to let it recreate as required
+			saddled.forceMove(src)
+
+	if(istype(O, /obj/item/pen/charcoal) && saddled)
 		RenameMount()
 	update_icon()
 
@@ -208,11 +212,11 @@
 /mob/living/simple_mob/animal/stormdrifter/bull/update_icon()
 	if(neutered)
 		icon_state = "bulldrifter_neutered"
-	if(rideable)
+	if(saddled)
 		icon = 'icons/mob/lavaland/lavaland_mobs32x64.dmi'
 		icon_state = "bulldrifter_riding"
 		add_overlay("bulldrifter_saddled")
-	else if(!rideable)
+	else if(!saddled)
 		icon = 'icons/mob/lavaland/lavaland_mobs.dmi'
 		icon_state = "bulldrifter_neutered"
 		cut_overlays()
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm b/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm
index 65e1f008a2bf..dc6669e6a9c8 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm
@@ -52,7 +52,7 @@
 	say_list_type = /datum/say_list/horse
 	ai_holder_type = /datum/ai_holder/polaris/simple_mob/retaliate
 
-	var/rideable = 0
+	var/obj/item/saddle/saddled = null
 
 /mob/living/simple_mob/horse/Initialize(mapload)
 	. = ..()
@@ -79,23 +79,27 @@
 	riding_handler_flags = CF_RIDING_HANDLER_IS_CONTROLLABLE
 
 /mob/living/simple_mob/horse/attackby(var/obj/item/O as obj, var/mob/user as mob)
-	if(istype(O, /obj/item/saddle/horse) && !rideable)
+	if(istype(O, /obj/item/saddle/horse) && !saddled)
 		to_chat(user, "<span class='danger'>You sling the [O] onto the [src]! It may now be ridden safely!</span>")
-		rideable = 1
-		AddComponent(/datum/component/riding_handler/horse)
-		qdel(O)
-	if(istype(O, /obj/item/tool/wirecutters) && rideable)
-		to_chat(user, "<span class='danger'>You nip the straps of the [O]! It falls off of the [src].</span>")
-		rideable = 0
-		DelComponent(/datum/component/riding_handler, /datum/component/riding_handler/horse)
+		saddled = O
+		var/datum/component/riding_filter/mob/animal/horse/filter_component = LoadComponent(/datum/component/riding_filter/mob/animal/horse)
+		filter_component.handler_typepath = /datum/component/riding_handler/horse
+		DelComponent(/datum/component/riding_handler) //Delete to let it recreate as required
+		saddled.forceMove(src)
+	if(O.is_wirecutter() && saddled)
+		to_chat(user, "<span class='danger'>You nip the straps of the [saddled]! It falls off of the [src].</span>")
+		var/datum/component/riding_filter/mob/animal/horse/filter_component = LoadComponent(/datum/component/riding_filter/mob/animal/horse)
+		filter_component.handler_typepath = initial(filter_component.handler_typepath)
+		DelComponent(/datum/component/riding_handler)
 		var/turf/T = get_turf(src)
-		new /obj/item/saddle/horse(T)
+		saddled.forceMove(T)
+		saddled = null
 	update_icon()
 
 /mob/living/simple_mob/horse/update_icon()
-	if(rideable)
+	if(saddled)
 		add_overlay("horse_saddled")
-	else if(!rideable)
+	else if(!saddled)
 		cut_overlays()
 
 /datum/say_list/horse
diff --git a/code/modules/vehicles/ridden/quad.dm b/code/modules/vehicles/ridden/quad.dm
index 90321e14f03d..3f0ed0cd97ec 100644
--- a/code/modules/vehicles/ridden/quad.dm
+++ b/code/modules/vehicles/ridden/quad.dm
@@ -1,6 +1,6 @@
 /obj/vehicle/ridden/quadbike
 	name = "electric all terrain vehicle"
-	desc = "A ridable electric ATV designed for all terrain. Except space."
+	desc = "A rideable electric ATV designed for all terrain. Except space."
 	icon = 'icons/obj/vehicles_64x64.dmi'
 	icon_state = "quad"
 	integrity = 200
diff --git a/code/modules/vehicles/ridden/three_wheel_carts.dm b/code/modules/vehicles/ridden/three_wheel_carts.dm
index a7bc9019fe47..bf9fb4031f11 100644
--- a/code/modules/vehicles/ridden/three_wheel_carts.dm
+++ b/code/modules/vehicles/ridden/three_wheel_carts.dm
@@ -38,7 +38,7 @@
 
 /obj/vehicle/ridden/cart/security
 	name = "Security Cart"
-	desc = "A ridable electric cart designed for personal transport."
+	desc = "A rideable electric cart designed for personal transport."
 	icon_state = "paddywagon"
 	key_type = /obj/item/key/security_cart
 
@@ -52,7 +52,7 @@
 
 /obj/vehicle/ridden/cart/medical
 	name = "Medical Cart"
-	desc = "A ridable electric cart designed for personal transport"
+	desc = "A rideable electric cart designed for personal transport"
 	icon_state = "medicalbus"
 	key_type = /obj/item/key/medical_cart
 
diff --git a/code/modules/vehicles_legacy/Securitrain_vr.dm b/code/modules/vehicles_legacy/Securitrain_vr.dm
index 7efc233c886f..740096703faa 100644
--- a/code/modules/vehicles_legacy/Securitrain_vr.dm
+++ b/code/modules/vehicles_legacy/Securitrain_vr.dm
@@ -4,7 +4,7 @@
 
 /obj/vehicle_old/train/security/engine
 	name = "Security Cart"
-	desc = "A ridable electric car designed for pulling trolleys as well as personal transport."
+	desc = "A rideable electric car designed for pulling trolleys as well as personal transport."
 	icon = 'icons/obj/vehicles.dmi'
 	icon_state = "paddywagon"
 	on = 0
diff --git a/code/modules/vehicles_legacy/cargo_train.dm b/code/modules/vehicles_legacy/cargo_train.dm
index fe4876567db4..6bfd12878f0a 100644
--- a/code/modules/vehicles_legacy/cargo_train.dm
+++ b/code/modules/vehicles_legacy/cargo_train.dm
@@ -1,7 +1,7 @@
 // TODO: port to modern vehicles. If you're in this file, STOP FUCKING WITH IT AND PORT IT OVER.
 /obj/vehicle_old/train/engine
 	name = "cargo train tug"
-	desc = "A ridable electric car designed for pulling cargo trolleys."
+	desc = "A rideable electric car designed for pulling cargo trolleys."
 	icon = 'icons/obj/vehicles.dmi'
 	icon_state = "cargo_engine"
 	on = 0
diff --git a/code/modules/vehicles_legacy/quad.dm b/code/modules/vehicles_legacy/quad.dm
index 3d027b5de952..e575534d0ba2 100644
--- a/code/modules/vehicles_legacy/quad.dm
+++ b/code/modules/vehicles_legacy/quad.dm
@@ -1,7 +1,7 @@
 // TODO: port to modern vehicles. If you're in this file, STOP FUCKING WITH IT AND PORT IT OVER.
 /obj/vehicle_old/train/engine/quadbike //It's a train engine, so it can tow trailers.
 	name = "electric all terrain vehicle"
-	desc = "A ridable electric ATV designed for all terrain. Except space."
+	desc = "A rideable electric ATV designed for all terrain. Except space."
 	icon = 'icons/obj/vehicles_64x64.dmi'
 	icon_state = "quad"
 	on = 0


Reviews:
user: login: TheLordME
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T15:04:50Z
user: login: MarsM0nd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T14:22:28Z
user: login: MarsM0nd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T14:16:27Z
user: login: TheLordME
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T20:11:37Z
user: login: silicons
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T03:56:33Z

Review comments:
diff_hunk: @@ -82,12 +82,16 @@
 	if(istype(O, /obj/item/saddle/horse) && !rideable)
 		to_chat(user, "<span class='danger'>You sling the [O] onto the [src]! It may now be ridden safely!</span>")
 		rideable = 1
-		AddComponent(/datum/component/riding_handler/horse)
+		var/datum/component/riding_filter/mob/animal/horse/filter_component = GetComponent(/datum/component/riding_filter/mob/animal/horse)
+		filter_component.handler_typepath = /datum/component/riding_handler/horse
+		DelComponent(/datum/component/riding_handler) //Delete to let it recreate as required
 		qdel(O)
-	if(istype(O, /obj/item/tool/wirecutters) && rideable)
+	if(O.is_wirecutter() && rideable)
 		to_chat(user, "<span class='danger'>You nip the straps of the [O]! It falls off of the [src].</span>")
path: code/modules/mob/living/simple_mob/subtypes/vore/horse.dm
user: login: TheLordME
body: [O] is the object used, not the saddle, since its hard coded to be a saddle you could just put a hard string in.
If you wanna go the extra mile, add a saddle var and and get the name from there, and move it to the floor later
created_at: 2025-04-30T15:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,12 +82,16 @@
 	if(istype(O, /obj/item/saddle/horse) && !rideable)
 		to_chat(user, "<span class='danger'>You sling the [O] onto the [src]! It may now be ridden safely!</span>")
 		rideable = 1
-		AddComponent(/datum/component/riding_handler/horse)
+		var/datum/component/riding_filter/mob/animal/horse/filter_component = GetComponent(/datum/component/riding_filter/mob/animal/horse)
+		filter_component.handler_typepath = /datum/component/riding_handler/horse
+		DelComponent(/datum/component/riding_handler) //Delete to let it recreate as required
 		qdel(O)
-	if(istype(O, /obj/item/tool/wirecutters) && rideable)
+	if(O.is_wirecutter() && rideable)
 		to_chat(user, "<span class='danger'>You nip the straps of the [O]! It falls off of the [src].</span>")
path: code/modules/mob/living/simple_mob/subtypes/vore/horse.dm
user: login: MarsM0nd
body: And [O] is tested to be a wirecutter, to remove the saddle that's there. Although searching more for it, probably could refactor rideable to be a simple mob var that stores the saddle, rather than a flag that's set on 3 mobs for that.
created_at: 2025-05-01T14:22:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,12 +82,16 @@
 	if(istype(O, /obj/item/saddle/horse) && !rideable)
 		to_chat(user, "<span class='danger'>You sling the [O] onto the [src]! It may now be ridden safely!</span>")
 		rideable = 1
-		AddComponent(/datum/component/riding_handler/horse)
+		var/datum/component/riding_filter/mob/animal/horse/filter_component = GetComponent(/datum/component/riding_filter/mob/animal/horse)
+		filter_component.handler_typepath = /datum/component/riding_handler/horse
+		DelComponent(/datum/component/riding_handler) //Delete to let it recreate as required
 		qdel(O)
-	if(istype(O, /obj/item/tool/wirecutters) && rideable)
+	if(O.is_wirecutter() && rideable)
 		to_chat(user, "<span class='danger'>You nip the straps of the [O]! It falls off of the [src].</span>")
path: code/modules/mob/living/simple_mob/subtypes/vore/horse.dm
user: login: MarsM0nd
body: Misunderstood it, but fixed now, not a full refactor as it's 3 mobs that are handled like that.
created_at: 2025-05-02T14:16:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: MarsM0nd
created_at: 2025-05-01T14:23:17Z
body: Converted to draft to figure out a way that doesn't break normal buckling (had only tested between mobs)
author_association: CONTRIBUTOR
user: login: MarsM0nd
created_at: 2025-05-02T07:59:57Z
body: Should be testmergable again, offsets seem okay with this, need to still updated the rideable mobs, and update the description with more changes done
author_association: CONTRIBUTOR
user: login: MarsM0nd
created_at: 2025-05-06T18:12:13Z
body: Borg riding changes with levels, one has to retoggle riding, I intent to figure a way out for that.
author_association: CONTRIBUTOR