diff --git a/_maps/map_files/stations/boxstation.dmm b/_maps/map_files/stations/boxstation.dmm
index ad0a08b187f..66730154a33 100644
--- a/_maps/map_files/stations/boxstation.dmm
+++ b/_maps/map_files/stations/boxstation.dmm
@@ -14423,7 +14423,7 @@
},
/area/station/security/checkpoint/secondary)
"aZi" = (
-/mob/living/simple_animal/mouse/brown/tom,
+/mob/living/basic/mouse/brown/tom,
/turf/simulated/floor/plasteel{
dir = 6;
icon_state = "vault"
@@ -21783,7 +21783,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/cyan{
dir = 4
},
-/mob/living/simple_animal/mouse/gray,
+/mob/living/basic/mouse/gray,
/turf/simulated/floor/plasteel,
/area/station/maintenance/port)
"bwx" = (
diff --git a/_maps/map_files/stations/cerestation.dmm b/_maps/map_files/stations/cerestation.dmm
index 7a1adc0812a..74465b3b17a 100644
--- a/_maps/map_files/stations/cerestation.dmm
+++ b/_maps/map_files/stations/cerestation.dmm
@@ -29487,7 +29487,7 @@
/obj/structure/bed/dogbed{
name = "Brain's bed"
},
-/mob/living/simple_animal/mouse/white/brain,
+/mob/living/basic/mouse/white/brain,
/obj/machinery/economy/vending/wallmed/directional/north,
/turf/simulated/floor/plasteel{
dir = 1;
@@ -90544,7 +90544,7 @@
"rRO" = (
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
-/mob/living/simple_animal/mouse/brown/tom,
+/mob/living/basic/mouse/brown/tom,
/obj/machinery/hologram/holopad,
/turf/simulated/floor/plasteel{
icon_state = "vault"
diff --git a/_maps/map_files/stations/deltastation.dmm b/_maps/map_files/stations/deltastation.dmm
index cdeb49c7208..260a601287c 100644
--- a/_maps/map_files/stations/deltastation.dmm
+++ b/_maps/map_files/stations/deltastation.dmm
@@ -81451,7 +81451,7 @@
/area/station/security/prisonershuttle)
"rbC" = (
/obj/structure/bed/dogbed,
-/mob/living/simple_animal/mouse/white/brain,
+/mob/living/basic/mouse/white/brain,
/turf/simulated/floor/plasteel{
icon_state = "whitegreen"
},
@@ -86795,7 +86795,7 @@
c_tag = "Vault";
dir = 4
},
-/mob/living/simple_animal/mouse/brown/tom,
+/mob/living/basic/mouse/brown/tom,
/turf/simulated/floor/plasteel/dark,
/area/station/command/vault)
"tdA" = (
diff --git a/_maps/map_files/stations/emeraldstation.dmm b/_maps/map_files/stations/emeraldstation.dmm
index 7c72f269540..d94f69df2d1 100644
--- a/_maps/map_files/stations/emeraldstation.dmm
+++ b/_maps/map_files/stations/emeraldstation.dmm
@@ -895,7 +895,7 @@
/turf/simulated/floor/plating,
/area/station/maintenance/aft2)
"alr" = (
-/mob/living/simple_animal/mouse/brown/tom,
+/mob/living/basic/mouse/brown/tom,
/turf/simulated/floor/greengrid,
/area/station/command/vault)
"alG" = (
@@ -17504,7 +17504,7 @@
/area/station/maintenance/security/fore)
"dut" = (
/obj/structure/bed/dogbed,
-/mob/living/simple_animal/mouse/white/brain,
+/mob/living/basic/mouse/white/brain,
/obj/machinery/atmospherics/pipe/manifold/hidden/supply,
/obj/structure/disposalpipe/segment{
dir = 4
diff --git a/_maps/map_files/stations/metastation.dmm b/_maps/map_files/stations/metastation.dmm
index ea3034f6fcb..b051012f024 100644
--- a/_maps/map_files/stations/metastation.dmm
+++ b/_maps/map_files/stations/metastation.dmm
@@ -42110,7 +42110,7 @@
/turf/simulated/floor/plating,
/area/station/engineering/atmos/control)
"gRu" = (
-/mob/living/simple_animal/mouse/brown/tom,
+/mob/living/basic/mouse/brown/tom,
/obj/structure/sink/directional/north,
/turf/simulated/floor/plating,
/area/station/maintenance/fore2)
diff --git a/code/__DEFINES/mob_defines.dm b/code/__DEFINES/mob_defines.dm
index ef83e5fd368..13acb49f0bf 100644
--- a/code/__DEFINES/mob_defines.dm
+++ b/code/__DEFINES/mob_defines.dm
@@ -248,7 +248,7 @@
#define isdog(A) (istype((A), /mob/living/simple_animal/pet/dog))
#define iscorgi(A) (istype((A), /mob/living/simple_animal/pet/dog/corgi))
-#define ismouse(A) (istype((A), /mob/living/simple_animal/mouse))
+#define ismouse(A) (istype((A), /mob/living/basic/mouse))
#define isbot(A) (istype((A), /mob/living/simple_animal/bot))
#define isguardian(A) (istype((A), /mob/living/simple_animal/hostile/guardian))
#define isnymph(A) (istype((A), /mob/living/simple_animal/diona))
diff --git a/code/controllers/subsystem/non_firing/SSlate_mapping.dm b/code/controllers/subsystem/non_firing/SSlate_mapping.dm
index bf7e3b1b4ef..92b3a1020c1 100644
--- a/code/controllers/subsystem/non_firing/SSlate_mapping.dm
+++ b/code/controllers/subsystem/non_firing/SSlate_mapping.dm
@@ -67,8 +67,8 @@ SUBSYSTEM_DEF(late_mapping)
for(var/i in 1 to mice_number)
if(prob(1))
- new /mob/living/simple_animal/mouse/white/linter(pick_n_take(maintenance_turfs))
+ new /mob/living/basic/mouse/white/linter(pick_n_take(maintenance_turfs))
else
- new /mob/living/simple_animal/mouse(pick_n_take(maintenance_turfs))
+ new /mob/living/basic/mouse(pick_n_take(maintenance_turfs))
log_debug("Spawned [mice_number] mice over in [stop_watch(watch)]s")
diff --git a/code/datums/ai/basic_mobs/hunting_behavior/hunting_mouse.dm b/code/datums/ai/basic_mobs/hunting_behavior/hunting_mouse.dm
new file mode 100644
index 00000000000..4fe86a44fdd
--- /dev/null
+++ b/code/datums/ai/basic_mobs/hunting_behavior/hunting_mouse.dm
@@ -0,0 +1,41 @@
+// Mouse subtree to hunt down delicious cheese.
+/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese
+ finding_behavior = /datum/ai_behavior/find_hunt_target/mouse
+ hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/mouse
+ hunt_targets = list(/obj/item/food/sliced/cheesewedge)
+ hunt_range = 1
+ finish_planning = FALSE
+
+// Mouse subtree to hunt down ... delicious cabling?
+/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables
+ target_key = BB_LOW_PRIORITY_HUNTING_TARGET
+ hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/mouse
+ finding_behavior = /datum/ai_behavior/find_hunt_target/mouse_cable
+ hunt_targets = list(/obj/structure/cable)
+ hunt_range = 1
+ hunt_chance = 1
+ finish_planning = FALSE
+
+/datum/ai_behavior/find_hunt_target/mouse
+ behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
+
+// When looking for a cable, we can only bite things we can reach.
+/datum/ai_behavior/find_hunt_target/mouse_cable
+
+/datum/ai_behavior/find_hunt_target/mouse_cable/valid_dinner(mob/living/source, obj/structure/cable/dinner, radius)
+ . = ..()
+ if(!.)
+ return
+
+ var/turf/simulated/floor/F = get_turf(dinner)
+ if(F.intact || F.transparent_floor)
+ return FALSE
+ var/obj/structure/cable/C = locate() in F
+ if(C)
+ return TRUE
+ return FALSE
+
+// Our hunts have a decent cooldown.
+/datum/ai_behavior/hunt_target/interact_with_target/mouse
+ hunt_cooldown = 20 SECONDS
+ behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
diff --git a/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm b/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm
index ef80c889f4b..3d0369c7141 100644
--- a/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm
+++ b/code/datums/ai/targeting_strategy/basic_targeting_strategy.dm
@@ -95,3 +95,34 @@
/datum/targeting_strategy/basic/always_check_factions
check_factions_exactly = TRUE
+
+/// Subtype which searches for mobs of a size relative to ours
+/datum/targeting_strategy/basic/of_size
+ /// If true, we will return mobs which are smaller than us. If false, larger.
+ var/find_smaller = TRUE
+ /// If true, we will return mobs which are the same size as us.
+ var/inclusive = TRUE
+
+/datum/targeting_strategy/basic/of_size/can_attack(mob/living/owner, atom/target, vision_range)
+ if(!isliving(target))
+ return FALSE
+ . = ..()
+ if(!.)
+ return FALSE
+
+ var/mob/living/mob_target = target
+ if(inclusive && owner.mob_size == mob_target.mob_size)
+ return TRUE
+ if(owner.mob_size > mob_target.mob_size)
+ return find_smaller
+ return !find_smaller
+
+// This is just using the default values but the subtype makes it clearer
+/datum/targeting_strategy/basic/of_size/ours_or_smaller
+
+/datum/targeting_strategy/basic/of_size/larger
+ find_smaller = FALSE
+ inclusive = FALSE
+
+/datum/targeting_strategy/basic/of_size/smaller
+ inclusive = FALSE
diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm
index 3017cd731e9..741877bf1b5 100644
--- a/code/datums/components/pet_commands/pet_commands_basic.dm
+++ b/code/datums/components/pet_commands/pet_commands_basic.dm
@@ -43,6 +43,8 @@
speech_commands = list("heel", "follow")
///the behavior we use to follow
var/follow_behavior = /datum/ai_behavior/pet_follow_friend
+ /// should we activate immediately if we're doing nothing else and gain a friend?
+ var/activate_on_befriend = FALSE
/datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander)
. = ..()
@@ -55,6 +57,17 @@
controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
+/datum/pet_command/follow/add_new_friend(mob/living/tamer)
+ . = ..()
+ var/mob/living/parent = locateUID(parent_uid)
+ if(!parent)
+ return
+ if(activate_on_befriend && !parent.ai_controller.blackboard_key_exists(BB_ACTIVE_PET_COMMAND))
+ try_activate_command(tamer)
+
+/// Like follow but start active
+/datum/pet_command/follow/start_active
+ activate_on_befriend = TRUE
/**
* # Pet Command: Use ability
* Use an an ability that does not require any targets
diff --git a/code/datums/components/tameable.dm b/code/datums/components/tameable.dm
new file mode 100644
index 00000000000..b32f5dd74c3
--- /dev/null
+++ b/code/datums/components/tameable.dm
@@ -0,0 +1,68 @@
+/// This component lets you make specific mobs tameable by feeding them
+/datum/component/tameable
+ /// If true, this atom can only be domesticated by one person
+ var/unique
+ /// Starting success chance for taming.
+ var/tame_chance
+ /// Added success chance after every failed tame attempt.
+ var/bonus_tame_chance
+ /// Current chance to tame on interaction
+ var/current_tame_chance
+
+/datum/component/tameable/Initialize(food_types, tame_chance, bonus_tame_chance, unique = TRUE)
+ if(!ismob(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ if(tame_chance)
+ src.tame_chance = tame_chance
+ src.current_tame_chance = tame_chance
+ if(bonus_tame_chance)
+ src.bonus_tame_chance = bonus_tame_chance
+ src.unique = unique
+
+ if(food_types && !HAS_TRAIT(parent, TRAIT_MOB_EATER))
+ parent.AddElement(/datum/element/basic_eating, food_types_ = food_types)
+
+ RegisterSignal(parent, COMSIG_MOB_ATE, PROC_REF(try_tame))
+
+/datum/component/tameable/proc/try_tame(atom/source, obj/item/food, mob/living/attacker)
+ SIGNAL_HANDLER
+
+ if(isnull(attacker) || already_friends(attacker))
+ return
+
+ var/inform_tamer = FALSE
+ if(prob(current_tame_chance)) // note: lack of feedback message is deliberate, keep them guessing unless they're an expert!
+ on_tame(source, attacker, food, inform_tamer)
+ else
+ current_tame_chance += bonus_tame_chance
+
+/// Check if the passed mob is already considered one of our friends
+/datum/component/tameable/proc/already_friends(mob/living/potential_friend)
+ if(!isliving(parent))
+ return FALSE // Figure this out when we actually need it
+ var/mob/living/living_parent = parent
+ var/living_parent_id = living_parent.UID()
+ return living_parent.faction.Find(living_parent_id)
+
+/// Ran once taming succeeds
+/datum/component/tameable/proc/on_tame(mob/source, mob/living/tamer, obj/item/food, inform_tamer = FALSE)
+ SIGNAL_HANDLER
+ source.tamed(tamer, food)//Run custom behavior if needed
+
+ if(isliving(parent) && isliving(tamer))
+ INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, befriend), tamer)
+ if(unique)
+ qdel(src)
+ else
+ current_tame_chance = tame_chance
+
+/datum/component/tameable/proc/rename_pet(mob/living/animal, mob/living/tamer)
+ var/chosen_name = tgui_input_text(tamer, "Choose your pet's name!", "Name pet", animal.name, MAX_NAME_LEN)
+ if(QDELETED(animal) || chosen_name == animal.name)
+ return
+ if(!chosen_name)
+ to_chat(tamer, "Please enter a valid name.")
+ rename_pet(animal, tamer)
+ return
+ animal.rename_character(animal.name, chosen_name)
diff --git a/code/datums/spell_targeting/matter_eater_targeting.dm b/code/datums/spell_targeting/matter_eater_targeting.dm
index bb7efb91d0c..b739c1a2930 100644
--- a/code/datums/spell_targeting/matter_eater_targeting.dm
+++ b/code/datums/spell_targeting/matter_eater_targeting.dm
@@ -9,7 +9,7 @@
/mob/living/simple_animal/hostile,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/crab,
- /mob/living/simple_animal/mouse,
+ /mob/living/basic/mouse,
/mob/living/carbon/human,
/mob/living/simple_animal/slime,
/mob/living/carbon/alien/larva,
diff --git a/code/datums/spells/shapeshift.dm b/code/datums/spells/shapeshift.dm
index fee6e1197c3..ac3e5004acc 100644
--- a/code/datums/spells/shapeshift.dm
+++ b/code/datums/spells/shapeshift.dm
@@ -11,7 +11,7 @@
var/shapeshift_type
var/list/current_shapes = list()
var/list/current_casters = list()
- var/list/possible_shapes = list(/mob/living/simple_animal/mouse,
+ var/list/possible_shapes = list(/mob/living/basic/mouse,
/mob/living/simple_animal/pet/dog/corgi,
/mob/living/simple_animal/bot/ed209,
/mob/living/simple_animal/hostile/construct/armoured)
diff --git a/code/game/verbs/suicide.dm b/code/game/verbs/suicide.dm
index f6b6fb203b7..51919c640a5 100644
--- a/code/game/verbs/suicide.dm
+++ b/code/game/verbs/suicide.dm
@@ -52,7 +52,7 @@
/mob/living/simple_animal/do_suicide()
setOxyLoss((health * 1.5), TRUE)
-/mob/living/simple_animal/mouse/do_suicide()
+/mob/living/basic/mouse/do_suicide()
visible_message("[src] is playing dead permanently! It looks like [p_theyre()] trying to commit suicide!")
adjustOxyLoss(max(100 - getBruteLoss(100), 0))
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index 9f53b74059b..b9d77676226 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -66,7 +66,7 @@
affecting.receive_damage(1, 0)
else if(ismouse(target))
- var/mob/living/simple_animal/mouse/M = target
+ var/mob/living/basic/mouse/M = target
visible_message("SPLAT!")
M.death()
M.splat()
diff --git a/code/modules/events/blob_spawn.dm b/code/modules/events/blob_spawn.dm
index ec180703ab7..2bb5b87697c 100644
--- a/code/modules/events/blob_spawn.dm
+++ b/code/modules/events/blob_spawn.dm
@@ -13,7 +13,7 @@
INVOKE_ASYNC(src, PROC_REF(make_blob))
/datum/event/blob/proc/make_blob()
- var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob infested mouse?", ROLE_BLOB, TRUE, source = /mob/living/simple_animal/mouse/blobinfected)
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a blob infested mouse?", ROLE_BLOB, TRUE, source = /mob/living/basic/mouse/blobinfected)
if(!length(candidates))
return kill()
@@ -22,7 +22,7 @@
message_admins("Warning: No suitable vents detected for spawning blob mouse. Force picking from station vents regardless of state!")
vents = get_valid_vent_spawns(unwelded_only = FALSE, min_network_size = 0)
var/obj/vent = pick(vents)
- var/mob/living/simple_animal/mouse/blobinfected/B = new(vent.loc)
+ var/mob/living/basic/mouse/blobinfected/B = new(vent.loc)
var/mob/M = pick(candidates)
B.key = M.key
dust_if_respawnable(M)
diff --git a/code/modules/events/clogged_disposals.dm b/code/modules/events/clogged_disposals.dm
index 21812e8c5f8..9d49565efa1 100644
--- a/code/modules/events/clogged_disposals.dm
+++ b/code/modules/events/clogged_disposals.dm
@@ -48,7 +48,7 @@
/mob/living/basic/clown/goblin = 5,
/mob/living/basic/goat = 5,
// Friendly
- /mob/living/simple_animal/mouse = 5,
+ /mob/living/basic/mouse = 5,
/mob/living/simple_animal/diona = 5,
/mob/living/simple_animal/crab = 5,
/mob/living/simple_animal/pet/dog/corgi = 5,
diff --git a/code/modules/events/infestation.dm b/code/modules/events/infestation.dm
index 7ddea7436b6..76f45a2ce0b 100644
--- a/code/modules/events/infestation.dm
+++ b/code/modules/events/infestation.dm
@@ -53,7 +53,7 @@
vermin = rand(0, 2)
switch(vermin)
if(VERM_MICE)
- spawn_types = list(/mob/living/simple_animal/mouse/gray, /mob/living/simple_animal/mouse/brown, /mob/living/simple_animal/mouse/white)
+ spawn_types = list(/mob/living/basic/mouse/gray, /mob/living/basic/mouse/brown, /mob/living/basic/mouse/white)
max_number = 12
vermstring = "mice"
if(VERM_LIZARDS)
diff --git a/code/modules/food_and_drinks/food_base.dm b/code/modules/food_and_drinks/food_base.dm
index 6e218d8a79c..e49cbbf4230 100644
--- a/code/modules/food_and_drinks/food_base.dm
+++ b/code/modules/food_and_drinks/food_base.dm
@@ -269,7 +269,7 @@
D.last_eaten = world.time
D.taste(reagents)
else if(ismouse(M))
- var/mob/living/simple_animal/mouse/N = M
+ var/mob/living/basic/mouse/N = M
to_chat(N, "You nibble away at [src].")
if(prob(50))
N.visible_message("[N] nibbles away at [src].", "")
diff --git a/code/modules/mining/lavaland/loot/colossus_loot.dm b/code/modules/mining/lavaland/loot/colossus_loot.dm
index d47a2e511a2..8f721222a35 100644
--- a/code/modules/mining/lavaland/loot/colossus_loot.dm
+++ b/code/modules/mining/lavaland/loot/colossus_loot.dm
@@ -237,7 +237,7 @@
mobcheck = 1
break
if(!mobcheck)
- new /mob/living/simple_animal/mouse(get_step(src,dir)) //Just in case there aren't any animals on the station, this will leave you with a terrible option to possess if you feel like it
+ new /mob/living/basic/mouse(get_step(src,dir)) //Just in case there aren't any animals on the station, this will leave you with a terrible option to possess if you feel like it
/obj/structure/closet/stasis
name = "quantum entanglement stasis warp field"
diff --git a/code/modules/mob/living/basic/friendly/mouse.dm b/code/modules/mob/living/basic/friendly/mouse.dm
new file mode 100644
index 00000000000..33f916d35c9
--- /dev/null
+++ b/code/modules/mob/living/basic/friendly/mouse.dm
@@ -0,0 +1,309 @@
+/mob/living/basic/mouse
+ name = "mouse"
+ real_name = "mouse"
+ desc = "It's a small, disease-ridden rodent."
+ icon_state = "mouse_gray"
+ icon_living = "mouse_gray"
+ icon_dead = "mouse_gray_dead"
+ icon_resting = "mouse_gray_sleep"
+ speak_emote = list("squeeks","squeaks","squiks")
+ var/squeak_sound = 'sound/creatures/mousesqueak.ogg'
+ ai_controller = /datum/ai_controller/basic_controller/mouse
+ see_in_dark = 6
+ maxHealth = 5
+ health = 5
+ butcher_results = list(/obj/item/food/meat = 1)
+ response_help_continuous = "pets"
+ response_help_simple = "pet"
+ response_disarm_continuous = "gently pushes aside"
+ response_disarm_simple = "gently push aside"
+ response_harm_continuous = "stamps on"
+ response_harm_simple = "stamp on"
+ faction = list("mouse")
+ density = FALSE
+ ventcrawler = VENTCRAWLER_ALWAYS
+ pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
+ mob_biotypes = MOB_ORGANIC | MOB_BEAST
+ mob_size = MOB_SIZE_TINY
+ var/mouse_color // brown, gray and white, leave blank for random
+ atmos_requirements = list("min_oxy" = 16, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0)
+ minimum_survivable_temperature = 223 // Below -50 Degrees Celcius
+ maximum_survivable_temperature = 323 // Above 50 Degrees Celcius
+ can_hide = TRUE
+ pass_door_while_hidden = TRUE
+ holder_type = /obj/item/holder/mouse
+ gold_core_spawnable = FRIENDLY_SPAWN
+ /// Probability that, if we successfully bite a shocked cable, that we will die to it.
+ var/cable_zap_prob = 85
+ /// Food types that mice eat
+ var/static/list/food_types = list(/obj/item/food/sliced/cheesewedge, /obj/item/food/sliceable/cheesewheel)
+
+/mob/living/basic/mouse/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wears_collar)
+ AddComponent(/datum/component/squeak, list(squeak_sound = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) // as quiet as a mouse or whatever
+ AddComponent(/datum/component/swarming, 16, 16) // max_x, max_y
+ if(!mouse_color)
+ mouse_color = pick("brown", "gray", "white")
+ icon_state = "mouse_[mouse_color]"
+ icon_living = "mouse_[mouse_color]"
+ icon_dead = "mouse_[mouse_color]_dead"
+ icon_resting = "mouse_[mouse_color]_sleep"
+ update_appearance(UPDATE_ICON_STATE|UPDATE_DESC)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100)
+
+/mob/living/basic/mouse/update_desc()
+ . = ..()
+ desc = "It's a small [mouse_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself."
+
+/mob/living/basic/mouse/attack_hand(mob/living/carbon/human/M as mob)
+ if(M.a_intent == INTENT_HELP)
+ get_scooped(M, TRUE)
+ ..()
+
+/mob/living/basic/mouse/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
+ . = ..()
+ if(!.)
+ return
+
+ if(!proximity_flag)
+ return
+
+ if(istype(attack_target, /obj/item/food/sliced/cheesewedge))
+ try_consume_cheese(attack_target)
+ return TRUE
+
+ if(istype(attack_target, /obj/structure/cable))
+ try_bite_cable(attack_target)
+ return TRUE
+
+/// Signal proc for [COMSIG_ATOM_ENTERED]. Sends a lil' squeak to chat when someone walks over us.
+/mob/living/basic/mouse/proc/on_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ if(ishuman(entered) && stat == CONSCIOUS)
+ to_chat(entered, "Squeak!")
+
+/// 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/sliced/cheesewedge/cheese)
+ new /obj/effect/temp_visual/heart(loc)
+ faction |= "neutral"
+ try_consume_cheese(cheese)
+ ai_controller.cancel_actions() // Interrupt any current fleeing
+
+/mob/living/basic/mouse/proc/try_consume_cheese(obj/item/food/sliced/cheesewedge/cheese)
+ if(prob(90) || health < maxHealth)
+ visible_message(
+ "[src] nibbles [cheese].",
+ "You nibble [cheese][health < maxHealth ? ", restoring your health" : ""]."
+ )
+ adjustHealth(-maxHealth)
+ else
+ visible_message(
+ "[src] nibbles through [cheese], attracting another mouse!",
+ "You nibble through [cheese], attracting another mouse!"
+ )
+ new /mob/living/basic/mouse(loc)
+ qdel(cheese)
+
+/// 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)
+ if(!prob(cable_zap_prob))
+ return
+ var/turf/simulated/floor/F = get_turf(cable)
+ if(cable.get_available_power() && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE))
+ visible_message("[src] chews through [cable]. It's toast!")
+ playsound(src, 'sound/effects/sparks2.ogg', 100, 1)
+ toast() // mmmm toasty.
+ else
+ visible_message("[src] chews through [cable].")
+ investigate_log("was chewed through by a mouse in [get_area(F)]([F.x], [F.y], [F.z] - [ADMIN_JMP(F)])",INVESTIGATE_WIRES)
+ cable.deconstruct()
+
+/mob/living/basic/mouse/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE)// Prevents mouse from pulling things
+ if(istype(AM, /obj/item/food/sliced/cheesewedge))
+ return ..() // Get dem
+ if(show_message)
+ to_chat(src, "You are too small to pull anything except cheese.")
+ return
+
+/mob/living/basic/mouse/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(ishuman(entered))
+ if(stat == CONSCIOUS)
+ var/mob/M = entered
+ to_chat(M, "[bicon(src)] Squeek!")
+
+/mob/living/basic/mouse/proc/toast()
+ add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
+ desc = "It's toast."
+ death()
+
+/mob/living/basic/mouse/proc/splat()
+ icon_dead = "mouse_[mouse_color]_splat"
+ icon_state = "mouse_[mouse_color]_splat"
+
+/mob/living/basic/mouse/death(gibbed)
+ // Only execute the below if we successfully died
+ playsound(src, squeak_sound, 40, 1)
+ . = ..(gibbed)
+ if(!.)
+ return FALSE
+ layer = MOB_LAYER
+ if(client)
+ client.persistent.time_died_as_mouse = world.time
+
+/*
+ * Mouse types
+ */
+
+/mob/living/basic/mouse/white
+ mouse_color = "white"
+ icon_state = "mouse_white"
+
+/mob/living/basic/mouse/white/linter
+ name = "Linter"
+
+/mob/living/basic/mouse/white/linter/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, ROUNDSTART_TRAIT)
+
+/mob/living/basic/mouse/gray
+ mouse_color = "gray"
+
+/mob/living/basic/mouse/brown
+ mouse_color = "brown"
+ icon_state = "mouse_brown"
+
+// TOM IS ALIVE! SQUEEEEEEEE~K :)
+/mob/living/basic/mouse/brown/tom
+ name = "Tom"
+ real_name = "Tom"
+ gold_core_spawnable = NO_SPAWN
+
+/mob/living/basic/mouse/brown/tom/update_desc()
+ . = ..()
+ desc = "Jerry the cat is not amused."
+
+/mob/living/basic/mouse/brown/tom/Initialize(mapload)
+ . = ..()
+ // Tom fears no cable.
+ ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, SPECIES_TRAIT)
+
+/mob/living/basic/mouse/white/brain
+ name = "Brain"
+ real_name = "Brain"
+ gold_core_spawnable = NO_SPAWN
+
+/mob/living/basic/mouse/white/brain/update_desc()
+ . = ..()
+ desc = "Gee Virology, what are we going to do tonight? The same thing we do every night, try to take over the world!"
+
+// Boolog
+/mob/living/basic/mouse/blobinfected
+ maxHealth = 100
+ health = 100
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minimum_survivable_temperature = 0
+ gold_core_spawnable = NO_SPAWN
+ var/bursted = FALSE
+
+/mob/living/basic/mouse/blobinfected/Initialize(mapload)
+ . = ..()
+ apply_status_effect(STATUS_EFFECT_BLOB_BURST, 120 SECONDS, CALLBACK(src, PROC_REF(burst), FALSE))
+
+/mob/living/basic/mouse/blobinfected/Life()
+ if(ismob(loc)) // if someone ate it, burst immediately
+ burst(FALSE)
+ return ..()
+
+/mob/living/basic/mouse/blobinfected/death(gibbed)
+ . = ..(gibbed)
+ if(.) // Only burst if they actually died
+ burst(gibbed)
+
+/mob/living/basic/mouse/blobinfected/proc/burst(gibbed)
+ if(bursted)
+ return // Avoids double bursting in some situations
+ bursted = TRUE
+ var/turf/T = get_turf(src)
+ if(!is_station_level(T.z) || isspaceturf(T))
+ to_chat(src, "You feel ready to burst, but this isn't an appropriate place! You must return to the station!")
+ apply_status_effect(STATUS_EFFECT_BLOB_BURST, 5 SECONDS, CALLBACK(src, PROC_REF(burst), FALSE))
+ return FALSE
+ var/datum/mind/blobmind = mind
+ var/client/C = client
+ var/obj/structure/blob/core/core
+ if(istype(blobmind) && istype(C))
+ core = new(T, C, 3)
+ core.lateblobtimer()
+ qdel(blobmind) // Delete the old mind. THe blob will make a new one
+ else
+ core = new(T) // Ghosts will be prompted to control it.
+ if(ismob(loc)) // in case some taj/etc ate the mouse.
+ var/mob/M = loc
+ M.gib()
+ if(!gibbed)
+ gib()
+
+ if(core)
+ core.admin_spawned = admin_spawned
+
+ SSticker.record_biohazard_start(BIOHAZARD_BLOB)
+
+/mob/living/basic/mouse/blobinfected/get_scooped(mob/living/carbon/grabber)
+ to_chat(grabber, "You try to pick up [src], but they slip out of your grasp!")
+ to_chat(src, "[src] tries to pick you up, but you wriggle free of their grasp!")
+
+/mob/living/basic/mouse/decompile_act(obj/item/matter_decompiler/C, mob/user)
+ if(!isdrone(user))
+ user.visible_message("[user] sucks [src] into its decompiler. There's a horrible crunching noise.", \
+ "It's a bit of a struggle, but you manage to suck [src] into your decompiler. It makes a series of visceral crunching noises.")
+ new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
+ C.stored_comms["metal"] += 2
+ C.stored_comms["glass"] += 1
+ qdel(src)
+ return TRUE
+ return ..()
+
+/// The mouse AI controller
+/datum/ai_controller/basic_controller/mouse
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Use this to find people to run away from
+ BB_BASIC_MOB_FLEE_DISTANCE = 3,
+ )
+
+ ai_traits = AI_FLAG_STOP_MOVING_WHEN_PULLED
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ // Try to speak, because it's cute
+ /datum/ai_planning_subtree/random_speech/mouse,
+ // 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
+ /datum/ai_planning_subtree/simple_find_nearest_target_to_flee,
+ // Skedaddle
+ /datum/ai_planning_subtree/flee_target/mouse,
+ // Otherwise, look for and execute hunts for cabling
+ /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables,
+ )
+
+/// Don't look for anything to run away from if you are distracted by being adjacent to cheese
+/datum/ai_planning_subtree/flee_target/mouse
+
+/datum/ai_planning_subtree/flee_target/mouse/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
+ var/atom/hunted_cheese = controller.blackboard[BB_CURRENT_HUNTING_TARGET]
+ if(!isnull(hunted_cheese))
+ return // We see some cheese, which is more important than our life
+ return ..()
+
+/datum/ai_planning_subtree/random_speech/mouse
+ speech_chance = 1
+ speak = list("Squeak!", "SQUEAK!", "Squeak?")
+ sound = list('sound/creatures/mousesqueak.ogg')
+ emote_hear = list("squeeks.","squeaks.","squiks.")
+ emote_see = list("runs in a circle.", "shakes.", "scritches at something.")
diff --git a/code/modules/mob/living/basic/friendly/snake.dm b/code/modules/mob/living/basic/friendly/snake.dm
index 869c3d1b7a9..97df8855760 100644
--- a/code/modules/mob/living/basic/friendly/snake.dm
+++ b/code/modules/mob/living/basic/friendly/snake.dm
@@ -33,7 +33,7 @@
var/poison_type = "toxin"
/// List of stuff (mice) that we want to eat
var/static/list/edibles = list(
- /mob/living/simple_animal/mouse,
+ /mob/living/basic/mouse,
)
/mob/living/basic/snake/Initialize(mapload)
diff --git a/code/modules/mob/living/carbon/human/species/tajaran.dm b/code/modules/mob/living/carbon/human/species/tajaran.dm
index 7d7f4b980ac..5b5330bb54f 100644
--- a/code/modules/mob/living/carbon/human/species/tajaran.dm
+++ b/code/modules/mob/living/carbon/human/species/tajaran.dm
@@ -44,7 +44,13 @@
unless they choose otherwise by selecting the colourblind disability in character creation (darksight = 8 but colourblind).*/
)
- allowed_consumed_mobs = list(/mob/living/simple_animal/mouse, /mob/living/basic/chick, /mob/living/basic/butterfly, /mob/living/simple_animal/parrot, /mob/living/simple_animal/hostile/poison/bees)
+ allowed_consumed_mobs = list(
+ /mob/living/basic/mouse,
+ /mob/living/basic/chick,
+ /mob/living/basic/butterfly,
+ /mob/living/simple_animal/parrot,
+ /mob/living/simple_animal/hostile/poison/bees,
+ )
suicide_messages = list(
"is attempting to bite their tongue off!",
diff --git a/code/modules/mob/living/carbon/human/species/unathi.dm b/code/modules/mob/living/carbon/human/species/unathi.dm
index f96a9b805bc..f9b4222f7fa 100644
--- a/code/modules/mob/living/carbon/human/species/unathi.dm
+++ b/code/modules/mob/living/carbon/human/species/unathi.dm
@@ -48,8 +48,16 @@
"eyes" = /obj/item/organ/internal/eyes/unathi //3 darksight.
)
- allowed_consumed_mobs = list(/mob/living/simple_animal/mouse, /mob/living/basic/lizard, /mob/living/basic/chick, /mob/living/basic/chicken,
- /mob/living/simple_animal/crab, /mob/living/basic/butterfly, /mob/living/simple_animal/parrot, /mob/living/simple_animal/hostile/poison/bees)
+ allowed_consumed_mobs = list(
+ /mob/living/basic/mouse,
+ /mob/living/basic/lizard,
+ /mob/living/basic/chick,
+ /mob/living/basic/chicken,
+ /mob/living/simple_animal/crab,
+ /mob/living/basic/butterfly,
+ /mob/living/simple_animal/parrot,
+ /mob/living/simple_animal/hostile/poison/bees,
+ )
suicide_messages = list(
"is attempting to bite their tongue off!",
diff --git a/code/modules/mob/living/carbon/human/species/vulpkanin.dm b/code/modules/mob/living/carbon/human/species/vulpkanin.dm
index 55286796834..89c68723816 100644
--- a/code/modules/mob/living/carbon/human/species/vulpkanin.dm
+++ b/code/modules/mob/living/carbon/human/species/vulpkanin.dm
@@ -38,8 +38,16 @@
unless they choose otherwise by selecting the colourblind disability in character creation (darksight = 8 but colourblind).*/
)
- allowed_consumed_mobs = list(/mob/living/simple_animal/mouse, /mob/living/basic/lizard, /mob/living/basic/chick, /mob/living/basic/chicken,
- /mob/living/simple_animal/crab, /mob/living/basic/butterfly, /mob/living/simple_animal/parrot, /mob/living/simple_animal/hostile/poison/bees)
+ allowed_consumed_mobs = list(
+ /mob/living/basic/mouse,
+ /mob/living/basic/lizard,
+ /mob/living/basic/chick,
+ /mob/living/basic/chicken,
+ /mob/living/simple_animal/crab,
+ /mob/living/basic/butterfly,
+ /mob/living/simple_animal/parrot,
+ /mob/living/simple_animal/hostile/poison/bees,
+ )
suicide_messages = list(
"is attempting to bite their tongue off!",
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index a59ab213ee3..1cd8a0658e6 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -22,7 +22,7 @@
response_harm = "kicks"
gold_core_spawnable = FRIENDLY_SPAWN
var/turns_since_scan = 0
- var/mob/living/simple_animal/mouse/movement_target
+ var/mob/living/basic/mouse/movement_target
var/eats_mice = 1
var/collar_icon_state = "cat"
footstep_type = FOOTSTEP_MOB_CLAW
@@ -138,7 +138,7 @@
//MICE!
if(eats_mice && isturf(loc) && !incapacitated())
- for(var/mob/living/simple_animal/mouse/M in view(1, src))
+ for(var/mob/living/basic/mouse/M in view(1, src))
if(!M.stat && Adjacent(M))
custom_emote(EMOTE_VISIBLE, "splats \the [M]!")
M.death()
@@ -168,7 +168,7 @@
movement_target = null
stop_automated_movement = FALSE
walk(src, 0)
- for(var/mob/living/simple_animal/mouse/snack in oview(src,3))
+ for(var/mob/living/basic/mouse/snack in oview(src,3))
if(isturf(snack.loc) && !snack.stat)
movement_target = snack
break
diff --git a/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm b/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm
index c8df5f54d75..3ce84e6cfdb 100644
--- a/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm
+++ b/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm
@@ -50,7 +50,7 @@
// Mouse
/datum/emote/living/simple_animal/mouse
- mob_type_allowed_typecache = list(/mob/living/simple_animal/mouse)
+ mob_type_allowed_typecache = list(/mob/living/basic/mouse)
/datum/emote/living/simple_animal/mouse/squeak
key = "squeak"
@@ -60,7 +60,7 @@
emote_type = EMOTE_AUDIBLE
/datum/emote/living/simple_animal/mouse/squeak/get_sound(mob/living/user)
- var/mob/living/simple_animal/mouse/M = user
+ var/mob/living/basic/mouse/M = user
return M.squeak_sound
// cat
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
deleted file mode 100644
index 8c58d1974ed..00000000000
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ /dev/null
@@ -1,245 +0,0 @@
-/mob/living/simple_animal/mouse
- name = "mouse"
- real_name = "mouse"
- desc = "It's a small, disease-ridden rodent."
- icon_state = "mouse_gray"
- icon_living = "mouse_gray"
- icon_dead = "mouse_gray_dead"
- icon_resting = "mouse_gray_sleep"
- speak = list("Squeek!","SQUEEK!","Squeek?")
- speak_emote = list("squeeks","squeaks","squiks")
- emote_hear = list("squeeks","squeaks","squiks")
- emote_see = list("runs in a circle", "shakes", "scritches at something")
- var/squeak_sound = 'sound/creatures/mousesqueak.ogg'
- speak_chance = 1
- turns_per_move = 5
- see_in_dark = 6
- maxHealth = 5
- health = 5
- butcher_results = list(/obj/item/food/meat = 1)
- response_help = "pets"
- response_disarm = "gently pushes aside"
- response_harm = "stamps on"
- density = FALSE
- ventcrawler = VENTCRAWLER_ALWAYS
- pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
- mob_biotypes = MOB_ORGANIC | MOB_BEAST
- mob_size = MOB_SIZE_TINY
- var/mouse_color //brown, gray and white, leave blank for random
- atmos_requirements = list("min_oxy" = 16, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 223 //Below -50 Degrees Celcius
- maxbodytemp = 323 //Above 50 Degrees Celcius
- can_hide = TRUE
- pass_door_while_hidden = TRUE
- holder_type = /obj/item/holder/mouse
- 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
- if(!mouse_color)
- mouse_color = pick("brown", "gray", "white")
- icon_state = "mouse_[mouse_color]"
- icon_living = "mouse_[mouse_color]"
- icon_dead = "mouse_[mouse_color]_dead"
- icon_resting = "mouse_[mouse_color]_sleep"
- update_appearance(UPDATE_ICON_STATE|UPDATE_DESC)
- var/static/list/loc_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
- )
- AddElement(/datum/element/connect_loc, loc_connections)
-
-/mob/living/simple_animal/mouse/handle_automated_action()
-#if defined(GAME_TESTS) || defined(MAP_TESTS) // DO NOT EAT MY CABLES DURING TESTS
- return
-#endif
- if(!prob(chew_probability) || !isfloorturf(loc))
- return
- var/turf/simulated/floor/F = get_turf(src)
- if(F.intact || F.transparent_floor)
- return
- var/obj/structure/cable/C = locate() in F
- if(!C || !prob(15))
- return
- if(C.get_available_power() && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE))
- visible_message("[src] chews through [C]. It's toast!")
- playsound(src, 'sound/effects/sparks2.ogg', 100, 1)
- toast() // mmmm toasty.
- else
- visible_message("[src] chews through [C].")
- investigate_log("was chewed through by a mouse in [get_area(F)]([F.x], [F.y], [F.z] - [ADMIN_JMP(F)])",INVESTIGATE_WIRES)
- C.deconstruct()
-
-/mob/living/simple_animal/mouse/handle_automated_speech()
- ..()
- if(prob(speak_chance) && !incapacitated())
- playsound(src, squeak_sound, 100, 1)
-
-/mob/living/simple_animal/mouse/handle_automated_movement()
- . = ..()
- if(IS_HORIZONTAL(src))
- if(prob(1))
- stand_up()
- else if(prob(5))
- custom_emote(EMOTE_AUDIBLE, "snuffles")
- else if(prob(0.5))
- lay_down()
-
-/mob/living/simple_animal/mouse/update_desc()
- . = ..()
- desc = "It's a small [mouse_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself."
-
-/mob/living/simple_animal/mouse/attack_hand(mob/living/carbon/human/M as mob)
- if(M.a_intent == INTENT_HELP)
- get_scooped(M, TRUE)
- ..()
-
-/mob/living/simple_animal/mouse/start_pulling(atom/movable/AM, state, force = pull_force, show_message = FALSE)//Prevents mouse from pulling things
- if(istype(AM, /obj/item/food/sliced/cheesewedge))
- return ..() // Get dem
- if(show_message)
- to_chat(src, "You are too small to pull anything except cheese.")
- return
-
-/mob/living/simple_animal/mouse/proc/on_atom_entered(datum/source, atom/movable/entered)
- if(ishuman(entered))
- if(stat == CONSCIOUS)
- var/mob/M = entered
- to_chat(M, "[bicon(src)] Squeek!")
-
-/mob/living/simple_animal/mouse/proc/toast()
- add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
- desc = "It's toast."
- death()
-
-/mob/living/simple_animal/mouse/proc/splat()
- icon_dead = "mouse_[mouse_color]_splat"
- icon_state = "mouse_[mouse_color]_splat"
-
-/mob/living/simple_animal/mouse/death(gibbed)
- // Only execute the below if we successfully died
- playsound(src, squeak_sound, 40, 1)
- . = ..(gibbed)
- if(!.)
- return FALSE
- layer = MOB_LAYER
- if(client)
- client.persistent.time_died_as_mouse = world.time
-
-/*
- * Mouse types
- */
-
-/mob/living/simple_animal/mouse/white
- mouse_color = "white"
- icon_state = "mouse_white"
-
-/mob/living/simple_animal/mouse/white/linter
- name = "Linter"
-
-/mob/living/simple_animal/mouse/white/linter/Initialize(mapload)
- . = ..()
- ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, ROUNDSTART_TRAIT)
-
-/mob/living/simple_animal/mouse/gray
- mouse_color = "gray"
-
-/mob/living/simple_animal/mouse/brown
- mouse_color = "brown"
- icon_state = "mouse_brown"
-
-//TOM IS ALIVE! SQUEEEEEEEE~K :)
-/mob/living/simple_animal/mouse/brown/tom
- name = "Tom"
- real_name = "Tom"
- response_harm = "splats"
- unique_pet = TRUE
- gold_core_spawnable = NO_SPAWN
-
-/mob/living/simple_animal/mouse/brown/tom/update_desc()
- . = ..()
- desc = "Jerry the cat is not amused."
-
-/mob/living/simple_animal/mouse/brown/tom/Initialize(mapload)
- . = ..()
- // Tom fears no cable.
- ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, SPECIES_TRAIT)
-
-/mob/living/simple_animal/mouse/white/brain
- name = "Brain"
- real_name = "Brain"
- response_harm = "splats"
- unique_pet = TRUE
- gold_core_spawnable = NO_SPAWN
-
-/mob/living/simple_animal/mouse/white/brain/update_desc()
- . = ..()
- desc = "Gee Virology, what are we going to do tonight? The same thing we do every night, try to take over the world!"
-
-/mob/living/simple_animal/mouse/blobinfected
- maxHealth = 100
- health = 100
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
- gold_core_spawnable = NO_SPAWN
- var/bursted = FALSE
-
-/mob/living/simple_animal/mouse/blobinfected/Initialize(mapload)
- . = ..()
- apply_status_effect(STATUS_EFFECT_BLOB_BURST, 120 SECONDS, CALLBACK(src, PROC_REF(burst), FALSE))
-
-/mob/living/simple_animal/mouse/blobinfected/Life()
- if(ismob(loc)) // if someone ate it, burst immediately
- burst(FALSE)
- return ..()
-
-/mob/living/simple_animal/mouse/blobinfected/death(gibbed)
- . = ..(gibbed)
- if(.) // Only burst if they actually died
- burst(gibbed)
-
-/mob/living/simple_animal/mouse/blobinfected/proc/burst(gibbed)
- if(bursted)
- return // Avoids double bursting in some situations
- bursted = TRUE
- var/turf/T = get_turf(src)
- if(!is_station_level(T.z) || isspaceturf(T))
- to_chat(src, "You feel ready to burst, but this isn't an appropriate place! You must return to the station!")
- apply_status_effect(STATUS_EFFECT_BLOB_BURST, 5 SECONDS, CALLBACK(src, PROC_REF(burst), FALSE))
- return FALSE
- var/datum/mind/blobmind = mind
- var/client/C = client
- var/obj/structure/blob/core/core
- if(istype(blobmind) && istype(C))
- core = new(T, C, 3)
- core.lateblobtimer()
- qdel(blobmind) // Delete the old mind. THe blob will make a new one
- else
- core = new(T) // Ghosts will be prompted to control it.
- if(ismob(loc)) // in case some taj/etc ate the mouse.
- var/mob/M = loc
- M.gib()
- if(!gibbed)
- gib()
-
- if(core)
- core.admin_spawned = admin_spawned
-
- SSticker.record_biohazard_start(BIOHAZARD_BLOB)
-
-/mob/living/simple_animal/mouse/blobinfected/get_scooped(mob/living/carbon/grabber)
- to_chat(grabber, "You try to pick up [src], but they slip out of your grasp!")
- to_chat(src, "[src] tries to pick you up, but you wriggle free of their grasp!")
-
-/mob/living/simple_animal/mouse/decompile_act(obj/item/matter_decompiler/C, mob/user)
- if(!isdrone(user))
- user.visible_message("[user] sucks [src] into its decompiler. There's a horrible crunching noise.", \
- "It's a bit of a struggle, but you manage to suck [src] into your decompiler. It makes a series of visceral crunching noises.")
- new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
- C.stored_comms["metal"] += 2
- C.stored_comms["glass"] += 1
- qdel(src)
- return TRUE
- return ..()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 9e6dba11b35..b979a62c0f9 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1118,7 +1118,7 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
to_chat(src, "Unable to find any unwelded vents to spawn mice at.")
return FALSE
var/obj/vent_found = pick(found_vents)
- var/mob/living/simple_animal/mouse/host = new(vent_found.loc)
+ var/mob/living/basic/mouse/host = new(vent_found.loc)
host.ckey = src.ckey
to_chat(host, "You are now a mouse, a small and fragile creature capable of scurrying through vents and under doors. Be careful who you reveal yourself to, for that will decide whether you receive cheese or death.")
host.forceMove(vent_found)
diff --git a/code/modules/mob/mob_misc_procs.dm b/code/modules/mob/mob_misc_procs.dm
index 89b9ef156a7..e500712d61e 100644
--- a/code/modules/mob/mob_misc_procs.dm
+++ b/code/modules/mob/mob_misc_procs.dm
@@ -856,3 +856,9 @@
// No, I don't like it either.
remove_from_all_data_huds()
add_to_all_human_data_huds()
+
+
+/// Called after the atom is 'tamed' for type-specific operations, Usually called by the tameable component but also other things.
+/mob/proc/tamed(mob/living/tamer, obj/item/food)
+ return
+
diff --git a/code/modules/projectiles/projectile/magic_projectiles.dm b/code/modules/projectiles/projectile/magic_projectiles.dm
index c1c97b46cc4..3a1b81efd8c 100644
--- a/code/modules/projectiles/projectile/magic_projectiles.dm
+++ b/code/modules/projectiles/projectile/magic_projectiles.dm
@@ -208,7 +208,7 @@ GLOBAL_LIST_INIT(wabbajack_docile_animals, list(
"corgi" = /mob/living/simple_animal/pet/dog/corgi,
"crab" = /mob/living/simple_animal/crab,
"cat" = /mob/living/simple_animal/pet/cat,
- "mouse" = /mob/living/simple_animal/mouse,
+ "mouse" = /mob/living/basic/mouse,
"chicken" = /mob/living/basic/chicken,
"cow" = /mob/living/basic/cow,
"lizard" = /mob/living/basic/lizard,
diff --git a/code/modules/research/strange_objects.dm b/code/modules/research/strange_objects.dm
index 2215dec165f..c4080415187 100644
--- a/code/modules/research/strange_objects.dm
+++ b/code/modules/research/strange_objects.dm
@@ -49,7 +49,7 @@
/mob/living/simple_animal/pet/dog/corgi,
/mob/living/simple_animal/pet/cat,
/mob/living/simple_animal/pet/dog/fox,
- /mob/living/simple_animal/mouse,
+ /mob/living/basic/mouse,
/mob/living/simple_animal/pet/dog/pug,
/mob/living/basic/lizard,
/mob/living/simple_animal/diona,
@@ -131,7 +131,7 @@
/mob/living/simple_animal/crab,
/mob/living/simple_animal/pet/dog/fox,
/mob/living/basic/lizard,
- /mob/living/simple_animal/mouse,
+ /mob/living/basic/mouse,
/mob/living/simple_animal/pet/dog/pug,
/mob/living/basic/bear/black,
/mob/living/basic/bear/brown,
diff --git a/paradise.dme b/paradise.dme
index b46c9aaf6b1..b8bf23c60f8 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -500,6 +500,7 @@
#include "code\datums\ai\basic_mobs\hunting_behavior\find_and_hunt_target.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\find_hunt_target.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\hunt_target.dm"
+#include "code\datums\ai\basic_mobs\hunting_behavior\hunting_mouse.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\hunting_corpses.dm"
#include "code\datums\ai\generic\find_and_set.dm"
#include "code\datums\ai\generic\generic_behaviors.dm"
@@ -571,6 +572,7 @@
#include "code\datums\components\sticky.dm"
#include "code\datums\components\surgery_initiator.dm"
#include "code\datums\components\swarming.dm"
+#include "code\datums\components\tameable.dm"
#include "code\datums\components\tether.dm"
#include "code\datums\components\tilted.dm"
#include "code\datums\components\tippable.dm"
@@ -2457,6 +2459,7 @@
#include "code\modules\mob\living\basic\farm_animals\deer.dm"
#include "code\modules\mob\living\basic\farm_animals\deer_ai.dm"
#include "code\modules\mob\living\basic\farm_animals\pig.dm"
+#include "code\modules\mob\living\basic\friendly\mouse.dm"
#include "code\modules\mob\living\basic\friendly\basic_pet.dm"
#include "code\modules\mob\living\basic\friendly\bunny.dm"
#include "code\modules\mob\living\basic\friendly\butterfly.dm"
@@ -2696,7 +2699,6 @@
#include "code\modules\mob\living\simple_animal\friendly\dog.dm"
#include "code\modules\mob\living\simple_animal\friendly\fox.dm"
#include "code\modules\mob\living\simple_animal\friendly\friendly_emote.dm"
-#include "code\modules\mob\living\simple_animal\friendly\mouse.dm"
#include "code\modules\mob\living\simple_animal\friendly\pet.dm"
#include "code\modules\mob\living\simple_animal\friendly\spiderbot.dm"
#include "code\modules\mob\living\simple_animal\hostile\angel_statue.dm"
diff --git a/tools/ci/check_simplemob_additions.py b/tools/ci/check_simplemob_additions.py
index df6dd933ac5..d9e2948cf78 100644
--- a/tools/ci/check_simplemob_additions.py
+++ b/tools/ci/check_simplemob_additions.py
@@ -191,14 +191,6 @@ BURNDOWN_LIST = {
"/mob/living/simple_animal/hostile/winter/santa/stage_4",
"/mob/living/simple_animal/hostile/winter/snowman",
"/mob/living/simple_animal/hostile/winter/snowman/ranged",
- "/mob/living/simple_animal/mouse",
- "/mob/living/simple_animal/mouse/blobinfected",
- "/mob/living/simple_animal/mouse/brown",
- "/mob/living/simple_animal/mouse/brown/tom",
- "/mob/living/simple_animal/mouse/gray",
- "/mob/living/simple_animal/mouse/white",
- "/mob/living/simple_animal/mouse/white/brain",
- "/mob/living/simple_animal/mouse/white/linter",
"/mob/living/simple_animal/parrot",
"/mob/living/simple_animal/parrot/poly",
"/mob/living/simple_animal/pet",