Saddles, saddlebags, calvary (#2291)

<!-- 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

Port of https://github.com/NovaSector/NovaSector/pull/3112, which is a
continuation of a work I stopped development on

Adds saddles, saddlebags, both of which can only be worn by quadruped
taurs in the backslot

Saddles allow taurs to be piggybacked with the rider having both hands
free, saddlebags allow riders to have ONE hand free, have sueprior
storage, but you can altclick the wearer to access the storage

Saddles can be made from leather, saddles are avaialble in the secvend,
and both saddles and saddlebags are in loadout

Part of a taur rework thingy magig I originally made for nova
Main design doc: https://hackmd.io/@NikoTheGuyDUde/rJ7C9HPe0
Quadruped design doc: https://hackmd.io/@NikoTheGuyDUde/rJzJiBPgC

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

<!-- Please make sure to actually test your PRs. If you have not tested
your PR mention it. -->

## Why It's Good For The Game

Read the main design doc. In short, taurs have very exotic anatomy that
isnt explored much, and the game would be greatly diversified if we
added some mechanical differences.

<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Proof Of Testing

<!-- Compile and run your code locally. Make sure it works. This is the
place to show off your changes! We are not responsible for testing your
features. -->
<details>
<summary>Screenshots/Videos</summary>


https://github.com/user-attachments/assets/c3a48127-0c54-4b62-a450-e14e210d1f56

</details>

## 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 its 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. -->

🆑
add: Quadruped taurs can now wear saddles, which allow anyone to
clickdrag themselves onto them to ride with free hands
add: Saddlebags for quadruped taurs, which are one-handed saddles that
allow anyone to access their storage by alt clicking the wearer
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 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. -->

<!-- By opening a pull request. You have read and understood the
repository rules located on the main README.md on this project. -->

Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com>
Co-authored-by: The Sharkening <95130227+StrangeWeirdKitten@users.noreply.github.com>
This commit is contained in:
nikothedude
2024-12-22 02:38:07 -05:00
committed by GitHub
parent 34a040e35d
commit 0a02a3aba4
18 changed files with 520 additions and 15 deletions

View File

@@ -57,12 +57,12 @@
#define HEADSMASH_BLOCK_ARMOR 20
#define SUPLEX_TIMER 3 SECONDS
// alt-clicking a human as another human while grappling them tightly makes you try for grappling-based maneuvers.
/// alt-clicking a human as another human while grappling them tightly makes you try for grappling-based maneuvers.
/mob/living/carbon/human/click_alt(mob/user)
if(!ishuman(user))
return ..()
var/mob/living/carbon/human/human_user = user
if(human_user == src || !human_user.combat_mode || !human_user.dna.species.try_grab_maneuver(user, src))
if(human_user != src && human_user.combat_mode && !human_user.dna.species.try_grab_maneuver(user, src))
return CLICK_ACTION_BLOCKING
/// State check for grab maneuver - because you can't logically suplex a man if you've stopped grappling them.

View File

@@ -0,0 +1,4 @@
/// From base of /obj/item/mob_can_equip. (mob/living/M, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action)
#define COMSIG_ITEM_MOB_CAN_EQUIP "item_mob_can_equip"
/// Forces mob_can_equip to return FALSE.
#define COMPONENT_ITEM_CANT_EQUIP (1<<10) // high to avoid flag conflict

View File

@@ -0,0 +1,21 @@
// Defines for the vehicle component
/// For use in ride_check_flags. Prevents the piggyback slowdown, causes the riding offsets to be applied.
#define RIDING_TAUR (1<<10) // high, to avoid flag conflict with tg)
// Vehicle offset defines
/// Applied when the ridee is oversized. Applied to front offsets.
#define OVERSIZED_OFFSET 18
/// Applied when the ridee is oversized. Applied to side offsets.
#define OVERSIZED_SIDE_OFFSET 11
/// Applied when the ridee is normal sized. Applies to front offsets.
#define REGULAR_OFFSET 6
/// Applied when the ridee is normal sized. Applies to side offsets.
#define REGULAR_SIDE_OFFSET 4
/// Sent when a mob attempts to ride our saddle. Should return a bitfield containing riding flags, ex. RIDER_NEEDS_ARMS (mob/living/carbon)
#define COMSIG_HUMAN_SADDLE_RIDE_ATTEMPT "human_saddle_ride_attempt"
/// If true, the saddled mob can have someone clickdragged onto them to be ridden.
#define TRAIT_SADDLED "trait_saddled"

View File

@@ -1,11 +1,5 @@
// For any mob that can be ridden
//SKYRAT EDIT START: Human Riding Defines
#define OVERSIZED_OFFSET 18
#define OVERSIZED_SIDE_OFFSET 11
#define REGULAR_OFFSET 6
#define REGULAR_SIDE_OFFSET 4
//SKYRAT EDIT END
/datum/component/riding/creature
/// If TRUE, this creature's movements can be controlled by the rider while mounted (as opposed to riding cyborgs and humans, which is passive)
var/can_be_driven = TRUE
@@ -214,9 +208,13 @@
/datum/component/riding/creature/human/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = NONE, potion_boost = FALSE)
. = ..()
var/mob/living/carbon/human/human_parent = parent
human_parent.add_movespeed_modifier(/datum/movespeed_modifier/human_carry)
//human_parent.add_movespeed_modifier(/datum/movespeed_modifier/human_carry) // BUBBER EDIT REMOVAL
// BUBBER EDIT ADDITION START - Taur saddles
if (!(ride_check_flags & RIDING_TAUR))
human_parent.add_movespeed_modifier(/datum/movespeed_modifier/human_carry)
// BUBBER EDIT ADDITION END
if(ride_check_flags & RIDER_NEEDS_ARMS) // piggyback
if(ride_check_flags & RIDER_NEEDS_ARMS || (ride_check_flags & RIDING_TAUR)) // BUBBER CHANGE - ORIGINAL: if(ride_check_flags & RIDER_NEEDS_ARMS) // piggyback
human_parent.buckle_lying = 0
// the riding mob is made nondense so they don't bump into any dense atoms the carrier is pulling,
// since pulled movables are moved before buckled movables
@@ -299,7 +297,13 @@
/datum/component/riding/creature/human/get_offsets(pass_index)
var/mob/living/carbon/human/H = parent
//SKYRAT EDIT BEGIN - Oversized Overhaul
/* BUBBER EDIT REMOVAL START
if(H.buckle_lying)
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6))
else
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))
*/ // BUBBER EDIT REMOVAL END
// BUBBER EDIT ADDITION BEGIN - Oversized Overhaul, Taur riding
if(H.buckle_lying)
return HAS_TRAIT(H, TRAIT_OVERSIZED) ? list(
TEXT_NORTH = list(0, OVERSIZED_OFFSET),
@@ -312,7 +316,7 @@
TEXT_EAST = list(0, REGULAR_OFFSET),
TEXT_WEST = list(0, REGULAR_OFFSET),
)
else
else if (!(ride_check_flags & RIDING_TAUR)) // BUBBER EDIT CHANGE - ORIGINAL: else
return HAS_TRAIT(H, TRAIT_OVERSIZED) ? list(
TEXT_NORTH = list(0, OVERSIZED_OFFSET),
TEXT_SOUTH = list(0, OVERSIZED_OFFSET),
@@ -324,7 +328,12 @@
TEXT_EAST = list(-REGULAR_OFFSET, REGULAR_SIDE_OFFSET),
TEXT_WEST = list(REGULAR_OFFSET, REGULAR_SIDE_OFFSET)
)
//SKYRAT EDIT END
if (ride_check_flags & RIDING_TAUR)
var/obj/item/organ/external/taur_body/taur_body = H.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAUR)
return taur_body.get_riding_offset(oversized = HAS_TRAIT(H, TRAIT_OVERSIZED))
//BUBBER EDIT END
/datum/component/riding/creature/human/force_dismount(mob/living/dismounted_rider)
var/atom/movable/AM = parent
AM.unbuckle_mob(dismounted_rider)

View File

@@ -901,12 +901,12 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
return open_storage_on_signal(source, user) ? CLICK_ACTION_SUCCESS : NONE
/// Opens the storage to the mob, showing them the contents to their UI.
/datum/storage/proc/open_storage(mob/to_show)
/datum/storage/proc/open_storage(mob/to_show, can_reach_target = parent) // BUBBER EDIT CHANGE - ORIGINAL: /datum/storage/proc/open_storage(mob/to_show)
if(isobserver(to_show))
show_contents(to_show)
return FALSE
if(!isliving(to_show) || !to_show.can_perform_action(parent, ALLOW_RESTING | FORBID_TELEKINESIS_REACH))
if(!isliving(to_show) || !to_show.can_perform_action(can_reach_target, ALLOW_RESTING | FORBID_TELEKINESIS_REACH)) // BUBBER EDIT CHANGE -- ORIGINAL: if(!isliving(to_show) || !to_show.can_perform_action(parent, ALLOW_RESTING | FORBID_TELEKINESIS_REACH))
return FALSE
if(locked)

View File

@@ -103,6 +103,7 @@ GLOBAL_LIST_INIT(skyrat_cloth_recipes, list(
new/datum/stack_recipe("eyepatch wrap", /obj/item/clothing/glasses/eyepatch/wrap, 2, category = CAT_CLOTHING),
new/datum/stack_recipe("eyepatch", /obj/item/clothing/glasses/eyepatch, 2, category = CAT_CLOTHING),
new/datum/stack_recipe("xenoarch bag", /obj/item/storage/bag/xenoarch, 4, category = CAT_CONTAINERS),
new/datum/stack_recipe("saddlebags", /obj/item/storage/backpack/saddlebags, 5, category = CAT_CONTAINERS),
))
/obj/item/stack/sheet/cloth/get_main_recipes()
@@ -119,6 +120,10 @@ GLOBAL_LIST_INIT(skyrat_leather_belt_recipes, list(
new/datum/stack_recipe("medical bandolier", /obj/item/storage/belt/medbandolier, 5, category = CAT_CONTAINERS),
new/datum/stack_recipe("gear harness", /obj/item/clothing/under/misc/skyrat/gear_harness, 6, category = CAT_CLOTHING),
new/datum/stack_recipe("ammo pouch", /obj/item/storage/pouch/ammo, 4, category = CAT_CONTAINERS),
new/datum/stack_recipe_list("saddles", list(
new/datum/stack_recipe("riding saddle (normal)", /obj/item/riding_saddle/leather, 5, category = CAT_CLOTHING),
new/datum/stack_recipe("riding saddle (peacekeeper)", /obj/item/riding_saddle/leather/peacekeeper, 5, category = CAT_CLOTHING),
)),
))
/obj/item/stack/sheet/leather/get_main_recipes()

View File

@@ -85,6 +85,7 @@
icon_state = "deer"
taur_mode = STYLE_TAUR_HOOF
alt_taur_mode = STYLE_TAUR_PAW
organ_type = /obj/item/organ/external/taur_body/horselike/deer
/datum/sprite_accessory/taur/drake
name = "Drake"

View File

@@ -23,11 +23,46 @@
/// If true, our sprite accessory will not render.
var/hide_self
/// If true, this taur body allows a saddle to be equipped and used.
var/can_use_saddle = FALSE
/// If true, can ride saddled taurs and be ridden by other taurs with this set to TRUE.
var/can_ride_saddled_taurs = FALSE
/// When being ridden via saddle, how much the rider is offset on the x axis when facing west or east.
var/riding_offset_side_x = 12
/// When being ridden via saddle, how much the rider is offset on the y axis when facing west or east.
var/riding_offset_side_y = 2
/// When being ridden via saddle, how much the rider is offset on the x axis when facing north or south.
var/riding_offset_front_x = 0
/// When being ridden via saddle, how much the rider is offset on the y axis when facing north or south.
var/riding_offset_front_y = 5
/// Lazylist of (TEXT_DIR -> y offset) to be applied to taur-specific clothing that isn't specifically made for this sprite.
var/list/taur_specific_clothing_y_offsets
/// When considering how much to offset our rider, we multiply size scaling against this.
var/riding_offset_scaling_mult = 0.8
/obj/item/organ/external/taur_body/horselike
can_use_saddle = TRUE
/obj/item/organ/external/taur_body/horselike/synth
organ_flags = ORGAN_ROBOTIC
/obj/item/organ/external/taur_body/horselike/deer
/obj/item/organ/external/taur_body/horselike/deer/Initialize(mapload)
. = ..()
taur_specific_clothing_y_offsets = list(
TEXT_EAST = 3,
TEXT_WEST = 3,
TEXT_NORTH = 0,
TEXT_SOUTH = 0,
)
/obj/item/organ/external/taur_body/serpentine
left_leg_name = "upper serpentine body"
right_leg_name = "lower serpentine body"
@@ -51,6 +86,8 @@
left_leg_name = null
right_leg_name = null
can_ride_saddled_taurs = TRUE
/obj/item/organ/external/taur_body/anthro/synth
organ_flags = ORGAN_ROBOTIC
@@ -145,3 +182,14 @@
if(old_right_leg)
QDEL_NULL(old_right_leg)
/obj/item/organ/external/taur_body/proc/get_riding_offset(oversized = FALSE)
var/size_scaling = (owner.dna.features["body_size"] / BODY_SIZE_NORMAL) - 1
var/scaling_mult = 1 + (size_scaling * riding_offset_scaling_mult)
return list(
TEXT_NORTH = list(riding_offset_front_x, round((riding_offset_front_y + taur_specific_clothing_y_offsets?[TEXT_NORTH]) * scaling_mult, 1)),
TEXT_SOUTH = list(riding_offset_front_x, round((riding_offset_front_y + taur_specific_clothing_y_offsets?[TEXT_SOUTH]) * scaling_mult, 1)),
TEXT_EAST = list(round(-riding_offset_side_x * scaling_mult, 1), round((riding_offset_side_y + taur_specific_clothing_y_offsets?[TEXT_EAST]) * scaling_mult, 1)),
TEXT_WEST = list(round(riding_offset_side_x * scaling_mult, 1), round((riding_offset_side_y + taur_specific_clothing_y_offsets?[TEXT_WEST]) * scaling_mult, 1)),
)

View File

@@ -0,0 +1,57 @@
// Any item with this component can have its storage accessed by alt clicking the wearer.
/datum/component/accessable_storage
/datum/component/accessable_storage/Initialize()
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/accessable_storage/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(parent_equipped))
RegisterSignal(parent, COMSIG_STORAGE_STORED_ITEM, PROC_REF(parent_stored_item))
RegisterSignal(parent, COMSIG_STORAGE_REMOVED_ITEM, PROC_REF(parent_removed_item))
/datum/component/accessable_storage/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_STORAGE_STORED_ITEM, COMSIG_STORAGE_REMOVED_ITEM))
/// Signal handler for COMSIG_ITEM_EQUIPPED. Handles registering signals.
/datum/component/accessable_storage/proc/parent_equipped(datum/signal_source, mob/equipper, slot)
SIGNAL_HANDLER
if (isliving(equipper) && !(equipper.get_slot_by_item(parent) & (ITEM_SLOT_HANDS|ITEM_SLOT_POCKETS)))
RegisterSignal(equipper, COMSIG_MOB_UNEQUIPPED_ITEM, PROC_REF(mob_unequipped_item))
RegisterSignal(equipper, COMSIG_CLICK_ALT, PROC_REF(mob_alt_clicked_on))
/// Signal handler for COMSIG_CLICK_ALT. Handles the actual opening of storage.
/datum/component/accessable_storage/proc/mob_alt_clicked_on(mob/signal_source, mob/clicker)
SIGNAL_HANDLER
var/obj/item/item_parent = parent
item_parent.atom_storage?.open_storage(clicker, signal_source)
animate_target(signal_source)
/// Signal handler for COMSIG_MOB_UNEQUIPPED_ITEM. Handles unregistering signals.
/datum/component/accessable_storage/proc/mob_unequipped_item(mob/signal_source, obj/item/item, force, atom/newloc, no_move, invdrop, silent)
SIGNAL_HANDLER
if (item == parent)
UnregisterSignal(signal_source, list(COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_CLICK_ALT))
/// Signal handler for COMSIG_STORAGE_STORED_ITEM. Handles animating our parent's wearer.
/datum/component/accessable_storage/proc/parent_stored_item(obj/item/signal_source, obj/item/inserted, mob/user, force)
SIGNAL_HANDLER
if (isliving(signal_source.loc))
animate_target(signal_source.loc)
/// Signal handler for COMSIG_STORAGE_REMOVED_ITEM. Handles animating our parent's wearer.
/datum/component/accessable_storage/proc/parent_removed_item(obj/item/signal_source, obj/item/thing, atom/remove_to_loc, silent)
SIGNAL_HANDLER
if (isliving(signal_source.loc))
animate_target(signal_source.loc)
/// Gives a spiffy animation to the target to represent opening and closing. Copy pasted from storage.dm, please change if that proc ever changes
/datum/component/accessable_storage/proc/animate_target(atom/target = parent)
var/matrix/old_matrix = target.transform
animate(target, time = 1.5, loop = 0, transform = target.transform.Scale(1.07, 0.9))
animate(time = 2, transform = old_matrix)

View File

@@ -0,0 +1,98 @@
/mob/living/carbon/human/mouse_buckle_handling(mob/living/buckling, mob/living/user)
if (!ride_saddle(buckling, user))
return ..()
/// The amount of time it takes to mount a mob with a saddle on.
#define SADDLE_MOUNTING_TIME 1.5 SECONDS
/// The mult to be applied to SADDLE_MOUNTING_TIME if the user is mounting someone else onto the saddled mob.
#define SADDLE_MOUNTING_OTHER_MULT 3
/// Attempts to have buckling ride on our saddle, if we have one.
/mob/living/carbon/human/proc/ride_saddle(mob/living/buckling, mob/living/user)
if (!can_be_ridden_by(buckling, user))
return FALSE
var/delay = SADDLE_MOUNTING_TIME
var/ridee_string = ""
var/list/mobs_with_special_messages = list(src)
if (buckling != user)
ridee_string = " [buckling] onto"
mobs_with_special_messages += buckling
delay *= SADDLE_MOUNTING_OTHER_MULT
user.visible_message(span_warning("[user] starts to mount[ridee_string] [src]..."), span_notice("You start to mount[ridee_string] [src]..."), ignored_mobs = mobs_with_special_messages)
to_chat(src, span_warning("[user] starts to mount[ridee_string] you!"))
if (buckling != user)
to_chat(buckling, span_boldwarning("[user] starts to mount you onto [src]!"))
if (!do_after(user, SADDLE_MOUNTING_TIME, target = src))
user.visible_message(span_warning("[user] fails to mount[ridee_string] [src]!"), span_warning("You fail to mount[ridee_string] [src]!"), ignored_mobs = mobs_with_special_messages)
to_chat(src, span_warning("[user] fails to mount[ridee_string] you!"))
if (buckling != user)
to_chat(buckling, span_warning("[user] fails to mount you onto [src]!"))
return FALSE
if (!can_be_ridden_by(buckling, user)) // because we slept
return FALSE // no feedback. this already gives some
var/saddle_flags = SEND_SIGNAL(src, COMSIG_HUMAN_SADDLE_RIDE_ATTEMPT, buckling)
if (!saddle_flags)
saddle_flags = RIDER_NEEDS_ARMS
return buckle_mob(buckling, force = TRUE, check_loc = TRUE, buckle_mob_flags = saddle_flags)
#undef SADDLE_MOUNTING_TIME
#undef SADDLE_MOUNTING_OTHER_MULT
/**
* Determines if src can be ridden by to_buckle.
*
* Args:
* * to_buckle: The mob trying to mount us. Non-nullable.
* * user: The mob mounting to_buckle onto us, most likely to_buckle itself. Non-nullable.
* * silent = FALSE: If FALSE, we do not send feedback messages. Boolean.
* Returns:
* * FALSE if we have no saddle, if we're trying to mount ourself, or if to_buckle can't be mounted. TRUE otherwise.
*/
/mob/living/carbon/human/proc/can_be_ridden_by(mob/living/to_buckle, mob/living/user, silent = FALSE)
if (!HAS_TRAIT(src, TRAIT_SADDLED))
return FALSE // no feedback as it's very very common
if (user == src) // would open the inventory screen otherwise
return FALSE // no feedback as you get your answer via the inventory screen
/// Conditions that prevent riding, with a balloon alert
var/cant_buckle_message
if (to_buckle == src)
cant_buckle_message = "can't ride self!"
else if (body_position == LYING_DOWN)
cant_buckle_message = "can't ride resting!"
else if (incapacitated)
cant_buckle_message = "can't mount incapacitated mobs!"
else if (INCAPACITATED_IGNORING(user, INCAPABLE_GRAB))
cant_buckle_message = "you are incapacitated!"
else if (INCAPACITATED_IGNORING(to_buckle, INCAPABLE_GRAB))
cant_buckle_message = "rider incapacitated!"
else if (length(buckled_mobs))
cant_buckle_message = "already being ridden!"
if (cant_buckle_message)
if (!silent)
balloon_alert(user, cant_buckle_message)
return FALSE
if (!ishuman(to_buckle))
return TRUE // no more checks need to be made
var/mob/living/carbon/human/human_target = to_buckle
var/obj/item/organ/external/taur_body/taur_body = get_organ_slot(ORGAN_SLOT_EXTERNAL_TAUR)
var/obj/item/organ/external/taur_body/other_taur_body = human_target.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAUR)
if (isnull(taur_body) || isnull(other_taur_body))
return TRUE
if (!other_taur_body.can_ride_saddled_taurs) // no stacking, sorry
return FALSE
return TRUE

View File

@@ -0,0 +1,10 @@
/obj/item/mob_can_equip(mob/living/M, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action)
. = ..()
if (!.)
return FALSE
if (SEND_SIGNAL(src, COMSIG_ITEM_MOB_CAN_EQUIP, M, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action) & COMPONENT_ITEM_CANT_EQUIP)
return FALSE
return TRUE

View File

@@ -0,0 +1,83 @@
/// Allows the attached item to enable saddle mechanics on the mob wearing it.
/datum/component/carbon_saddle
/// The piggyback flags to apply to any mob that wears parent.
var/saddle_flags = RIDER_NEEDS_ARM|RIDING_TAUR
/datum/component/carbon_saddle/Initialize(saddle_flags)
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
if (!isnull(saddle_flags))
src.saddle_flags = saddle_flags
/datum/component/carbon_saddle/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_POST_EQUIPPED, PROC_REF(parent_equipped))
RegisterSignal(parent, COMSIG_ITEM_MOB_CAN_EQUIP, PROC_REF(parent_can_equip))
/datum/component/carbon_saddle/UnregisterFromParent()
var/obj/item/item_parent = parent
UnregisterSignal(item_parent, COMSIG_ITEM_EQUIPPED)
/// Signal handler for COMSIG_ITEM_POST_EQUIPPED. Handles registering signals and traits on the equipper.
/datum/component/carbon_saddle/proc/parent_equipped(datum/signal_source, mob/equipper, slot)
SIGNAL_HANDLER
if (!isliving(equipper) || (slot & (ITEM_SLOT_HANDS|ITEM_SLOT_POCKETS)))
return
var/mob/living/living_equipper = equipper
RegisterSignal(living_equipper, COMSIG_MOB_UNEQUIPPED_ITEM, PROC_REF(mob_unequipped_item))
RegisterSignal(living_equipper, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(wearer_lost_organ))
RegisterSignal(living_equipper, COMSIG_HUMAN_SADDLE_RIDE_ATTEMPT, PROC_REF(wearer_ridden))
ADD_TRAIT(living_equipper, TRAIT_SADDLED, REF(src))
/// Signal handler for COMSIG_MOB_UNEQUIPPED_ITEM.
/datum/component/carbon_saddle/proc/mob_unequipped_item(mob/signal_source, obj/item/item, force, atom/newloc, no_move, invdrop, silent)
SIGNAL_HANDLER
if (item == parent)
mob_unequipped_parent(signal_source)
/// Called when our parent is inequipped. Handles unsetting signals and traits.
/datum/component/carbon_saddle/proc/mob_unequipped_parent(mob/target)
UnregisterSignal(target, list(COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_CARBON_LOSE_ORGAN, COMSIG_HUMAN_SADDLE_RIDE_ATTEMPT))
REMOVE_TRAIT(target, TRAIT_SADDLED, REF(src))
/// Signal handler for COMSIG_CARBON_LOSE_ORGAN. Handles unequipping if the requisite organ is removed.
/datum/component/carbon_saddle/proc/wearer_lost_organ(mob/living/carbon/signal_source, obj/item/organ/lost)
SIGNAL_HANDLER
if (!wearer_has_requisite_organ(signal_source))
var/obj/item/item_parent = parent
item_parent.forceMove(get_turf(item_parent)) // force unequip
/// Signal handler for COMSIG_HUMAN_SADDLE_RIDE_ATTEMPT. Returns saddle_flags into the signal bitfield.
/datum/component/carbon_saddle/proc/wearer_ridden(mob/living/carbon/human/wearer, mob/living/carbon/rider)
SIGNAL_HANDLER
return saddle_flags
/// Signal handler for COMSIG_ITEM_MOB_CAN_EQUIP. If equipped into a non-hands and pockets slot, returns COMPONENT_ITEM_CANT_EQUIP if our owner doesnt have our required organ.
/datum/component/carbon_saddle/proc/parent_can_equip(obj/item/signal_source, mob/living/target, slot, disable_warning, bypass_equip_delay_self, ignore_equipped, indirect_action)
SIGNAL_HANDLER
if (slot & (ITEM_SLOT_HANDS|ITEM_SLOT_POCKETS))
return
if (!wearer_has_requisite_organ(target))
return COMPONENT_ITEM_CANT_EQUIP
/// Determines if our wearer, target, has our required organ.
/datum/component/carbon_saddle/proc/wearer_has_requisite_organ(mob/living/carbon/target)
if (!istype(target))
return TRUE
for (var/obj/item/organ/iter_organ as anything in target.organs)
if (!istype(iter_organ, /obj/item/organ/external/taur_body))
continue
var/obj/item/organ/external/taur_body/taur_body = iter_organ
if (taur_body.can_use_saddle)
return TRUE
return FALSE

View File

@@ -0,0 +1,74 @@
/obj/item/riding_saddle
name = "generic riding saddle"
desc = "someone spawned a basetype!"
slot_flags = ITEM_SLOT_BACK // no storage
icon = 'modular_skyrat/modules/taur_mechanics/sprites/saddles.dmi'
worn_icon = 'modular_skyrat/modules/taur_mechanics/sprites/saddles.dmi'
worn_icon_taur_snake = 'modular_skyrat/modules/taur_mechanics/sprites/saddles.dmi'
supports_variations_flags = STYLE_TAUR_HOOF|STYLE_TAUR_PAW
/obj/item/riding_saddle/Initialize(mapload)
. = ..()
if(type == /obj/item/riding_saddle) // don't even let these prototypes exist
return INITIALIZE_HINT_QDEL
AddComponent(/datum/component/carbon_saddle, RIDING_TAUR) // FREE HANDS
AddComponent(/datum/component/taur_clothing_offset)
/obj/item/riding_saddle/leather
name = "riding saddle"
desc = "A thick leather riding saddle. Typically used for animals, this one has been designed for use by the taurs of the galaxy. \n\
This saddle has specialized footrests that will allow a rider to <b>use both their hands</b> while riding."
icon_state = "saddle_leather_item"
worn_icon_state = "saddle_leather"
inhand_icon_state = "syringe_kit" // placeholder
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
/obj/item/riding_saddle/leather/Initialize(mapload)
. = ..()
/obj/item/riding_saddle/leather/peacekeeper
name = "peacekeeper saddle"
icon_state = "saddle_sec_item"
worn_icon_state = "saddle_sec"
/obj/item/riding_saddle/leather/peacekeeper/Initialize(mapload)
. = ..()
desc += " This one is painted in peacekeeper livery."
/obj/item/storage/backpack/saddlebags
name = "saddlebags"
desc = "A collection of small pockets bound together by belt, typically used on caravan animals due to their superior storage capacity. This one has been designed for use by the taurs of the galaxy. \n\
These saddlebags can be accessed by anyone if they <b>alt-click</b> the wearer.\n\
Additionally, they have been modified with a hand grip that would allow <b>one free hand</b> during riding."
gender = PLURAL
slot_flags = ITEM_SLOT_BACK
icon = 'modular_skyrat/modules/taur_mechanics/sprites/saddles.dmi'
worn_icon = 'modular_skyrat/modules/taur_mechanics/sprites/saddles.dmi'
worn_icon_taur_snake = 'modular_skyrat/modules/taur_mechanics/sprites/saddles.dmi'
supports_variations_flags = STYLE_TAUR_HOOF|STYLE_TAUR_PAW
storage_type = /datum/storage/saddlebags
icon_state = "saddle_satchel_item"
worn_icon_state = "saddle_satchel"
// slightly better than a backpack, but accessable_storage counterbalances this
/datum/storage/saddlebags
max_total_storage = 26
max_slots = 21
/obj/item/storage/backpack/saddlebags/Initialize(mapload)
. = ..()
AddComponent(/datum/component/carbon_saddle, RIDING_TAUR|RIDER_NEEDS_ARM) // one arm
AddComponent(/datum/component/accessable_storage)
AddComponent(/datum/component/taur_clothing_offset)

View File

@@ -0,0 +1,73 @@
/// Offsets parent's worn overlay based on their wearer's taur body.
/datum/component/taur_clothing_offset
/datum/component/taur_clothing_offset/RegisterWithParent()
. = ..()
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_ITEM_GET_WORN_OVERLAYS, PROC_REF(modify_overlays))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(parent_equipped))
/datum/component/taur_clothing_offset/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_GET_WORN_OVERLAYS, COMSIG_ITEM_EQUIPPED))
/// Signal handler for COMSIG_ITEM_GET_WORN_OVERLAYS. Offsets parent's worn overlay based on their wearer's taur body.
/datum/component/taur_clothing_offset/proc/modify_overlays(obj/item/signal_source, list/overlays, mutable_appearance/standing, isinhands, icon_file)
SIGNAL_HANDLER
if (!iscarbon(signal_source.loc))
return
if (isinhands)
return
var/mob/living/carbon/target_carbon = signal_source.loc
var/obj/item/organ/external/taur_body/taur_body = target_carbon.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAUR)
if (!istype(taur_body))
return
var/icon_dir = target_carbon.dir
if(ISDIAGONALDIR(icon_dir))
icon_dir &= ~(NORTH | SOUTH)
var/offset = taur_body.taur_specific_clothing_y_offsets?["[icon_dir]"]
if (!offset)
return
standing.pixel_y += offset
/// Signal handler for COMSIG_ITEM_EQUIPPED. Handles registering signals.
/datum/component/taur_clothing_offset/proc/parent_equipped(datum/signal_source, mob/equipper, slot)
SIGNAL_HANDLER
if (ishuman(equipper) && !(equipper.get_slot_by_item(parent) & (ITEM_SLOT_HANDS|ITEM_SLOT_POCKETS)))
RegisterSignal(equipper, COMSIG_MOB_UNEQUIPPED_ITEM, PROC_REF(mob_unequipped_item))
RegisterSignal(equipper, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(wearer_dir_changed))
/// Signal handler for COMSIG_MOB_UNEQUIPPED_ITEM. Handles unregistering signals.
/datum/component/taur_clothing_offset/proc/mob_unequipped_item(mob/signal_source, obj/item/item, force, atom/newloc, no_move, invdrop, silent)
SIGNAL_HANDLER
if (item == parent)
UnregisterSignal(signal_source, list(COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_ATOM_POST_DIR_CHANGE))
/// Signal handler for COMSIG_ATOM_POST_DIR_CHANGE. Handles updating the offset based on dir.
/datum/component/taur_clothing_offset/proc/wearer_dir_changed(mob/living/carbon/human/signal_source, old_dir, new_dir)
SIGNAL_HANDLER
var/obj/item/organ/external/taur_body/taur_body = signal_source.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAUR)
if (isnull(taur_body))
return
if(ISDIAGONALDIR(new_dir))
new_dir &= ~(NORTH | SOUTH) //remove diagonal for lookup, matching how directional player sprites are selected
if(ISDIAGONALDIR(old_dir))
old_dir &= ~(NORTH | SOUTH)
if(new_dir == old_dir)
return
var/obj/item/item_parent = parent
if (taur_body.taur_specific_clothing_y_offsets?["[new_dir]"] || taur_body.taur_specific_clothing_y_offsets?["[old_dir]"]) // discounts null and 0
// if the last dir had a offset, we need to reset if the new dir has NO offset
signal_source.update_clothing(item_parent.slot_flags)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -11,3 +11,16 @@
name = "Mr. Fluff"
item_path = /obj/item/clothing/head/mob_holder/pet/donator/centralsmith
/datum/loadout_item/inhand/saddlebags
name = "saddlebags"
item_path = /obj/item/storage/backpack/saddlebags
/datum/loadout_item/inhand/saddle // these should be in the other category but apparantly those are "pocket" loadout items so idk?
name = "riding saddle (leather)"
item_path = /obj/item/riding_saddle/leather
/datum/loadout_item/inhand/saddle_peacekeeper
name = "riding saddle (peacekeeper)"
item_path = /obj/item/riding_saddle/leather/peacekeeper
restricted_roles = list(JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_DETECTIVE, JOB_CORRECTIONS_OFFICER, JOB_HEAD_OF_SECURITY)

View File

@@ -119,6 +119,7 @@
/obj/item/clothing/accessory/badge/holo/cord = 10,
/obj/item/clothing/head/helmet/blueshirt = 3,
/obj/item/clothing/suit/armor/vest/blueshirt = 3,
/obj/item/riding_saddle/leather/peacekeeper = 3,
)
//CONTRABAND: Basically for less serious/hard to cat stuff like the Cowboy and Bluecoat stuff. And for stuff that shouldn't be easy to get like HUD varients.

View File

@@ -499,6 +499,7 @@
#include "code\__DEFINES\~~bubber_defines\economy.dm"
#include "code\__DEFINES\~~bubber_defines\experisci.dm"
#include "code\__DEFINES\~~bubber_defines\footsteps.dm"
#include "code\__DEFINES\~~bubber_defines\item.dm"
#include "code\__DEFINES\~~bubber_defines\jobs.dm"
#include "code\__DEFINES\~~bubber_defines\loadout.dm"
#include "code\__DEFINES\~~bubber_defines\misc.dm"
@@ -511,6 +512,7 @@
#include "code\__DEFINES\~~bubber_defines\status_indicator_defines.dm"
#include "code\__DEFINES\~~bubber_defines\storyteller_defines.dm"
#include "code\__DEFINES\~~bubber_defines\transport.dm"
#include "code\__DEFINES\~~bubber_defines\vehicles.dm"
#include "code\__DEFINES\~~bubber_defines\___HELPERS\global_lists.dm"
#include "code\__DEFINES\~~bubber_defines\research\techweb_nodes.dm"
#include "code\__DEFINES\~~bubber_defines\traits\declarations.dm"
@@ -8557,11 +8559,17 @@
#include "modular_skyrat\modules\tarkon\code\misc-fluff\research.dm"
#include "modular_skyrat\modules\tarkon\code\misc-fluff\spawner.dm"
#include "modular_skyrat\modules\tarkon\code\misc-fluff\tools.dm"
#include "modular_skyrat\modules\taur_mechanics\code\accessible_storage.dm"
#include "modular_skyrat\modules\taur_mechanics\code\constrict.dm"
#include "modular_skyrat\modules\taur_mechanics\code\human.dm"
#include "modular_skyrat\modules\taur_mechanics\code\item.dm"
#include "modular_skyrat\modules\taur_mechanics\code\mod.dm"
#include "modular_skyrat\modules\taur_mechanics\code\preferences.dm"
#include "modular_skyrat\modules\taur_mechanics\code\saddle_component.dm"
#include "modular_skyrat\modules\taur_mechanics\code\saddles.dm"
#include "modular_skyrat\modules\taur_mechanics\code\serpentine_taur.dm"
#include "modular_skyrat\modules\taur_mechanics\code\species.dm"
#include "modular_skyrat\modules\taur_mechanics\code\taur_clothing_offset.dm"
#include "modular_skyrat\modules\telecomms_specialist\telecomms_specialist.dm"
#include "modular_skyrat\modules\tesh_augments\code\all_nodes.dm"
#include "modular_skyrat\modules\tesh_augments\code\limbs.dm"