diff --git a/code/__DEFINES/dcs/mob_signals.dm b/code/__DEFINES/dcs/mob_signals.dm
index 7ade7b633f2..fa194f0ef62 100644
--- a/code/__DEFINES/dcs/mob_signals.dm
+++ b/code/__DEFINES/dcs/mob_signals.dm
@@ -196,6 +196,7 @@
///from of mob/MouseDrop(): (/atom/over, /mob/user)
#define COMSIG_DO_MOB_STRIP "do_mob_strip"
+#define COMSIG_STRIPPABLE_REQUEST_ITEMS "strippable_request_items"
// Sent when a mob spawner is attacked directly or via projectile.
#define COMSIG_SPAWNER_SET_TARGET "spawner_set_target"
@@ -239,3 +240,5 @@
#define MOVE_ARG_NEW_LOC 1
/// The arugment of move_args which dictates our movement direction
#define MOVE_ARG_DIRECTION 2
+
+#define COMSIG_LIVING_RESTING "living_resting"
diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm
index 11a88c871d2..d0283e3897a 100644
--- a/code/datums/elements/strippable.dm
+++ b/code/datums/elements/strippable.dm
@@ -313,8 +313,11 @@
var/list/items = list()
- for(var/strippable_key in strippable.items)
- var/datum/strippable_item/item_data = strippable.items[strippable_key]
+ var/list/unfiltered_items = strippable.items.Copy()
+ SEND_SIGNAL(owner, COMSIG_STRIPPABLE_REQUEST_ITEMS, unfiltered_items)
+
+ for(var/strippable_key in unfiltered_items)
+ var/datum/strippable_item/item_data = unfiltered_items[strippable_key]
if(!item_data.should_show(owner, user))
continue
@@ -389,11 +392,14 @@
if(!isliving(ui.user) || !HAS_TRAIT(user, TRAIT_CAN_STRIP))
return
+ var/list/unfiltered_items = strippable.items.Copy()
+ SEND_SIGNAL(owner, COMSIG_STRIPPABLE_REQUEST_ITEMS, unfiltered_items)
+
. = TRUE
switch(action)
if("use")
var/key = params["key"]
- var/datum/strippable_item/strippable_item = strippable.items[key]
+ var/datum/strippable_item/strippable_item = unfiltered_items[key]
if(isnull(strippable_item))
return
diff --git a/code/datums/elements/wears_collar.dm b/code/datums/elements/wears_collar.dm
new file mode 100644
index 00000000000..ee89ca6a18a
--- /dev/null
+++ b/code/datums/elements/wears_collar.dm
@@ -0,0 +1,122 @@
+/datum/element/wears_collar
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+ ///our icon's pathfile
+ var/collar_icon
+ ///our collar's icon state
+ var/collar_icon_state
+ ///iconstate of our collar while resting
+ var/collar_resting_icon_state
+
+ var/datum/strippable_item/pet_collar/pet_collar
+
+/datum/element/wears_collar/Attach(datum/target, collar_icon_ = 'icons/mob/pets.dmi', collar_icon_state_, collar_resting_icon_state_ = FALSE)
+ . = ..()
+
+ if(!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+
+ collar_icon = collar_icon_
+ collar_icon_state = collar_icon_state_
+ collar_resting_icon_state = collar_resting_icon_state_
+ pet_collar = new
+
+ RegisterSignal(target, COMSIG_ATTACK_BY, PROC_REF(attach_collar))
+ RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_overlays_updated))
+ RegisterSignal(target, COMSIG_ATOM_EXITED, PROC_REF(on_content_exit))
+ RegisterSignal(target, COMSIG_ATOM_ENTERED, PROC_REF(on_content_enter))
+ RegisterSignal(target, COMSIG_LIVING_RESTING, PROC_REF(on_rest))
+ RegisterSignal(target, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change))
+ RegisterSignal(target, COMSIG_STRIPPABLE_REQUEST_ITEMS, PROC_REF(on_strippable_request_items))
+
+/datum/element/wears_collar/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, list(
+ COMSIG_ATTACK_BY,
+ COMSIG_ATOM_UPDATE_OVERLAYS,
+ COMSIG_ATOM_EXITED,
+ COMSIG_ATOM_ENTERED,
+ COMSIG_LIVING_RESTING,
+ COMSIG_MOB_STATCHANGE,
+ COMSIG_STRIPPABLE_REQUEST_ITEMS,
+ ))
+
+/datum/element/wears_collar/proc/on_strippable_request_items(datum/source, list/items)
+ SIGNAL_HANDLER // COMSIG_STRIPPABLE_REQUEST_ITEMS
+ items[STRIPPABLE_ITEM_PET_COLLAR] = pet_collar
+
+/datum/element/wears_collar/proc/on_stat_change(mob/living/source)
+ SIGNAL_HANDLER // COMSIG_MOB_STATCHANGE
+
+ if(collar_icon_state)
+ source.update_icon(UPDATE_OVERLAYS)
+
+/datum/element/wears_collar/proc/on_content_exit(mob/living/source, atom/moved)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXITED
+
+ var/obj/item/petcollar/collar = moved
+ if(!istype(moved))
+ return
+
+ if(collar.tagname)
+ source.name = collar.original_name
+ source.real_name = collar.original_real_name
+
+ collar.original_name = null
+ collar.original_real_name = null
+
+ if(collar_icon_state)
+ source.update_appearance()
+
+/datum/element/wears_collar/proc/on_content_enter(mob/living/source, obj/item/petcollar/new_collar)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ if(!istype(new_collar))
+ return
+
+ if(new_collar.tagname)
+ new_collar.original_name = source.name
+ new_collar.original_real_name = source.real_name
+ source.name = new_collar.tagname
+ source.real_name = new_collar.tagname
+
+ if(collar_icon_state)
+ source.update_appearance()
+
+/datum/element/wears_collar/proc/attach_collar(atom/source, atom/movable/attacking_item, mob/user, params)
+ SIGNAL_HANDLER // COMSIG_ATTACK_BY
+
+ if(!istype(attacking_item, /obj/item/petcollar))
+ return NONE
+ if(locate(/obj/item/petcollar) in source)
+ to_chat(user, "[source] is already wearing a collar!")
+ return COMPONENT_SKIP_AFTERATTACK
+
+ if(user.drop_item())
+ attacking_item.forceMove(source)
+ to_chat(user, "You put [attacking_item] around [source]'s neck.")
+ else
+ to_chat(user, "[attacking_item] is stuck to your hand!")
+
+ return COMPONENT_SKIP_AFTERATTACK
+
+/datum/element/wears_collar/proc/on_overlays_updated(mob/living/source, list/overlays)
+ SIGNAL_HANDLER // COMSIG_ATOM_UPDATE_OVERLAYS
+
+ if(!locate(/obj/item/petcollar) in source)
+ return
+
+ var/icon_tag = ""
+
+ if(source.stat == DEAD || HAS_TRAIT(source, TRAIT_FAKEDEATH))
+ icon_tag = "_dead"
+ else if(collar_resting_icon_state && source.resting)
+ icon_tag = "_rest"
+
+ overlays += mutable_appearance(collar_icon, "[collar_icon_state][icon_tag]collar")
+ overlays += mutable_appearance(collar_icon, "[collar_icon_state][icon_tag]tag")
+
+/datum/element/wears_collar/proc/on_rest(atom/movable/source)
+ SIGNAL_HANDLER // COMSIG_LIVING_RESTING
+
+ source.update_icon(UPDATE_OVERLAYS)
diff --git a/code/game/objects/items/collar.dm b/code/game/objects/items/collar.dm
index 9bb839442c8..6eb50053982 100644
--- a/code/game/objects/items/collar.dm
+++ b/code/game/objects/items/collar.dm
@@ -4,6 +4,8 @@
icon_state = "petcollar"
item_color = "petcollar"
var/tagname = null
+ var/original_name
+ var/original_real_name
var/obj/item/card/id/access_id
/obj/item/petcollar/Destroy()
@@ -62,7 +64,7 @@
/obj/item/petcollar/process()
var/mob/living/simple_animal/M = loc
// if it wasn't intentionally unequipped but isn't being worn, possibly gibbed
- if(istype(M) && src == M.pcollar && M.stat != DEAD)
+ if(istype(M) && M.stat != DEAD)
return
var/area/pet_death_area = get_area(M)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index afb9caeee19..cc0a9ca7da5 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2093,10 +2093,9 @@
P.master_commander = H
P.universal_speak = TRUE
P.universal_understand = TRUE
- P.set_can_collar(TRUE)
+ P.AddElement(/datum/element/wears_collar)
P.faction = list("neutral")
- var/obj/item/petcollar/C = new
- P.add_collar(C)
+ var/obj/item/petcollar/C = new(P)
var/obj/item/card/id/I = H.wear_id
if(I)
var/obj/item/card/id/D = new /obj/item/card/id(C)
diff --git a/code/modules/events/sentience.dm b/code/modules/events/sentience.dm
index ada9e98b3f7..e4a4ae4b39a 100644
--- a/code/modules/events/sentience.dm
+++ b/code/modules/events/sentience.dm
@@ -27,7 +27,7 @@
dust_if_respawnable(SG)
SA.universal_speak = TRUE
SA.sentience_act()
- SA.set_can_collar(TRUE)
+ SA.AddElement(/datum/element/wears_collar)
SA.maxHealth = max(SA.maxHealth, 200)
SA.health = SA.maxHealth
SA.del_on_death = FALSE
diff --git a/code/modules/mining/equipment/lazarus_injector.dm b/code/modules/mining/equipment/lazarus_injector.dm
index 0ebd211169b..ebc0a03cce7 100644
--- a/code/modules/mining/equipment/lazarus_injector.dm
+++ b/code/modules/mining/equipment/lazarus_injector.dm
@@ -27,7 +27,7 @@
if(M.stat == DEAD)
M.faction = list("neutral")
M.revive()
- M.set_can_collar(TRUE)
+ M.AddElement(/datum/element/wears_collar)
if(ishostile(target))
var/mob/living/simple_animal/hostile/H = M
if(isretaliate(target))
diff --git a/code/modules/mob/living/living_status_procs.dm b/code/modules/mob/living/living_status_procs.dm
index e83bab19b03..c44fa0c63b4 100644
--- a/code/modules/mob/living/living_status_procs.dm
+++ b/code/modules/mob/living/living_status_procs.dm
@@ -120,6 +120,7 @@ STATUS EFFECTS
layer = LYING_MOB_LAYER //so mob lying always appear behind standing mobs
ADD_TRAIT(src, TRAIT_UI_BLOCKED, LYING_DOWN_TRAIT)
ADD_TRAIT(src, TRAIT_CANNOT_PULL, LYING_DOWN_TRAIT)
+ SEND_SIGNAL(src, COMSIG_LIVING_RESTING, TRUE)
RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(orient_crawling))
set_density(FALSE)
set_lying_angle(pick(90, 270))
@@ -129,6 +130,7 @@ STATUS EFFECTS
layer = initial(layer)
set_density(initial(density))
REMOVE_TRAITS_IN(src, LYING_DOWN_TRAIT)
+ SEND_SIGNAL(src, COMSIG_LIVING_RESTING, FALSE)
UnregisterSignal(src, COMSIG_ATOM_DIR_CHANGE)
set_lying_angle(0)
pixel_y = 0
diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm
index 95c22172bf9..5b919326889 100644
--- a/code/modules/mob/living/simple_animal/animal_defense.dm
+++ b/code/modules/mob/living/simple_animal/animal_defense.dm
@@ -1,8 +1,3 @@
-/mob/living/simple_animal/item_interaction(mob/living/user, obj/item/O, list/modifiers)
- if(can_collar && istype(O, /obj/item/petcollar) && !pcollar)
- add_collar(O, user)
- return ITEM_INTERACT_COMPLETE
-
/mob/living/simple_animal/attack_hand(mob/living/carbon/human/M)
..()
switch(M.a_intent)
diff --git a/code/modules/mob/living/simple_animal/friendly/bunny.dm b/code/modules/mob/living/simple_animal/friendly/bunny.dm
index 881e014f147..ff96383f425 100644
--- a/code/modules/mob/living/simple_animal/friendly/bunny.dm
+++ b/code/modules/mob/living/simple_animal/friendly/bunny.dm
@@ -25,10 +25,13 @@
maxbodytemp = 323 //Above 50 Degrees Celcius
can_hide = TRUE
holder_type = /obj/item/holder/bunny
- can_collar = TRUE
gold_core_spawnable = FRIENDLY_SPAWN
ventcrawler = VENTCRAWLER_ALWAYS
+/mob/living/simple_animal/bunny/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/mob/living/simple_animal/bunny/attack_hand(mob/living/carbon/human/M)
if(M.a_intent == INTENT_HELP)
get_scooped(M, TRUE)
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 83696196b72..ae3c7e47bd3 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -22,12 +22,16 @@
response_disarm = "gently pushes aside"
response_harm = "kicks"
gold_core_spawnable = FRIENDLY_SPAWN
- collar_type = "cat"
var/turns_since_scan = 0
var/mob/living/simple_animal/mouse/movement_target
var/eats_mice = 1
+ var/collar_icon_state = "cat"
footstep_type = FOOTSTEP_MOB_CLAW
+/mob/living/simple_animal/pet/cat/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar, collar_icon_state_ = collar_icon_state, collar_resting_icon_state_ = TRUE)
+
//RUNTIME IS ALIVE! SQUEEEEEEEE~
/mob/living/simple_animal/pet/cat/runtime
name = "Runtime"
@@ -118,7 +122,6 @@
resting = TRUE
custom_emote(EMOTE_VISIBLE, pick("sits down.", "crouches on its hind legs.", "looks alert."))
icon_state = "[icon_living]_sit"
- collar_type = "[initial(collar_type)]_sit"
/mob/living/simple_animal/pet/cat/handle_automated_action()
if(stat == CONSCIOUS && !buckled)
@@ -195,7 +198,7 @@
gender = NEUTER
density = FALSE
pass_flags = PASSMOB
- collar_type = "kitten"
+ collar_icon_state = "kitten"
/mob/living/simple_animal/pet/cat/syndi
name = "SyndiCat"
diff --git a/code/modules/mob/living/simple_animal/friendly/corgi_stripping.dm b/code/modules/mob/living/simple_animal/friendly/corgi_stripping.dm
index 863dbb2e081..01858b3968f 100644
--- a/code/modules/mob/living/simple_animal/friendly/corgi_stripping.dm
+++ b/code/modules/mob/living/simple_animal/friendly/corgi_stripping.dm
@@ -2,8 +2,7 @@
GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list(
/datum/strippable_item/corgi_head,
- /datum/strippable_item/corgi_back,
- /datum/strippable_item/pet_collar
+ /datum/strippable_item/corgi_back
)))
/datum/strippable_item/corgi_head
@@ -41,7 +40,7 @@ GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list(
if(!istype(pet_source))
return
- return pet_source.pcollar
+ return (locate(/obj/item/petcollar) in source)
/datum/strippable_item/pet_collar/try_equip(atom/source, obj/item/equipping, mob/user)
. = ..()
@@ -59,15 +58,15 @@ GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list(
if(!istype(pet_source))
return
- INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living/simple_animal, add_collar), equipping, user)
+ user.transfer_item_to(equipping, source)
/datum/strippable_item/pet_collar/finish_unequip(atom/source, mob/user)
var/mob/living/simple_animal/pet_source = source
if(!istype(pet_source))
return
- INVOKE_ASYNC(pet_source, TYPE_PROC_REF(/mob/living/simple_animal, remove_collar), user.drop_location(), user)
-
+ var/obj/item/petcollar/collar = locate() in source
+ user.put_in_hands(collar)
/datum/strippable_item/corgi_back
key = STRIPPABLE_ITEM_BACK
diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm
index 8be2a37fdf1..2fd41ecf05d 100644
--- a/code/modules/mob/living/simple_animal/friendly/crab.dm
+++ b/code/modules/mob/living/simple_animal/friendly/crab.dm
@@ -18,9 +18,12 @@
friendly = "pinches"
ventcrawler = VENTCRAWLER_ALWAYS
can_hide = TRUE
- can_collar = TRUE
gold_core_spawnable = FRIENDLY_SPAWN
+/mob/living/simple_animal/crab/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/mob/living/simple_animal/crab/handle_automated_movement()
//CRAB movement
if(stat == CONSCIOUS || !isturf(loc) || IS_HORIZONTAL(src) || buckled)
diff --git a/code/modules/mob/living/simple_animal/friendly/deer.dm b/code/modules/mob/living/simple_animal/friendly/deer.dm
index 3bba3bfeeba..4e79d504a84 100644
--- a/code/modules/mob/living/simple_animal/friendly/deer.dm
+++ b/code/modules/mob/living/simple_animal/friendly/deer.dm
@@ -15,6 +15,9 @@
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
- can_collar = TRUE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
gold_core_spawnable = FRIENDLY_SPAWN
+
+/mob/living/simple_animal/deer/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
diff --git a/code/modules/mob/living/simple_animal/friendly/diona_nymph.dm b/code/modules/mob/living/simple_animal/friendly/diona_nymph.dm
index fd3e170fc39..3d22c9dde18 100644
--- a/code/modules/mob/living/simple_animal/friendly/diona_nymph.dm
+++ b/code/modules/mob/living/simple_animal/friendly/diona_nymph.dm
@@ -43,7 +43,6 @@
var/list/donors = list()
holder_type = /obj/item/holder/diona
- can_collar = TRUE
a_intent = INTENT_HELP
var/evolve_donors = 5 //amount of blood donors needed before evolving
@@ -54,6 +53,10 @@
var/datum/action/innate/diona/evolve/evolve_action = new()
var/datum/action/innate/diona/steal_blood/steal_blood_action = new()
+/mob/living/simple_animal/diona/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/datum/action/innate/diona/merge
name = "Merge with gestalt"
button_overlay_icon = 'icons/mob/human_races/r_diona.dmi'
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index 9bac2802d85..1f599885b55 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -73,7 +73,6 @@
butcher_results = list(/obj/item/food/meat/corgi = 3, /obj/item/stack/sheet/animalhide/corgi = 1)
childtype = list(/mob/living/simple_animal/pet/dog/corgi/puppy = 95, /mob/living/simple_animal/pet/dog/corgi/puppy/void = 5)
animal_species = /mob/living/simple_animal/pet/dog
- collar_type = "corgi"
hud_type = /datum/hud/corgi
///Currently worn item on the head slot
var/obj/item/inventory_head = null
@@ -84,13 +83,14 @@
var/shaved = FALSE
var/nofur = FALSE //Corgis that have risen past the material plane of existence.
var/razor_shave_delay = 5 SECONDS
+ var/collar_icon_state = "corgi"
+
/mob/living/simple_animal/pet/dog/corgi/Initialize(mapload)
. = ..()
- regenerate_icons()
-
-/mob/living/simple_animal/pet/dog/corgi/add_strippable_element()
+ AddElement(/datum/element/wears_collar, collar_icon_state_ = collar_icon_state)
AddElement(/datum/element/strippable, length(strippable_inventory_slots) ? create_strippable_list(strippable_inventory_slots) : GLOB.strippable_corgi_items)
+ regenerate_icons()
/mob/living/simple_animal/pet/dog/corgi/Destroy()
QDEL_NULL(inventory_head)
@@ -246,7 +246,7 @@
item_to_add.forceMove(src)
inventory_head = item_to_add
update_corgi_fluff()
- regenerate_icons()
+ update_appearance()
else
to_chat(user, "You set [item_to_add] on [src]'s head, but it falls off!")
item_to_add.forceMove(drop_location())
@@ -276,8 +276,9 @@
var/datum/dog_fashion/DF = new inventory_back.dog_fashion(src)
DF.apply(src)
-/mob/living/simple_animal/pet/dog/corgi/regenerate_icons()
- ..()
+/mob/living/simple_animal/pet/dog/corgi/update_overlays()
+ . = ..()
+
if(inventory_head)
var/image/head_icon
var/datum/dog_fashion/DF = new inventory_head.dog_fashion(src)
@@ -296,7 +297,7 @@
else
head_icon = DF.get_overlay()
- add_overlay(head_icon)
+ . += head_icon
if(inventory_back)
var/image/back_icon
@@ -315,7 +316,8 @@
back_icon.transform = turn(back_icon.transform, 180)
else
back_icon = DF.get_overlay()
- add_overlay(back_icon)
+
+ . += back_icon
//IAN! SQUEEEEEEEEE~
/mob/living/simple_animal/pet/dog/corgi/ian
@@ -519,9 +521,8 @@
density = FALSE
pass_flags = PASSMOB
mob_size = MOB_SIZE_SMALL
- collar_type = "puppy"
- strippable_inventory_slots = list(/datum/strippable_item/pet_collar) // Puppies do not have a head or back equipment slot.
-
+ strippable_inventory_slots = list() // Puppies do not have a head or back equipment slot.
+ collar_icon_state = "puppy"
/// Tribute to the corgis born in nullspace
/mob/living/simple_animal/pet/dog/corgi/puppy/void
@@ -553,7 +554,7 @@
response_help = "pets"
response_disarm = "bops"
response_harm = "kicks"
- strippable_inventory_slots = list(/datum/strippable_item/corgi_back, /datum/strippable_item/pet_collar) //Lisa already has a cute bow!
+ strippable_inventory_slots = list(/datum/strippable_item/corgi_back) //Lisa already has a cute bow!
var/turns_since_scan = 0
/mob/living/simple_animal/pet/dog/corgi/lisa/Life()
@@ -655,7 +656,10 @@
icon_living = "pug"
icon_dead = "pug_dead"
butcher_results = list(/obj/item/food/meat/pug = 3)
- collar_type = "pug"
+
+/mob/living/simple_animal/pet/dog/pug/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar, collar_icon_state_ = "pug")
/mob/living/simple_animal/pet/dog/pug/handle_automated_movement()
. = ..()
diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
index 54837024ea3..e7cfc84a62e 100644
--- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
+++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
@@ -26,7 +26,6 @@
melee_damage_lower = 1
melee_damage_upper = 2
stop_automated_movement_when_pulled = TRUE
- can_collar = TRUE
blood_volume = BLOOD_VOLUME_NORMAL
var/obj/item/udder/cow/udder = null
gender = FEMALE
@@ -35,6 +34,7 @@
/mob/living/simple_animal/hostile/retaliate/goat/Initialize(mapload)
. = ..()
udder = new()
+ AddElement(/datum/element/wears_collar)
/mob/living/simple_animal/hostile/retaliate/goat/Destroy()
QDEL_NULL(udder)
@@ -135,12 +135,12 @@
mob_size = MOB_SIZE_TINY
holder_type = /obj/item/holder/chicken
can_hide = TRUE
- can_collar = TRUE
gold_core_spawnable = FRIENDLY_SPAWN
footstep_type = FOOTSTEP_MOB_CLAW
/mob/living/simple_animal/chick/Initialize(mapload)
. = ..()
+ AddElement(/datum/element/wears_collar)
scatter_atom()
/mob/living/simple_animal/chick/scatter_atom(x_offset, y_offset)
@@ -157,10 +157,10 @@
C.name = name
if(mind)
mind.transfer_to(C)
- if(pcollar)
- var/the_collar = pcollar
- drop_item_to_ground(pcollar)
- C.add_collar(the_collar)
+
+ for(var/atom/movable/AM in contents)
+ AM.forceMove(C)
+
qdel(src)
@@ -208,7 +208,6 @@ GLOBAL_VAR_INIT(chicken_count, 0)
mob_size = MOB_SIZE_SMALL
holder_type = /obj/item/holder/chicken
can_hide = TRUE
- can_collar = TRUE
var/list/feedMessages = list("It clucks happily.","It clucks happily.")
var/list/layMessage = EGG_LAYING_MESSAGES
var/list/validColors = list("brown","black","white")
@@ -222,6 +221,7 @@ GLOBAL_VAR_INIT(chicken_count, 0)
icon_state = "[icon_prefix]_[body_color]"
icon_living = "[icon_prefix]_[body_color]"
icon_dead = "[icon_prefix]_[body_color]_dead"
+ AddElement(/datum/element/wears_collar)
scatter_atom()
update_appearance(UPDATE_ICON_STATE)
GLOB.chicken_count += 1
@@ -325,11 +325,14 @@ GLOBAL_VAR_INIT(chicken_count, 0)
attacktext = "pecks"
health = 50
maxHealth = 50
- can_collar = TRUE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
gold_core_spawnable = FRIENDLY_SPAWN
footstep_type = FOOTSTEP_MOB_CLAW
+/mob/living/simple_animal/turkey/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/mob/living/simple_animal/goose
name = "goose"
desc = "A pretty goose. Would make a nice comforter."
@@ -350,11 +353,14 @@ GLOBAL_VAR_INIT(chicken_count, 0)
attacktext = "kicks"
health = 50
maxHealth = 50
- can_collar = TRUE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
gold_core_spawnable = FRIENDLY_SPAWN
footstep_type = FOOTSTEP_MOB_CLAW
+/mob/living/simple_animal/goose/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/mob/living/simple_animal/seal
name = "seal"
desc = "A beautiful white seal."
@@ -375,11 +381,14 @@ GLOBAL_VAR_INIT(chicken_count, 0)
attacktext = "kicks"
health = 50
maxHealth = 50
- can_collar = TRUE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
gold_core_spawnable = FRIENDLY_SPAWN
blood_volume = BLOOD_VOLUME_NORMAL
+/mob/living/simple_animal/seal/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/mob/living/simple_animal/walrus
name = "walrus"
desc = "A big brown walrus."
@@ -400,11 +409,14 @@ GLOBAL_VAR_INIT(chicken_count, 0)
attacktext = "kicks"
health = 50
maxHealth = 50
- can_collar = TRUE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
gold_core_spawnable = FRIENDLY_SPAWN
blood_volume = BLOOD_VOLUME_NORMAL
+/mob/living/simple_animal/walrus/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/obj/item/udder
name = "udder"
diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm
index 4932f70e997..4119e34aec3 100644
--- a/code/modules/mob/living/simple_animal/friendly/lizard.dm
+++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm
@@ -23,13 +23,16 @@
can_hide = TRUE
pass_door_while_hidden = TRUE
butcher_results = list(/obj/item/food/meat = 1)
- can_collar = TRUE
mob_biotypes = MOB_ORGANIC | MOB_BEAST | MOB_REPTILE
gold_core_spawnable = FRIENDLY_SPAWN
var/eating_sound = 'sound/weapons/bite.ogg'
/// Lizards start with a tail
var/has_tail = TRUE
+/mob/living/simple_animal/lizard/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+
/mob/living/simple_animal/lizard/handle_automated_action()
if(stat == CONSCIOUS && !buckled)
if(prob(1))
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index c2069cc539f..bab3b8564f0 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -34,12 +34,12 @@
can_hide = TRUE
pass_door_while_hidden = TRUE
holder_type = /obj/item/holder/mouse
- can_collar = TRUE
gold_core_spawnable = FRIENDLY_SPAWN
var/chew_probability = 1
/mob/living/simple_animal/mouse/Initialize(mapload)
. = ..()
+ AddElement(/datum/element/wears_collar)
AddComponent(/datum/component/squeak, list('sound/creatures/mousesqueak.ogg' = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) //as quiet as a mouse or whatever
/mob/living/simple_animal/mouse/handle_automated_action()
diff --git a/code/modules/mob/living/simple_animal/friendly/nian_caterpillar.dm b/code/modules/mob/living/simple_animal/friendly/nian_caterpillar.dm
index 591b9d9ba84..74a9769dfc3 100644
--- a/code/modules/mob/living/simple_animal/friendly/nian_caterpillar.dm
+++ b/code/modules/mob/living/simple_animal/friendly/nian_caterpillar.dm
@@ -41,7 +41,6 @@
attack_sound = 'sound/weapons/bite.ogg'
holder_type = /obj/item/holder/nian_caterpillar
- can_collar = TRUE
/// Evolution action.
var/datum/action/innate/nian_caterpillar_emerge/evolve_action = new()
@@ -51,6 +50,7 @@
/mob/living/simple_animal/nian_caterpillar/Initialize(mapload)
. = ..()
real_name = name
+ AddElement(/datum/element/wears_collar)
add_language("Tkachi")
evolve_action.Grant(src)
diff --git a/code/modules/mob/living/simple_animal/friendly/penguin.dm b/code/modules/mob/living/simple_animal/friendly/penguin.dm
index 1f554d3552f..c2f16d8f348 100644
--- a/code/modules/mob/living/simple_animal/friendly/penguin.dm
+++ b/code/modules/mob/living/simple_animal/friendly/penguin.dm
@@ -19,6 +19,7 @@
/mob/living/simple_animal/pet/penguin/Initialize(mapload)
. = ..()
+ AddElement(/datum/element/wears_collar)
AddElement(/datum/element/waddling)
/mob/living/simple_animal/pet/penguin/emperor
diff --git a/code/modules/mob/living/simple_animal/friendly/pet.dm b/code/modules/mob/living/simple_animal/friendly/pet.dm
index 45ace13f41c..1697c08a850 100644
--- a/code/modules/mob/living/simple_animal/friendly/pet.dm
+++ b/code/modules/mob/living/simple_animal/friendly/pet.dm
@@ -3,7 +3,6 @@
mob_size = MOB_SIZE_SMALL
mob_biotypes = MOB_ORGANIC | MOB_BEAST
blood_volume = BLOOD_VOLUME_NORMAL
- can_collar = TRUE
speed = 0 // same speed as a person.
hud_type = /datum/hud/corgi
diff --git a/code/modules/mob/living/simple_animal/friendly/sloth.dm b/code/modules/mob/living/simple_animal/friendly/sloth.dm
index 1f911f1a10d..c30f73aa5bf 100644
--- a/code/modules/mob/living/simple_animal/friendly/sloth.dm
+++ b/code/modules/mob/living/simple_animal/friendly/sloth.dm
@@ -25,6 +25,9 @@
speed = 2
footstep_type = FOOTSTEP_MOB_CLAW
+/mob/living/simple_animal/pet/sloth/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
//IAA Sloth
/mob/living/simple_animal/pet/sloth/paperwork
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index a510384adbf..f87fe41d9a8 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -33,7 +33,6 @@
icon_dead = "parrot_dead"
icon_resting = "parrot_sit"
pass_flags = PASSTABLE
- can_collar = TRUE
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
faction = list("neutral", "jungle")
@@ -127,7 +126,7 @@
/obj/machinery/computer/guestpass
))
-/mob/living/simple_animal/parrot/add_strippable_element()
+ AddElement(/datum/element/wears_collar)
AddElement(/datum/element/strippable, GLOB.strippable_parrot_items)
/mob/living/simple_animal/parrot/Destroy()
diff --git a/code/modules/mob/living/simple_animal/parrot_stripping.dm b/code/modules/mob/living/simple_animal/parrot_stripping.dm
index 8de380b3a9f..f1be160dad4 100644
--- a/code/modules/mob/living/simple_animal/parrot_stripping.dm
+++ b/code/modules/mob/living/simple_animal/parrot_stripping.dm
@@ -1,6 +1,5 @@
GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
/datum/strippable_item/parrot_headset,
- /datum/strippable_item/pet_collar
)))
/datum/strippable_item/parrot_headset
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 349c82e35da..158a24e09ae 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -87,13 +87,8 @@
/// Allows a mob to pass unbolted doors while hidden
var/pass_door_while_hidden = FALSE
- var/obj/item/petcollar/pcollar = null
- /// If the mob has collar sprites, define them.
- var/collar_type
/// If the mob can be renamed
var/unique_pet = FALSE
- /// Can add collar to mob or not, use the set_can_collar if you want to change this on runtime
- var/can_collar = FALSE
/// Hot simple_animal baby making vars
var/list/childtype = null
@@ -152,12 +147,8 @@
if(can_hide)
var/datum/action/innate/hide/hide = new()
hide.Grant(src)
- if(pcollar)
- pcollar = new(src)
- regenerate_icons()
if(footstep_type)
AddComponent(/datum/component/footstep, footstep_type)
- add_strippable_element()
apply_atmos_requirements()
AddElement(/datum/element/body_temperature, minbodytemp, maxbodytemp, cold_damage_per_tick, heat_damage_per_tick)
@@ -165,7 +156,6 @@
/mob/living/simple_animal/Destroy()
/// We need to clear the reference to where we're walking to properly GC
GLOB.move_manager.stop_looping(src)
- QDEL_NULL(pcollar)
for(var/datum/action/innate/hide/hide in actions)
hide.Remove(src)
master_commander = null
@@ -200,11 +190,6 @@
/mob/living/simple_animal/proc/remove_atmos_requirements()
RemoveElement(/datum/element/atmos_requirements)
-/mob/living/simple_animal/handle_atom_del(atom/A)
- if(A == pcollar)
- pcollar = null
- return ..()
-
/mob/living/simple_animal/examine(mob/user)
. = ..()
if(stat == DEAD)
@@ -222,9 +207,6 @@
..()
if(icon_resting && stat != DEAD)
icon_state = icon_resting
- if(collar_type)
- collar_type = "[initial(collar_type)]_rest"
- regenerate_icons()
if(!can_crawl)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, LYING_DOWN_TRAIT) //simple mobs cannot crawl (unless they can)
@@ -232,9 +214,6 @@
..()
if(icon_resting && stat != DEAD)
icon_state = icon_living
- if(collar_type)
- collar_type = "[initial(collar_type)]"
- regenerate_icons()
/mob/living/simple_animal/update_stat(reason = "none given")
if(status_flags & GODMODE)
@@ -316,10 +295,8 @@
for(var/path in butcher_results)
for(var/i in 1 to butcher_results[path])
new path(Tsec)
- if(pcollar)
- pcollar.forceMove(drop_location())
- pcollar = null
..()
+
/mob/living/simple_animal/say_quote(message)
var/verb = "says"
@@ -353,9 +330,6 @@
density = initial(density)
if(TRAIT_FLYING in initial_traits)
ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
- if(collar_type)
- collar_type = "[initial(collar_type)]"
- regenerate_icons()
/mob/living/simple_animal/death(gibbed)
// Only execute the below if we successfully died
@@ -389,9 +363,6 @@
if(flip_on_death)
transform = transform.Turn(180)
density = FALSE
- if(collar_type)
- collar_type = "[initial(collar_type)]_dead"
- regenerate_icons()
/mob/living/simple_animal/proc/CanAttack(atom/the_target)
if(see_invisible < the_target.invisibility)
@@ -472,20 +443,11 @@
/mob/living/simple_animal/get_item_by_slot(slot_id)
switch(slot_id)
if(ITEM_SLOT_COLLAR)
- return pcollar
+ return (locate(/obj/item/petcollar) in src)
. = ..()
/mob/living/simple_animal/can_equip(obj/item/I, slot, disable_warning = 0)
- // . = ..() // Do not call parent. We do not want animals using their hand slots.
- switch(slot)
- if(ITEM_SLOT_COLLAR)
- if(pcollar)
- return FALSE
- if(!can_collar)
- return FALSE
- if(!istype(I, /obj/item/petcollar))
- return FALSE
- return TRUE
+ return FALSE
/mob/living/simple_animal/equip_to_slot(obj/item/W, slot, initial = FALSE)
if(!istype(W))
@@ -497,23 +459,15 @@
W.layer = ABOVE_HUD_LAYER
W.plane = ABOVE_HUD_PLANE
- switch(slot)
- if(ITEM_SLOT_COLLAR)
- add_collar(W)
-
/mob/living/simple_animal/unequip_to(obj/item/target, atom/destination, force = FALSE, silent = FALSE, drop_inventory = TRUE, no_move = FALSE)
. = ..()
if(!. || !target)
return
- if(target == pcollar)
- pcollar = null
- regenerate_icons()
-
/mob/living/simple_animal/get_access()
. = ..()
- if(pcollar)
- . |= pcollar.GetAccess()
+ for(var/obj/item/petcollar/collar in contents)
+ . |= collar.GetAccess()
/mob/living/simple_animal/update_transform()
var/matrix/ntransform = matrix(transform) //aka transform.Copy()
@@ -587,41 +541,6 @@
LAZYREMOVE(idle_mobs_on_old_z, src)
toggle_ai(initial(AIStatus))
-/mob/living/simple_animal/proc/add_collar(obj/item/petcollar/P, mob/user)
- if(!istype(P) || QDELETED(P) || pcollar)
- return
- if(user && !user.drop_item_to_ground(P))
- return
- P.forceMove(src)
- P.equipped(src)
- pcollar = P
- regenerate_icons()
- if(user)
- to_chat(user, "You put [P] around [src]'s neck.")
- if(P.tagname && !unique_pet)
- name = P.tagname
- real_name = P.tagname
-
-/mob/living/simple_animal/proc/remove_collar(atom/new_loc, mob/user)
- if(!pcollar)
- return
-
- var/obj/old_collar = pcollar
-
- drop_item_to_ground(pcollar)
-
- if(user)
- user.put_in_hands(old_collar)
-
- return old_collar
-
-
-/mob/living/simple_animal/regenerate_icons()
- cut_overlays()
- if(pcollar && collar_type)
- add_overlay("[collar_type]collar")
- add_overlay("[collar_type]tag")
-
/mob/living/simple_animal/Login()
..()
GLOB.move_manager.stop_looping(src) // if mob is moving under ai control, then stop AI movement
@@ -639,16 +558,3 @@
/mob/living/simple_animal/proc/end_dchat_plays()
stop_automated_movement = FALSE
-
-/mob/living/simple_animal/proc/set_can_collar(new_value)
- can_collar = (new_value ? TRUE : FALSE)
- if(can_collar)
- add_strippable_element()
- return
- remove_collar(drop_location())
- RemoveElement(/datum/element/strippable)
-
-/mob/living/simple_animal/proc/add_strippable_element()
- if(!can_collar)
- return
- AddElement(/datum/element/strippable, create_strippable_list(list(/datum/strippable_item/pet_collar)))
diff --git a/code/modules/mob/mob_misc_procs.dm b/code/modules/mob/mob_misc_procs.dm
index 2ed23918833..c19669ec952 100644
--- a/code/modules/mob/mob_misc_procs.dm
+++ b/code/modules/mob/mob_misc_procs.dm
@@ -5,13 +5,6 @@
return 1
return 0
-/proc/ispet(A)
- if(isanimal(A))
- var/mob/living/simple_animal/SA = A
- if(SA.can_collar)
- return 1
- return 0
-
/mob/proc/get_screen_colour()
SHOULD_CALL_PARENT(TRUE)
// OOC Colourblind setting takes priority over everything else.
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index 91077b379f9..be67faed004 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -253,7 +253,7 @@
SM.faction = user.faction
SM.master_commander = user
SM.sentience_act()
- SM.set_can_collar(TRUE)
+ SM.AddElement(/datum/element/wears_collar)
to_chat(SM, "All at once it makes sense: you know what you are and who you are! Self awareness is yours!")
to_chat(SM, "You are grateful to be self aware and owe [user] a great debt. Serve [user], and assist [user.p_them()] in completing [user.p_their()] goals at any cost.")
if(SM.flags_2 & HOLOGRAM_2) //Check to see if it's a holodeck creature
@@ -322,7 +322,7 @@
SM.universal_speak = TRUE
SM.faction = user.faction
SM.sentience_act() //Same deal here as with sentience
- SM.set_can_collar(TRUE)
+ SM.AddElement(/datum/element/wears_collar)
user.death()
to_chat(SM, "In a quick flash, you feel your consciousness flow into [SM]!")
to_chat(SM, "You are now [SM]. Your allegiances, alliances, and roles are still the same as they were prior to consciousness transfer!")
diff --git a/code/tests/game_tests.dm b/code/tests/game_tests.dm
index 7da27df4b8d..38fa65a7be4 100644
--- a/code/tests/game_tests.dm
+++ b/code/tests/game_tests.dm
@@ -47,6 +47,7 @@
#include "test_subsystem_init.dm"
#include "test_subsystem_metric_sanity.dm"
#include "test_timer_sanity.dm"
+#include "test_wears_collar.dm"
#endif
#ifdef MAP_TESTS
diff --git a/code/tests/test_wears_collar.dm b/code/tests/test_wears_collar.dm
new file mode 100644
index 00000000000..46a473d89e7
--- /dev/null
+++ b/code/tests/test_wears_collar.dm
@@ -0,0 +1,24 @@
+/datum/game_test/wears_collar/Run()
+ var/datum/test_puppeteer/player = new(src)
+
+ var/obj/chair = player.spawn_obj_nearby(/obj/structure/chair)
+ var/mob/corgi = player.spawn_mob_nearby(/mob/living/simple_animal/pet/dog/corgi)
+ chair.buckle_mob(corgi) // So it doesn't wander off
+
+ var/obj/item/petcollar/collar = player.spawn_obj_in_hand(/obj/item/petcollar)
+ player.click_on(corgi)
+ TEST_ASSERT(collar in corgi, "Collar not placed on corgi")
+ TEST_ASSERT_EQUAL(corgi.name, "corgi", "animal name not preserved")
+
+ qdel(collar)
+ qdel(corgi)
+
+ corgi = player.spawn_mob_nearby(/mob/living/simple_animal/pet/dog/corgi)
+ chair.buckle_mob(corgi) // So it doesn't wander off
+
+ collar = player.spawn_obj_in_hand(/obj/item/petcollar)
+ collar.tagname = "Bucephalus"
+ player.click_on(corgi)
+ TEST_ASSERT(collar in corgi, "Collar not placed on corgi")
+ TEST_ASSERT_EQUAL(corgi.name, "Bucephalus", "animal name not changed")
+
diff --git a/icons/mob/pets.dmi b/icons/mob/pets.dmi
index 66e406c42c5..9352135ba5f 100644
Binary files a/icons/mob/pets.dmi and b/icons/mob/pets.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 4da7f497ee5..c69c1fef5cd 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -601,6 +601,7 @@
#include "code\datums\elements\shatters_when_thrown.dm"
#include "code\datums\elements\strippable.dm"
#include "code\datums\elements\waddling.dm"
+#include "code\datums\elements\wears_collar.dm"
#include "code\datums\helper_datums\async_input.dm"
#include "code\datums\helper_datums\construction_datum.dm"
#include "code\datums\helper_datums\icon_snapshot.dm"