Spiders can now be tamed and ridden (#95194)

## About The Pull Request
This lets people ride and tame spiders by feeding them mouse, lizard,
moth, fly, and worm meat. You can only do this when a spider is a
spiderling or young; once it's fully grown, it's no longer possible.

This also refactors some tameable code to use `TRAIT_TAMED` instead of
setting variables on mobs individually.

## Why It's Good For The Game
It looks cool.

<img width="1920" height="1080" alt="dreamseeker_97k7fgzdhv"
src="https://github.com/user-attachments/assets/87d53102-468d-4dd7-b0a7-004f9a98df17"
/>


## Changelog
🆑
add: Spiders can now be tamed and ridden. They eat mouse, lizard, moth,
fly, and worm meat and can only be tamed when they are spiderlings or
young.
code: Refactored tameable code to use TRAIT_TAMED instead of setting
individual variables on each mob.
/🆑
This commit is contained in:
Tim
2026-03-15 14:20:57 -05:00
committed by GitHub
parent 03cb799009
commit 5602082624
20 changed files with 80 additions and 19 deletions
@@ -481,6 +481,7 @@
flick("glutton_mouth", src)
/mob/living/basic/clown/mutant/glutton/tamed(mob/living/tamer, atom/food)
. = ..()
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/glutton)
@@ -51,6 +51,7 @@
ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(enjoyed_food))
/mob/living/basic/vatbeast/tamed(mob/living/tamer, obj/item/food)
. = ..()
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/vatbeast)
set_faction(list(FACTION_NEUTRAL))
@@ -79,6 +79,7 @@
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
/mob/living/basic/cow/tamed(mob/living/tamer, atom/food)
. = ..()
visible_message("[src] [tame_message] as it seems to bond with [tamer].", "You [self_tame_message], recognizing [tamer] as your new pal.")
AddElement(/datum/element/ridable, /datum/component/riding/creature/cow)
@@ -55,6 +55,7 @@
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
/mob/living/basic/pig/tamed(mob/living/tamer, atom/food)
. = ..()
AddElement(/datum/element/ridable, /datum/component/riding/creature/pig)
visible_message(span_notice("[src] snorts respectfully."))
@@ -60,6 +60,7 @@
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, unique = unique_tamer)
/mob/living/basic/pony/tamed(mob/living/tamer, atom/food)
. = ..()
playsound(src, 'sound/mobs/non-humanoids/pony/snort.ogg', 50)
AddElement(/datum/element/ridable, /datum/component/riding/creature/pony)
visible_message(span_notice("[src] snorts happily."))
@@ -68,6 +68,7 @@
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 15, bonus_tame_chance = 5)
/mob/living/basic/mining/wolf/tamed(mob/living/tamer, atom/food)
. = ..()
new /obj/effect/temp_visual/heart(src.loc)
// ride wolf, life good
AddElement(/datum/element/ridable, /datum/component/riding/creature/wolf)
@@ -118,6 +118,7 @@
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 5)
/mob/living/basic/mining/goldgrub/tamed(mob/living/tamer, atom/food)
. = ..()
new /obj/effect/temp_visual/heart(src.loc)
AddElement(/datum/element/ridable, /datum/component/riding/creature/goldgrub)
AddComponent(/datum/component/obeys_commands, pet_commands)
@@ -36,8 +36,6 @@
var/tentacle_warning_state = "goliath_preattack"
/// Can this kind of goliath be tamed?
var/tameable = TRUE
/// Has this particular goliath been tamed?
var/tamed = FALSE
/// Can someone ride us around like a horse?
var/saddled = FALSE
/// Slight cooldown to prevent double-dipping if we use both abilities at once
@@ -112,7 +110,7 @@
if (saddled)
balloon_alert(user, "already saddled!")
return
if (!tamed)
if (!HAS_TRAIT(src, TRAIT_TAMED))
balloon_alert(user, "too rowdy!")
return
balloon_alert(user, "affixing saddle...")
@@ -150,10 +148,6 @@
return
icon_state = tentacle_warning_state
/// Get ready for mounting
/mob/living/basic/mining/goliath/tamed(mob/living/tamer, atom/food)
tamed = TRUE
// Copy entire faction rather than just placing user into faction, to avoid tentacle peril on station
/mob/living/basic/mining/goliath/befriend(mob/living/new_friend)
. = ..()
@@ -74,6 +74,7 @@
charge.Trigger(target = atom_target)
/mob/living/basic/mining/lobstrosity/tamed(mob/living/tamer, obj/item/food)
. = ..()
new /obj/effect/temp_visual/heart(loc)
/// Pet commands for this mob, however you'll have to tame juvenile lobstrosities to a trained adult one.
var/list/pet_commands = list(
@@ -167,8 +168,6 @@
base_fishing_level = SKILL_LEVEL_NOVICE
/// What do we become when we grow up?
var/mob/living/basic/mining/lobstrosity/grow_type = /mob/living/basic/mining/lobstrosity
/// Were we tamed? If yes, tame the mob we become when we grow up too.
var/was_tamed = FALSE
/datum/emote/lobstrosity_juvenile
abstract_type = /datum/emote/lobstrosity_juvenile
@@ -226,7 +225,6 @@
/mob/living/basic/mining/lobstrosity/juvenile/tamed(mob/living/tamer, obj/item/food)
. = ..()
was_tamed = TRUE
// They are more pettable I guess
AddElement(/datum/element/pet_bonus, "chitter")
REMOVE_TRAIT(src, TRAIT_MOB_HIDE_HAPPINESS, INNATE_TRAIT)
@@ -237,7 +235,7 @@
/mob/living/basic/mining/lobstrosity/juvenile/proc/grow_up()
var/name_to_use = name == initial(name) ? grow_type::name : name
var/mob/living/basic/mining/lobstrosity/grown = change_mob_type(grow_type, get_turf(src), name_to_use)
if(was_tamed)
if(HAS_TRAIT(src, TRAIT_TAMED))
grown.tamed()
for(var/friend in ai_controller?.blackboard?[BB_FRIENDS_LIST])
grown.befriend(friend)
@@ -91,6 +91,7 @@
///Proc to run on a successful taming attempt
/mob/living/basic/pet/dog/tamed(mob/living/tamer, atom/food)
. = ..()
visible_message(span_notice("[src] licks at [tamer] in a friendly manner!"))
/// A dog bone fully heals a dog, and befriends it if it's not your friend.
@@ -447,6 +447,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
return returnable_list
/mob/living/basic/parrot/tamed(mob/living/tamer, atom/food)
. = ..()
new /obj/effect/temp_visual/heart(drop_location())
/mob/living/basic/parrot/proc/drop_item_on_signal(mob/living/user)
@@ -142,6 +142,7 @@
/// Called when another mob has forged a bond of friendship with this one, passed the taming mob as 'tamer'
/mob/living/basic/carp/tamed(mob/living/tamer, atom/food, feedback = TRUE)
. = ..()
AddElement(/datum/element/ridable, ridable_data)
AddComponent(/datum/component/obeys_commands, tamed_commands)
if (!feedback)
@@ -117,6 +117,7 @@
COOLDOWN_START(src, eye_healing, 15 SECONDS)
/mob/living/basic/eyeball/tamed(mob/living/tamer, atom/food)
. = ..()
spin(spintime = 2 SECONDS, speed = 1)
//become passive to the humens
APPLY_FACTION_AND_ALLIES_FROM(src, tamer)
@@ -53,6 +53,13 @@
var/menu_description = "Tanky and strong for the defense of the nest and other spiders."
/// If true then you shouldn't be told that you're a spider antagonist as soon as you are placed into this mob
var/apply_spider_antag = TRUE
/// Commands you can give this spider once it is tamed
var/static/list/tamed_commands = list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
/datum/pet_command/attack,
)
/datum/emote/spider
abstract_type = /datum/emote/spider
@@ -85,6 +92,17 @@
webbing.Grant(src)
ai_controller?.set_blackboard_key(BB_SPIDER_WEB_ACTION, webbing)
var/static/list/food_types = list(
/obj/item/food/meat/slab/human/mutant/lizard,
/obj/item/food/meat/slab/human/mutant/fly,
/obj/item/food/meat/slab/human/mutant/moth,
/obj/item/food/meat/slab/mouse,
/obj/item/food/meat/slab/mothroach,
/obj/item/food/meat/slab/blood_worm,
/obj/item/food/deadmouse,
)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 20, bonus_tame_chance = 10)
/mob/living/basic/spider/Login()
. = ..()
if(!. || !client)
@@ -92,6 +110,8 @@
GLOB.spidermobs[src] = TRUE
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier, multiplicative_slowdown = player_speed_modifier)
AddElement(/datum/element/ridable, /datum/component/riding/creature/spider)
/mob/living/basic/spider/Logout()
. = ..()
remove_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier)
@@ -100,6 +120,12 @@
GLOB.spidermobs -= src
return ..()
/mob/living/basic/spider/tamed(mob/living/tamer, atom/food, feedback = TRUE)
. = ..()
new /obj/effect/temp_visual/heart(src.loc)
AddElement(/datum/element/ridable, /datum/component/riding/creature/spider)
AddComponent(/datum/component/obeys_commands, tamed_commands)
/mob/living/basic/spider/mob_negates_gravity()
if(locate(/obj/structure/spider/stickyweb) in loc)
return TRUE
@@ -158,6 +184,12 @@
grown.set_name()
grown.set_brute_loss(get_brute_loss())
grown.set_fire_loss(get_fire_loss())
if(HAS_TRAIT(src, TRAIT_TAMED))
grown.tamed()
else if(istype(grown, /mob/living/basic/spider/giant)) // Adults cannot be tamed via snacks
qdel(grown.GetComponent(/datum/component/tameable))
qdel(src)
/**
@@ -28,8 +28,6 @@
ai_controller = /datum/ai_controller/basic_controller/mouse
/// Whether this rat is friendly to players
var/tame = FALSE
/// What color our mouse is. Brown, gray and white - leave blank for random.
var/body_color
/// Does this mouse contribute to the ratcap?
@@ -63,7 +61,8 @@
SSmobs.cheeserats |= src
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
src.tame = tame
if(tame)
ADD_TRAIT(src, TRAIT_TAMED, INNATE_TRAIT)
if(!isnull(new_body_color))
body_color = new_body_color
if(isnull(body_color))
@@ -81,7 +80,7 @@
AddComponent(/datum/component/swarming, 16, 16) //max_x, max_y
/mob/living/basic/mouse/proc/make_tameable()
if (tame)
if (HAS_TRAIT(src, TRAIT_TAMED))
add_faction(FACTION_NEUTRAL)
else
var/static/list/food_types = list(/obj/item/food/cheese)
@@ -196,9 +195,9 @@
/// Called when a mouse is hand-fed some cheese, it will stop being afraid of humans
/mob/living/basic/mouse/tamed(mob/living/tamer, obj/item/food/cheese/cheese)
. = ..()
new /obj/effect/temp_visual/heart(loc)
add_faction(FACTION_NEUTRAL)
tame = TRUE
try_consume_cheese(cheese)
ai_controller.CancelActions() // Interrupt any current fleeing
@@ -248,7 +247,7 @@
/// Creates a new mouse based on this mouse's subtype.
/mob/living/basic/mouse/proc/create_a_new_rat()
new /mob/living/basic/mouse(loc, /* tame = */ tame)
new /mob/living/basic/mouse(loc, HAS_TRAIT(src, TRAIT_TAMED))
/// Biting into a cable will cause a mouse to get shocked and die if applicable. Or do nothing if they're lucky.
/mob/living/basic/mouse/proc/try_bite_cable(obj/structure/cable/cable)
@@ -302,7 +301,7 @@
contributes_to_ratcap = FALSE
/mob/living/basic/mouse/brown/tom/make_tameable()
tame = TRUE
ADD_TRAIT(src, TRAIT_TAMED, INNATE_TRAIT)
return ..()
/mob/living/basic/mouse/brown/tom/Initialize(mapload)
@@ -312,7 +311,7 @@
AddElement(/datum/element/pet_bonus, "squeak")
/mob/living/basic/mouse/brown/tom/create_a_new_rat()
new /mob/living/basic/mouse/brown(loc, /* tame = */ tame) // dominant gene
new /mob/living/basic/mouse/brown(loc, HAS_TRAIT(src, TRAIT_TAMED)) // dominant gene
/mob/living/basic/mouse/rat
name = "rat"