Refactor var/can_be_held to an element (#96917)

## About The Pull Request

We were doing a lot of mental gymnastics in a bunch of other places, so
let's change this wonky var to a streamlined element that will rely on
the same signals that a lot of stuff was already using/accounting for in
its own signal handling pathways, instead of being a weird coverage gap.
This patch should also make the whole "checking if someone is attempting
to pick a mob up" thing make a lot more sense and use a unified proc
instead of spot-checking whatever random things it wants to spot-check.
## Why It's Good For The Game

I didn't know this was a thing until I looked at #96873 and it made me
sad because literally everything else involving mob drag-and-drop is
already signal-based except this weird stinker that relied on proc
overrides. Never mind that now, let's use nice traits to avoid
typecasting and elements to avoid duplicating code. It should also be
much cleaner to add holdability to a mob isntead of having to do
`can_be_held = FALSE` as a weird behavior (at least one instance had
this non-necessarily). All this really is is just middleware on the
extant /mob/living code but still making it in proper lockstep with the
other signalling procs.

I did port over raptor code faithfully but I'm not 100% sure if it was
meant to be like this? Regardless, that's how it is.
## Changelog
🆑
refactor: Picking up mobs has been altered a bit, please report any bugs
or glitches.
/🆑
This commit is contained in:
san7890
2026-07-13 16:57:47 +02:00
committed by GitHub
parent 02b6286fee
commit cb535cdfa6
30 changed files with 112 additions and 52 deletions
@@ -9,7 +9,6 @@
layer = BELOW_MOB_LAYER
anchored = FALSE
health = 35
can_be_held = TRUE
maxHealth = 35
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 7.8, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 2)
path_image_color = "#80dae7"
@@ -93,6 +92,7 @@
our_screwdriver = new(src)
our_rods = new(src, our_rods::max_amount)
set_color(toolbox_color)
AddElement(/datum/element/can_be_held)
START_PROCESSING(SSobj, src)
/mob/living/basic/bot/repairbot/proc/set_color(new_color)
@@ -46,7 +46,6 @@
lighting_cutoff_red = 30
lighting_cutoff_green = 35
lighting_cutoff_blue = 25
can_be_held = TRUE
worn_slot_flags = ITEM_SLOT_HEAD
inhand_holder_type = /obj/item/mob_holder/drone
/// `TRUE` if we have picked our visual appearance, `FALSE` otherwise (default)
@@ -155,6 +154,8 @@
listener.RegisterSignal(src, COMSIG_LIVING_DEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes))
listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, allow_alarm_changes))
AddElement(/datum/element/can_be_held)
/mob/living/basic/drone/med_hud_set_health()
set_hud_image_state(DIAG_HUD, "huddiag[RoundDiagBar(health/maxHealth)]")
@@ -36,7 +36,6 @@
mob_biotypes = MOB_ORGANIC|MOB_BUG
density = FALSE
gold_core_spawnable = FRIENDLY_SPAWN
can_be_held = TRUE
held_w_class = WEIGHT_CLASS_TINY
environment_smash = ENVIRONMENT_SMASH_NONE
habitable_atmos = null
@@ -71,6 +70,7 @@
AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_QUEEN_BEE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
AddElement(/datum/element/basic_allergenic_attack, allergen = BUGS, allergen_chance = 33, histamine_add = 5)
AddElement(/datum/element/can_be_held)
/mob/living/basic/bee/mob_pickup(mob/living/picker)
if(flags_1 & HOLOGRAM_1)
@@ -18,7 +18,6 @@
health = 15
maxHealth = 15
mob_size = MOB_SIZE_SMALL
can_be_held = TRUE
density = FALSE
gold_core_spawnable = FRIENDLY_SPAWN
speak_emote = list("sniffles", "twitches")
@@ -55,6 +54,7 @@
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/pet_bonus, "hop")
AddElement(/datum/element/animal_variety, icon_prefix, pick("brown", "black", "white"), TRUE)
AddElement(/datum/element/can_be_held)
if(prob(20)) // bunny
name = "bunny"
@@ -78,6 +78,8 @@ GLOBAL_LIST_EMPTY(raptor_population)
var/datum/raptor_inheritance/inherited_stats = null
/// Current happiness value of the raptor
var/happiness_percentage = 0
/// The ability for this raptor to be picked up and held. Defaults to FALSE as it's meant to be in lockstep with the element being added/removed.
var/could_be_held = FALSE
/mob/living/basic/raptor/Initialize(mapload, datum/raptor_color/color_type, datum/raptor_inheritance/passed_stats)
. = ..()
@@ -283,6 +285,16 @@ GLOBAL_LIST_EMPTY(raptor_population)
return pick_weight(prob_list)
/// Updates the presence of the can_be_held element based on what we want from the raptor
/mob/living/basic/raptor/proc/update_holdability(bool)
if(bool && !could_be_held)
AddElement(/datum/element/can_be_held)
could_be_held = TRUE
if(!bool && could_be_held)
RemoveElement(/datum/element/can_be_held)
could_be_held = FALSE
/mob/living/basic/raptor/proc/on_picked_up(mob/living/basic/raptor/source, mob/living/user, obj/item/mob_holder/holder)
SIGNAL_HANDLER
// Our inventory code sucks so we have to do this
@@ -364,16 +376,16 @@ GLOBAL_LIST_EMPTY(raptor_population)
base_pixel_w = initial(base_pixel_w)
mob_size = initial(mob_size)
can_be_held = initial(can_be_held)
density = initial(density)
move_resist = initial(move_resist)
can_breed = initial(can_breed)
update_holdability(initial(could_be_held))
if (new_stage == RAPTOR_ADULT)
// Adults need to be tamed with skill rather than snacks
qdel(GetComponent(/datum/component/tameable))
else // Make us teeny-tiny
can_be_held = TRUE
update_holdability(TRUE)
density = FALSE
can_breed = FALSE
move_resist = MOVE_RESIST_DEFAULT
@@ -389,7 +401,7 @@ GLOBAL_LIST_EMPTY(raptor_population)
var/obj/item/mob_holder/holder = null
if (istype(loc, /obj/item/mob_holder))
holder = loc
if (!can_be_held)
if (!could_be_held)
holder.release()
holder = null
@@ -128,7 +128,7 @@ GLOBAL_LIST_INIT(raptor_colors, init_raptor_colors())
// Purple raptors never "fully" grow up, and remain usable as backpacks
/datum/raptor_color/purple/setup_adult(mob/living/basic/raptor/raptor)
raptor.can_be_held = TRUE
raptor.update_holdability(TRUE)
raptor.density = FALSE
raptor.move_resist = MOVE_RESIST_DEFAULT
raptor.held_w_class = WEIGHT_CLASS_BULKY
@@ -24,7 +24,6 @@
response_harm_simple = "kick"
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
gold_core_spawnable = FRIENDLY_SPAWN
can_be_held = TRUE
ai_controller = /datum/ai_controller/basic_controller/cat
held_state = "cat2"
attack_verb_continuous = "claws"
@@ -91,6 +90,7 @@
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/pet_bonus, "purr", /datum/mood_event/pet_animal)
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/can_be_held)
add_cell_sample()
add_verb(src, /mob/living/proc/toggle_resting)
add_traits(list(TRAIT_CATLIKE_GRACE, TRAIT_VENTCRAWLER_ALWAYS, TRAIT_WOUND_LICKER, TRAIT_COLORBLIND), INNATE_TRAIT)
@@ -27,7 +27,6 @@
response_harm_simple = "kick"
speak_emote = list("barks", "woofs")
faction = list(FACTION_NEUTRAL)
can_be_held = TRUE
ai_controller = /datum/ai_controller/basic_controller/dog
// The dog attack pet command can raise melee attack above 0
attack_verb_continuous = "bites"
@@ -74,6 +73,7 @@
AddElement(/datum/element/pet_bonus, "woof")
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/unfriend_attacker, untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.")
AddElement(/datum/element/can_be_held)
var/static/list/food_types = list(
/obj/item/food/meat/slab/human/mutant/skeleton,
/obj/item/stack/sheet/bone,
+1 -1
View File
@@ -19,7 +19,6 @@
response_harm_continuous = "kicks"
response_harm_simple = "kick"
gold_core_spawnable = FRIENDLY_SPAWN
can_be_held = TRUE
held_state = "fox"
melee_damage_lower = 5
melee_damage_upper = 5
@@ -58,6 +57,7 @@
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/tiny_mob_hunter, MOB_SIZE_SMALL)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/can_be_held)
/datum/ai_controller/basic_controller/fox
blackboard = list(
+1 -1
View File
@@ -10,7 +10,6 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth)
speak_emote = list("yawns")
can_be_held = TRUE
held_state = "sloth"
response_help_continuous = "pets"
@@ -53,6 +52,7 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth)
AddElement(/datum/element/pet_bonus, "ssmile")
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/can_be_held)
AddComponent(/datum/component/tree_climber)
if(!mapload || !isnull(GLOB.cargo_sloth) || !is_station_level(z))
@@ -19,7 +19,6 @@
speed = 6
verb_say = "gurgles"
verb_ask = "gurgles curiously"
can_be_held = TRUE
verb_exclaim = "gurgles loudly"
verb_yell = "gurgles loudly"
worn_slot_flags = ITEM_SLOT_HEAD
@@ -28,6 +27,8 @@
ai_controller = /datum/ai_controller/basic_controller/snail
/// What do we turn into if effected by a regal rat?
var/minion_path = /mob/living/basic/snail/angry
/// Are we able to be held by a player?
var/should_be_holdable = TRUE
/mob/living/basic/snail/Initialize(mapload)
. = ..()
@@ -53,6 +54,9 @@
if (minion_path)
AddElement(/datum/element/regal_rat_minion, converted_path = minion_path, success_balloon = "gurgle", pet_commands = GLOB.regal_rat_minion_commands)
if(should_be_holdable)
AddElement(/datum/element/can_be_held)
/mob/living/basic/snail/proc/on_entered(datum/source, obj/effect/decal/cleanable/food/salt/potential_salt)
SIGNAL_HANDLER
if(istype(potential_salt))
@@ -105,7 +109,7 @@
melee_damage_lower = 5
melee_damage_upper = 8
obj_damage = 8
can_be_held = FALSE
should_be_holdable = FALSE
minion_path = null
ai_controller = /datum/ai_controller/basic_controller/snail/trash
@@ -26,7 +26,6 @@
response_harm_simple = "kick"
gold_core_spawnable = FRIENDLY_SPAWN
faction = list(FACTION_NEUTRAL)
can_be_held = FALSE
health = 100
maxHealth = 100
light_range = 1.5 // Bioluminescence!
@@ -200,7 +200,6 @@
icon_state = "maint_spider"
icon_living = "maint_spider"
icon_dead = "maint_spider_dead"
can_be_held = TRUE
mob_size = MOB_SIZE_TINY
held_w_class = WEIGHT_CLASS_TINY
worn_slot_flags = ITEM_SLOT_HEAD
@@ -234,3 +233,4 @@
AddElement(/datum/element/ai_retaliate)
AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/tiny_mob_hunter)
AddElement(/datum/element/can_be_held)
@@ -19,7 +19,6 @@
response_help_simple = "pet"
verb_say = "chips"
verb_ask = "chips curiously"
can_be_held = TRUE
verb_exclaim = "chips loudly"
verb_yell = "chips loudly"
faction = list(FACTION_NEUTRAL)
@@ -47,6 +46,7 @@
AddComponent(/datum/component/tameable, food_types = eatable_food, tame_chance = 70, bonus_tame_chance = 0)
ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food))
AddElement(/datum/element/wears_collar)
AddElement(/datum/element/can_be_held)
AddComponent(/datum/component/obeys_commands, pet_commands)
if(can_breed)
add_breeding_component()
@@ -22,7 +22,6 @@
response_harm_continuous = "splats"
response_harm_simple = "splat"
can_be_held = TRUE
held_w_class = WEIGHT_CLASS_TINY
held_lh = 'icons/mob/inhands/animal_item_lefthand.dmi'
held_rh = 'icons/mob/inhands/animal_item_righthand.dmi'
@@ -35,6 +34,7 @@
. = ..()
add_traits(list(TRAIT_NODROWN, TRAIT_SWIMMER, TRAIT_VENTCRAWLER_ALWAYS), INNATE_TRAIT)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_AXOLOTL, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
AddElement(/datum/element/can_be_held)
/datum/ai_controller/basic_controller/axolotl
ai_traits = PASSIVE_AI_FLAGS
@@ -10,7 +10,6 @@
health = 1
maxHealth = 1
speed = 1.25
can_be_held = TRUE
gold_core_spawnable = FRIENDLY_SPAWN
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
@@ -50,6 +49,7 @@
. = ..()
AddElement(/datum/element/death_drops, /obj/effect/decal/cleanable/insectguts)
AddElement(/datum/element/swabable, cockroach_cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7)
AddElement(/datum/element/can_be_held)
AddComponent( \
/datum/component/squashable, \
squash_chance = 50, \
+1 -1
View File
@@ -31,7 +31,6 @@
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
mob_size = MOB_SIZE_TINY
gold_core_spawnable = FRIENDLY_SPAWN
can_be_held = TRUE
held_w_class = WEIGHT_CLASS_TINY
worn_slot_flags = ITEM_SLOT_HEAD
head_icon = 'icons/mob/clothing/head/pets_head.dmi'
@@ -60,6 +59,7 @@
AddElement(/datum/element/venomous, poison_type, poison_per_bite)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_FROG, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
AddElement(/datum/element/can_be_held)
if (minion_type)
AddElement(/datum/element/regal_rat_minion, converted_path = minion_type, success_balloon = "ribbit", pet_commands = GLOB.regal_rat_minion_commands)
@@ -32,7 +32,6 @@
gold_core_spawnable = FRIENDLY_SPAWN
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
can_be_held = TRUE
held_w_class = WEIGHT_CLASS_TINY
held_lh = 'icons/mob/inhands/animal_item_lefthand.dmi'
held_rh = 'icons/mob/inhands/animal_item_righthand.dmi'
@@ -60,6 +59,7 @@
. = ..()
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
AddElement(/datum/element/pet_bonus, "tongue")
AddElement(/datum/element/can_be_held)
AddElement(/datum/element/basic_eating, heal_amt = 5, food_types = edibles)
ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(edibles))
@@ -16,7 +16,6 @@
maxHealth = 25
speed = 1.25
gold_core_spawnable = FRIENDLY_SPAWN
can_be_held = TRUE
worn_slot_flags = ITEM_SLOT_HEAD
verb_say = "flutters"
@@ -60,6 +59,7 @@
ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(food_types))
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/pet_bonus, "squeak")
AddElement(/datum/element/can_be_held)
add_verb(src, /mob/living/proc/toggle_resting)
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
@@ -11,7 +11,6 @@
density = FALSE
pass_flags = PASSTABLE|PASSGRILLE|PASSMOB
mob_size = MOB_SIZE_TINY
can_be_held = TRUE
held_w_class = WEIGHT_CLASS_TINY
mob_biotypes = MOB_ORGANIC|MOB_BEAST
gold_core_spawnable = FRIENDLY_SPAWN
@@ -78,6 +77,7 @@
AddElement(/datum/element/connect_loc, loc_connections)
make_tameable()
AddComponent(/datum/component/swarming, 16, 16) //max_x, max_y
AddElement(/datum/element/can_be_held)
/mob/living/basic/mouse/proc/make_tameable()
if (HAS_TRAIT(src, TRAIT_TAMED))