diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm
index e519fb25240..6696da857c8 100644
--- a/code/__DEFINES/basic_mobs.dm
+++ b/code/__DEFINES/basic_mobs.dm
@@ -33,6 +33,9 @@
///keeps track of how many gutlunches are born
GLOBAL_VAR_INIT(gutlunch_count, 0)
+///Pet customization settings saved for every client
+GLOBAL_LIST_EMPTY(customized_pets)
+
//raptor defines
#define RAPTOR_RED "Red"
diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm
index 2137aea08f3..62f30855abb 100644
--- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm
+++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm
@@ -229,6 +229,21 @@
icon_file = 'icons/obj/toys/plushes.dmi'
json_config = 'code/datums/greyscale/json_configs/plushie_carp.json'
+/datum/greyscale_config/pet_carrier
+ name = "Pet Carrier"
+ icon_file = 'icons/obj/pet_carrier.dmi'
+ json_config = 'code/datums/greyscale/json_configs/pet_carrier.json'
+
+/datum/greyscale_config/pet_carrier_inhands_left
+ name = "Pet Carrier Left"
+ icon_file = 'icons/mob/inhands/items_lefthand.dmi'
+ json_config = 'code/datums/greyscale/json_configs/pet_carrier_inhands.json'
+
+/datum/greyscale_config/pet_carrier_inhands_right
+ name = "Pet Carrier Right"
+ icon_file = 'icons/mob/inhands/items_righthand.dmi'
+ json_config = 'code/datums/greyscale/json_configs/pet_carrier_inhands.json'
+
/datum/greyscale_config/plush_lizard
name = "Plushie Lizard"
icon_file = 'icons/obj/toys/plushes.dmi'
diff --git a/code/datums/greyscale/json_configs/bandanaskull_inhands.json b/code/datums/greyscale/json_configs/bandanaskull_inhands.json
index b7067cf3c2e..40fade07973 100644
--- a/code/datums/greyscale/json_configs/bandanaskull_inhands.json
+++ b/code/datums/greyscale/json_configs/bandanaskull_inhands.json
@@ -14,3 +14,4 @@
}
]
}
+
diff --git a/code/datums/greyscale/json_configs/pet_carrier.json b/code/datums/greyscale/json_configs/pet_carrier.json
new file mode 100644
index 00000000000..1674765268f
--- /dev/null
+++ b/code/datums/greyscale/json_configs/pet_carrier.json
@@ -0,0 +1,87 @@
+{
+ "pet_carrier_open": [
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_open",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_gags",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "pet_carrier_closed_unlocked": [
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_unlocked",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_closed",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_gags",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "pet_carrier_closed_locked": [
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_locked",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_closed",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_gags",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "pet_carrier_occupied_unlocked": [
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_unlocked",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_occupied",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_gags",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "pet_carrier_occupied_locked": [
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_locked",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_occupied",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_gags",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ]
+}
diff --git a/code/datums/greyscale/json_configs/pet_carrier_inhands.json b/code/datums/greyscale/json_configs/pet_carrier_inhands.json
new file mode 100644
index 00000000000..05978e3c829
--- /dev/null
+++ b/code/datums/greyscale/json_configs/pet_carrier_inhands.json
@@ -0,0 +1,15 @@
+{
+ "pet_carrier": [
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier",
+ "blend_mode": "overlay"
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "pet_carrier_gags",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ]
+}
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
index 74a8e542400..2fb7072f55d 100644
--- a/code/datums/station_traits/neutral_traits.dm
+++ b/code/datums/station_traits/neutral_traits.dm
@@ -544,3 +544,47 @@
dynamic_category = RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
threat_reduction = 15
dynamic_threat_id = "Background Checks"
+
+
+/datum/station_trait/pet_day
+ name = "Bring Your Pet To Work Day"
+ trait_type = STATION_TRAIT_NEUTRAL
+ show_in_report = FALSE
+ weight = 2
+ sign_up_button = TRUE
+
+/datum/station_trait/pet_day/New()
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn))
+
+/datum/station_trait/pet_day/setup_lobby_button(atom/movable/screen/lobby/button/sign_up/lobby_button)
+ lobby_button.desc = "Want to bring your innocent pet to a giant metal deathtrap? Click here to customize it!"
+ RegisterSignal(lobby_button, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_lobby_button_update_overlays))
+ return ..()
+
+/datum/station_trait/pet_day/can_display_lobby_button(client/player)
+ return sign_up_button
+
+/datum/station_trait/pet_day/on_round_start()
+ return
+
+/datum/station_trait/pet_day/on_lobby_button_click(atom/movable/screen/lobby/button/sign_up/lobby_button, updates)
+ var/mob/our_player = lobby_button.get_mob()
+ var/client/player_client = our_player.client
+ if(isnull(player_client))
+ return
+ var/datum/pet_customization/customization = GLOB.customized_pets[REF(player_client)]
+ if(isnull(customization))
+ customization = new(player_client)
+ INVOKE_ASYNC(customization, TYPE_PROC_REF(/datum, ui_interact), our_player)
+
+/datum/station_trait/pet_day/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
+ SIGNAL_HANDLER
+
+ var/datum/pet_customization/customization = GLOB.customized_pets[REF(player_client)]
+ if(isnull(customization))
+ return
+ INVOKE_ASYNC(customization, TYPE_PROC_REF(/datum/pet_customization, create_pet), spawned, player_client)
+
+/datum/station_trait/pet_day/proc/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays)
+ overlays += "select_pet"
diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm
index 8be08a876d7..336d177f2a2 100644
--- a/code/game/objects/items/pet_carrier.dm
+++ b/code/game/objects/items/pet_carrier.dm
@@ -11,6 +11,10 @@
inhand_icon_state = "pet_carrier"
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ greyscale_config = /datum/greyscale_config/pet_carrier
+ greyscale_config_inhand_left = /datum/greyscale_config/pet_carrier_inhands_left
+ greyscale_config_inhand_right = /datum/greyscale_config/pet_carrier_inhands_right
+ greyscale_colors = COLOR_BLUE
force = 5
attack_verb_continuous = list("bashes", "carries")
attack_verb_simple = list("bash", "carry")
@@ -19,7 +23,6 @@
throw_range = 3
custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 7.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT)
interaction_flags_mouse_drop = NEED_DEXTERITY
-
var/open = TRUE
var/locked = FALSE
var/list/occupants = list()
@@ -147,14 +150,9 @@
if(open)
icon_state = initial(icon_state)
return ..()
- icon_state = "[base_icon_state]_[!occupants.len ? "closed" : "occupied"]"
+ icon_state = "[base_icon_state]_[!occupants.len ? "closed" : "occupied"]_[locked ? "locked" : "unlocked"]"
return ..()
-/obj/item/pet_carrier/update_overlays()
- . = ..()
- if(!open)
- . += "[base_icon_state]_[locked ? "" : "un"]locked"
-
/obj/item/pet_carrier/mouse_drop_dragged(atom/over_atom, mob/user, src_location, over_location, params)
if(isopenturf(over_atom) && open && occupants.len)
user.visible_message(span_notice("[user] unloads [src]."), \
@@ -202,5 +200,9 @@
base_icon_state = "biopod"
icon_state = "biopod_open"
inhand_icon_state = "biopod"
+ greyscale_config = null
+ greyscale_config_inhand_left = null
+ greyscale_config_inhand_right = null
+ greyscale_colors = null
#undef pet_carrier_full
diff --git a/code/modules/mob/living/basic/pets/cat/cat.dm b/code/modules/mob/living/basic/pets/cat/cat.dm
index 7394d1fd5c5..c911fa64c20 100644
--- a/code/modules/mob/living/basic/pets/cat/cat.dm
+++ b/code/modules/mob/living/basic/pets/cat/cat.dm
@@ -45,6 +45,14 @@
/obj/item/food/deadmouse,
/obj/item/food/fishmeat,
)
+ ///list of pet commands we follow
+ var/static/list/pet_commands = list(
+ /datum/pet_command/idle,
+ /datum/pet_command/free,
+ /datum/pet_command/follow,
+ /datum/pet_command/perform_trick_sequence,
+ )
+
///item we are currently holding
var/obj/item/held_food
///mutable appearance for held item
@@ -74,6 +82,7 @@
/mob/living/basic/pet/cat/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/cultist_pet, pet_cult_icon_state = cult_icon_state)
AddElement(/datum/element/wears_collar, collar_icon_state = collar_icon_state, collar_resting_icon_state = TRUE)
AddElement(/datum/element/ai_retaliate)
@@ -224,3 +233,9 @@
name = "Jerry"
desc = "Tom is VERY amused."
gender = MALE
+
+/mob/living/basic/pet/cat/tabby
+ icon_state = "cat"
+ icon_living = "cat"
+ icon_dead = "cat_dead"
+ held_state = "cat"
diff --git a/code/modules/mob/living/basic/pets/cat/cat_ai.dm b/code/modules/mob/living/basic/pets/cat/cat_ai.dm
index 8dde1049568..2a2d3204bad 100644
--- a/code/modules/mob/living/basic/pets/cat/cat_ai.dm
+++ b/code/modules/mob/living/basic/pets/cat/cat_ai.dm
@@ -1,6 +1,7 @@
/datum/ai_controller/basic_controller/cat
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_HOSTILE_MEOWS = list("Mawwww", "Mrewwww", "mhhhhng..."),
BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/cat),
BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/cat/kitten),
@@ -9,6 +10,7 @@
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+ /datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/reside_in_home,
/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle,
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice,
diff --git a/code/modules/mob/living/basic/pets/cat/kitten_ai.dm b/code/modules/mob/living/basic/pets/cat/kitten_ai.dm
index b694235a39e..4136aa0aa01 100644
--- a/code/modules/mob/living/basic/pets/cat/kitten_ai.dm
+++ b/code/modules/mob/living/basic/pets/cat/kitten_ai.dm
@@ -3,10 +3,12 @@
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_HUNGRY_MEOW = list("mrrp...", "mraw..."),
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_MAX_DISTANCE_TO_FOOD = 2,
)
planning_subtrees = list(
+ /datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/target_retaliate,
/datum/ai_planning_subtree/flee_target,
/datum/ai_planning_subtree/beg_human,
diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm
index 1ff991941d3..35996d4d77c 100644
--- a/code/modules/mob/living/basic/pets/dog/_dog.dm
+++ b/code/modules/mob/living/basic/pets/dog/_dog.dm
@@ -40,6 +40,7 @@
/datum/pet_command/free,
/datum/pet_command/good_boy/dog,
/datum/pet_command/follow/dog,
+ /datum/pet_command/perform_trick_sequence,
/datum/pet_command/point_targeting/attack/dog,
/datum/pet_command/point_targeting/fetch,
/datum/pet_command/play_dead,
diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm
index a442f607158..c45ff27bbc3 100644
--- a/code/modules/mob/living/basic/pets/fox.dm
+++ b/code/modules/mob/living/basic/pets/fox.dm
@@ -28,9 +28,19 @@
attack_sound = 'sound/items/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
ai_controller = /datum/ai_controller/basic_controller/fox
+ ///list of our pet commands we follow
+ var/static/list/pet_commands = list(
+ /datum/pet_command/idle,
+ /datum/pet_command/free,
+ /datum/pet_command/follow,
+ /datum/pet_command/point_targeting/attack,
+ /datum/pet_command/perform_trick_sequence,
+ )
+
/mob/living/basic/pet/fox/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/cultist_pet)
AddElement(/datum/element/wears_collar)
AddElement(/datum/element/pet_bonus, "pants and yaps happily!")
@@ -42,12 +52,14 @@
blackboard = list(
BB_ALWAYS_IGNORE_FACTION = TRUE,
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller,
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
)
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+ /datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/target_retaliate/to_flee,
/datum/ai_planning_subtree/flee_target/from_flee_key,
/datum/ai_planning_subtree/simple_find_target/not_while_observed,
diff --git a/code/modules/mob/living/basic/pets/parrot/_parrot.dm b/code/modules/mob/living/basic/pets/parrot/_parrot.dm
index e61b0522179..e22948848eb 100644
--- a/code/modules/mob/living/basic/pets/parrot/_parrot.dm
+++ b/code/modules/mob/living/basic/pets/parrot/_parrot.dm
@@ -76,6 +76,13 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
var/static/list/edibles = list(
/obj/item/food/cracker,
)
+ ///list of commands we follow
+ var/static/list/pet_commands = list(
+ /datum/pet_command/idle,
+ /datum/pet_command/free,
+ /datum/pet_command/follow,
+ /datum/pet_command/perform_trick_sequence,
+ )
/// Tracks the times when we send a phrase through either being pet or attack to ensure it's not abused to flood
COOLDOWN_DECLARE(forced_speech_cooldown)
@@ -92,7 +99,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
AddElement(/datum/element/simple_flying)
AddComponent(/datum/component/listen_and_repeat, desired_phrases = get_static_list_of_phrases(), blackboard_key = BB_PARROT_REPEAT_STRING)
AddComponent(/datum/component/tameable, food_types = edibles, tame_chance = 100, bonus_tame_chance = 0)
-
+ AddComponent(/datum/component/obeys_commands, pet_commands)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attacking))
RegisterSignal(src, COMSIG_MOB_CLICKON, PROC_REF(on_click))
RegisterSignal(src, COMSIG_ATOM_ATTACKBY_SECONDARY, PROC_REF(on_attacked)) // this means we could have a peaceful interaction, like getting a cracker
diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm
index 8a0fe5e157d..2d6bccbc703 100644
--- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm
+++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm
@@ -1,6 +1,7 @@
/datum/ai_controller/basic_controller/parrot
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items,
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_HOARD_LOCATION_RANGE = 9,
)
@@ -9,11 +10,12 @@
idle_behavior = /datum/idle_behavior/idle_random_walk/parrot
planning_subtrees = list(
+ /datum/ai_planning_subtree/parrot_as_in_repeat, // always get a witty oneliner in when you can
+ /datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/target_retaliate,
/datum/ai_planning_subtree/perch_on_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/hoard_items,
- /datum/ai_planning_subtree/parrot_as_in_repeat, // always get a witty oneliner in when you can
)
/datum/idle_behavior/idle_random_walk/parrot
diff --git a/code/modules/mob/living/basic/pets/pet_designer.dm b/code/modules/mob/living/basic/pets/pet_designer.dm
new file mode 100644
index 00000000000..a0cc7ca4018
--- /dev/null
+++ b/code/modules/mob/living/basic/pets/pet_designer.dm
@@ -0,0 +1,205 @@
+#define PET_OPTION_DOG "Dog"
+#define PET_OPTION_CAT "Cat"
+#define PET_OPTION_FOX "Fox"
+#define PET_OPTION_VERMIN "Vermin"
+#define PET_OPTION_BIRD "Bird"
+
+GLOBAL_LIST_INIT(pet_options, list(
+ PET_OPTION_DOG = list(
+ /mob/living/basic/pet/dog/corgi,
+ /mob/living/basic/pet/dog/pug,
+ /mob/living/basic/pet/dog/bullterrier,
+ /mob/living/basic/pet/dog/corgi/puppy,
+ /mob/living/basic/pet/dog/corgi/exoticcorgi,
+ ),
+ PET_OPTION_CAT = list(
+ /mob/living/basic/pet/cat/tabby,
+ /mob/living/basic/pet/cat,
+ /mob/living/basic/pet/cat/kitten,
+ ),
+ PET_OPTION_FOX = list(
+ /mob/living/basic/pet/fox,
+ ),
+ PET_OPTION_VERMIN = list(
+ /mob/living/basic/mothroach,
+ /mob/living/basic/spider/maintenance,
+ /mob/living/basic/mouse,
+ ),
+ PET_OPTION_BIRD = list(
+ /mob/living/basic/parrot,
+ ),
+))
+
+/datum/pet_customization
+ ///our selected path type
+ var/mob/living/basic/selected_path = /mob/living/basic/pet/dog/corgi
+ ///current selected specie
+ var/pet_specie = PET_OPTION_DOG
+ ///custom name to apply to our pet
+ var/custom_name
+ ///type of pet carrier to give our pet
+ var/pet_carrier_path
+ ///pet collar color to give our pet
+ var/pet_collar_type
+ ///our pet's gender
+ var/pet_gender = MALE
+ ///trick name of our pet!
+ var/pet_trick_name = "Trick"
+ ///our pet's trick moves
+ var/list/pet_trick_moves
+ ///our selected carrier
+ var/pet_carrier
+ ///our cached pet carrier icons
+ var/static/list/custom_pet_carriers
+ ///possible emotes our pet can run
+ var/static/list/pet_possible_emotes = list(
+ /datum/emote/flip,
+ /datum/emote/jump,
+ /datum/emote/spin,
+ )
+ ///list of carrier colors we can pick from
+ var/static/list/possible_colors = list(
+ "Blue" = COLOR_BLUE,
+ "Red" = COLOR_RED,
+ "Yellow" = COLOR_YELLOW,
+ "Green" = COLOR_GREEN,
+ )
+
+/datum/pet_customization/New(client/player_client)
+ . = ..()
+ if(isnull(custom_pet_carriers))
+ custom_pet_carriers = setup_pet_carriers()
+ pet_carrier = possible_colors[1]
+ GLOB.customized_pets[REF(player_client)] = src
+
+/datum/pet_customization/proc/setup_pet_carriers()
+ var/list/list_to_return = list()
+
+ var/obj/item/pet_carrier/demo_carrier = new()
+ demo_carrier.open = FALSE
+ demo_carrier.update_appearance()
+
+ for(var/color in possible_colors)
+ demo_carrier.set_greyscale(possible_colors[color])
+ list_to_return[color] = icon2base64(getFlatIcon(demo_carrier))
+ qdel(demo_carrier)
+ return list_to_return
+
+/datum/pet_customization/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PetBuilder")
+ ui.open()
+
+/datum/pet_customization/ui_data(mob/user)
+ var/list/data = list()
+ data["pet_name"] = custom_name
+ data["pet_path"] = selected_path
+ data["pet_gender"] = pet_gender
+ data["pet_trick_name"] = pet_trick_name
+ data["pet_specie"] = pet_specie
+ data["pet_types"] = list()
+ data["pet_options"] = list()
+ for(var/pet_option in GLOB.pet_options)
+ data["pet_types"] += pet_option
+ data["pet_options"] += retrieve_pet_options(pet_option, GLOB.pet_options[pet_option])
+ data["pet_carrier"] = pet_carrier
+
+ data["carrier_options"] = list()
+ for(var/carrier_option in custom_pet_carriers)
+ data["carrier_options"] += list(list(
+ "carrier_color" = carrier_option,
+ "carrier_icon" = custom_pet_carriers[carrier_option],
+ ))
+
+ data["pet_possible_emotes"] = list()
+ for(var/datum/emote/emote as anything in pet_possible_emotes)
+ data["pet_possible_emotes"] += emote.key
+
+ return data
+
+/datum/pet_customization/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/pet_customization/proc/retrieve_pet_options(pet_specie, list/input_list)
+ var/list/pet_options = list()
+ for(var/mob/living/pet_type as anything in input_list)
+ pet_options += list(list(
+ "pet_specie" = pet_specie,
+ "pet_name" = pet_type::name,
+ "pet_icon" = pet_type:icon,
+ "pet_path" = pet_type,
+ "pet_icon_state" = pet_type::icon_state,
+ ))
+ return pet_options
+
+/datum/pet_customization/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ switch(action)
+ if("finalize_pet")
+
+ var/pet_type = text2path(params["selected_path"])
+ for(var/pet_category in GLOB.pet_options)
+ var/list/pet_list = GLOB.pet_options[pet_category]
+ if(pet_list.Find(pet_type))
+ selected_path = pet_type
+ break
+
+ var/trick_name = params["selected_trick_name"]
+ if(trick_name && sanitize_name(trick_name))
+ pet_trick_name = trick_name
+
+ var/pet_name = params["selected_pet_name"]
+ if(pet_name && sanitize_name(pet_name))
+ custom_name = pet_name
+
+ switch(params["selected_gender"])
+ if("male")
+ pet_gender = MALE
+ if("female")
+ pet_gender = FEMALE
+ if("neuter")
+ pet_gender = NEUTER
+
+ var/list/trick_moves = params["selected_trick_moves"]
+ if(length(trick_moves))
+ pet_trick_moves = trick_moves
+
+ var/selected_color = params["selected_carrier"]
+ if(!isnull(selected_color))
+ pet_carrier = selected_color
+
+ var/selected_specie = params["selected_specie"]
+ if(!isnull(selected_specie))
+ pet_specie = selected_specie
+ ui.close()
+
+ return TRUE
+
+/datum/pet_customization/proc/create_pet(mob/living/spawned, client/player_client)
+ var/obj/item/pet_carrier/carrier = new(get_turf(spawned))
+ carrier.open = FALSE
+ carrier.update_appearance()
+ var/final_color = possible_colors[pet_carrier] || COLOR_BLUE
+ carrier.set_greyscale(final_color)
+ var/mob/living/our_pet = new selected_path(get_turf(spawned)) //spawning these in nullspace leads to runtimes
+ our_pet.gender = pet_gender
+
+ if(custom_name)
+ our_pet.fully_replace_character_name(our_pet.name, custom_name)
+
+ if(pet_trick_name)
+ our_pet.ai_controller.set_blackboard_key(BB_TRICK_NAME, pet_trick_name)
+ if(pet_trick_moves)
+ our_pet.ai_controller.override_blackboard_key(BB_TRICK_SEQUENCE, pet_trick_moves)
+ our_pet.befriend(spawned)
+ carrier.add_occupant(our_pet)
+ spawned.put_in_hands(carrier, forced = TRUE)
+ GLOB.customized_pets -= REF(player_client)
+ qdel(src)
+
+#undef PET_OPTION_DOG
+#undef PET_OPTION_CAT
+#undef PET_OPTION_FOX
+#undef PET_OPTION_VERMIN
+#undef PET_OPTION_BIRD
diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm
index ae971fd2558..18ae6fc3e75 100644
--- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm
+++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm
@@ -44,11 +44,13 @@
/datum/ai_controller/basic_controller/giant_spider/pest
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller, // Hunt mobs our size
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are
)
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+ /datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/target_retaliate/to_flee,
/datum/ai_planning_subtree/flee_target/from_flee_key,
/datum/ai_planning_subtree/simple_find_target,
diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider.dm b/code/modules/mob/living/basic/space_fauna/spider/spider.dm
index 118487f0383..9f94d839a76 100644
--- a/code/modules/mob/living/basic/space_fauna/spider/spider.dm
+++ b/code/modules/mob/living/basic/space_fauna/spider/spider.dm
@@ -171,10 +171,18 @@
response_harm_simple = "splat"
ai_controller = /datum/ai_controller/basic_controller/giant_spider/pest
apply_spider_antag = FALSE
+ ///list of pet commands we follow
+ var/static/list/pet_commands = list(
+ /datum/pet_command/idle,
+ /datum/pet_command/free,
+ /datum/pet_command/follow,
+ /datum/pet_command/perform_trick_sequence,
+ )
/mob/living/basic/spider/maintenance/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
AddElement(/datum/element/web_walker, /datum/movespeed_modifier/average_web)
AddElement(/datum/element/ai_retaliate)
+ AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/tiny_mob_hunter)
diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm b/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm
index 9659408b8b1..d0605d4dc93 100644
--- a/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm
+++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm
@@ -33,11 +33,19 @@
faction = list(FACTION_NEUTRAL)
ai_controller = /datum/ai_controller/basic_controller/mothroach
+ ///list of pet commands we follow
+ var/static/list/pet_commands = list(
+ /datum/pet_command/idle,
+ /datum/pet_command/free,
+ /datum/pet_command/follow,
+ /datum/pet_command/perform_trick_sequence,
+ )
/mob/living/basic/mothroach/Initialize(mapload)
. = ..()
var/static/list/food_types = list(/obj/item/clothing)
AddElement(/datum/element/basic_eating, food_types = food_types)
+ AddComponent(/datum/component/obeys_commands, pet_commands)
ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(food_types))
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/pet_bonus, "squeaks happily!")
diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm b/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm
index 5801d183215..bed72a98239 100644
--- a/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm
+++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm
@@ -3,6 +3,7 @@
/datum/ai_controller/basic_controller/mothroach
blackboard = list(
BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items,
)
@@ -10,6 +11,7 @@
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+ /datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/find_food/mothroach,
/datum/ai_planning_subtree/target_retaliate/to_flee,
/datum/ai_planning_subtree/flee_target/from_flee_key,
diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm
index cb62ad956f9..aba7c00d4c7 100644
--- a/code/modules/mob/living/basic/vermin/mouse.dm
+++ b/code/modules/mob/living/basic/vermin/mouse.dm
@@ -36,6 +36,13 @@
var/contributes_to_ratcap = TRUE
/// Probability that, if we successfully bite a shocked cable, that we will die to it.
var/cable_zap_prob = 85
+ ///list of pet commands we follow
+ var/static/list/pet_commands = list(
+ /datum/pet_command/idle,
+ /datum/pet_command/free,
+ /datum/pet_command/follow,
+ /datum/pet_command/perform_trick_sequence,
+ )
/mob/living/basic/mouse/Initialize(mapload, tame = FALSE, new_body_color)
. = ..()
@@ -55,6 +62,7 @@
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
+ AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/connect_loc, loc_connections)
make_tameable()
AddComponent(/datum/component/swarming, 16, 16) //max_x, max_y
@@ -383,9 +391,8 @@
/// The mouse AI controller
/datum/ai_controller/basic_controller/mouse
blackboard = list( // Always cowardly
- BB_CURRENT_HUNTING_TARGET = null, // cheese
- BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // Use this to find people to run away from
+ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_BASIC_MOB_FLEE_DISTANCE = 3,
)
@@ -393,6 +400,7 @@
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+ /datum/ai_planning_subtree/pet_planning,
// Top priority is to look for and execute hunts for cheese even if someone is looking at us
/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese,
// Next priority is see if anyone is looking at us
diff --git a/icons/hud/lobby/signup_button.dmi b/icons/hud/lobby/signup_button.dmi
index e015fa71948..26d32e9af83 100644
Binary files a/icons/hud/lobby/signup_button.dmi and b/icons/hud/lobby/signup_button.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index ebcfea59675..f38ce001619 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index b8fd65ec072..4639b290fe3 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/obj/pet_carrier.dmi b/icons/obj/pet_carrier.dmi
index 0a2b4f6c430..366fddd4144 100644
Binary files a/icons/obj/pet_carrier.dmi and b/icons/obj/pet_carrier.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index f7f1ba6df14..7cd2ddfbf85 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -5079,6 +5079,7 @@
#include "code\modules\mob\living\basic\pets\fox.dm"
#include "code\modules\mob\living\basic\pets\penguin.dm"
#include "code\modules\mob\living\basic\pets\pet.dm"
+#include "code\modules\mob\living\basic\pets\pet_designer.dm"
#include "code\modules\mob\living\basic\pets\sloth.dm"
#include "code\modules\mob\living\basic\pets\cat\bread_cat_ai.dm"
#include "code\modules\mob\living\basic\pets\cat\cat.dm"
diff --git a/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx b/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx
index fb792f907f2..a214a0a969e 100644
--- a/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx
+++ b/tgui/packages/tgui/interfaces/LootPanel/IconDisplay.tsx
@@ -4,11 +4,18 @@ import { SearchItem } from './types';
type Props = {
item: SearchItem;
+ size: Size;
+};
+
+type Size = {
+ height: number;
+ width: number;
};
export function IconDisplay(props: Props) {
const {
item: { icon, icon_state },
+ size: { height, width },
} = props;
const fallback =