diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 1d5e0f7b05b..e9124580793 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -292,7 +292,7 @@
if("monkey") M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob )
if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob )
if("cat") M.change_mob_type( /mob/living/simple_animal/cat , null, null, delmob )
- if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/Runtime , null, null, delmob )
+ if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/fluff/Runtime , null, null, delmob )
if("corgi") M.change_mob_type( /mob/living/simple_animal/corgi , null, null, delmob )
if("ian") M.change_mob_type( /mob/living/simple_animal/corgi/Ian , null, null, delmob )
if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob )
diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm
index 95593385a00..781d88feb52 100644
--- a/code/modules/customitems/item_defines.dm
+++ b/code/modules/customitems/item_defines.dm
@@ -1412,12 +1412,6 @@
////////////////////////////// Foxler - Erstatz Vryroxes /////////////////////////////////////////////////
-/obj/item/weapon/holder/cat/fluff/bones
- name = "Bones"
- desc = "It's Bones! Meow."
- gender = MALE
- icon_state = "cat3"
-
//Use this subtype for spawning in the custom item.
/obj/item/weapon/holder/cat/fluff/bones/custom_item
@@ -1426,6 +1420,12 @@
new/mob/living/simple_animal/cat/fluff/bones (src)
..()
+/obj/item/weapon/holder/cat/fluff/bones
+ name = "Bones"
+ desc = "It's Bones! Meow."
+ gender = MALE
+ icon_state = "cat3"
+
/mob/living/simple_animal/cat/fluff/bones
name = "Bones"
desc = "That's Bones the cat. He's a laid back, black cat. Meow."
@@ -1434,61 +1434,12 @@
icon_living = "cat3"
icon_dead = "cat3_dead"
holder_type = /obj/item/weapon/holder/cat/fluff/bones
- bff_name = "Erstatz Vryroxes"
+ var/friend_name = "Erstatz Vryroxes"
-/mob/living/simple_animal/cat/fluff
- var/bff_name
- var/mob/living/carbon/human/bff
-
-/mob/living/simple_animal/cat/fluff/handle_movement_target()
- if (!bff)
+/mob/living/simple_animal/cat/fluff/bones/handle_movement_target()
+ if (!friend)
for (var/mob/living/carbon/human/M in player_list)
- if (M.real_name == bff_name)
- bff = M
+ if (M.real_name == friend_name)
+ friend = M
break
-
- if (bff)
- var/follow_dist = 5
- if (bff.stat >= DEAD || bff.health <= config.health_threshold_softcrit) //danger
- follow_dist = 1
- else if (bff.stat || bff.health <= 50) //danger or just sleeping
- follow_dist = 2
- var/near_dist = max(follow_dist - 3, 1)
- var/current_dist = get_dist(src, bff)
-
- if (movement_target != bff)
- if (current_dist > follow_dist && !istype(movement_target, /mob/living/simple_animal/mouse) && (bff in oview(src)))
- //stop existing movement
- walk_to(src,0)
- turns_since_scan = 0
-
- //walk to bff
- stop_automated_movement = 1
- movement_target = bff
- walk_to(src, movement_target, near_dist, 4)
-
- //already following and close enough, stop
- else if (current_dist <= near_dist)
- walk_to(src,0)
- movement_target = null
- stop_automated_movement = 0
-
- if (!(bff && movement_target == bff))
- ..()
-
-/mob/living/simple_animal/cat/fluff/Life()
..()
- if (stat || !bff)
- return
- if (get_dist(src, bff) <= 1)
- if (bff.stat >= DEAD || bff.health <= config.health_threshold_softcrit)
- if (prob((bff.stat < DEAD)? 50 : 15))
- audible_emote(pick("meows in distress.", "meows anxiously."))
- else
- if (prob(5))
- visible_emote(pick("nuzzles [bff].",
- "brushes against [bff].",
- "rubs against [bff].",
- "purrs."))
- else if (bff.health <= 50)
- if (prob(10)) audible_emote("meows anxiously.")
diff --git a/code/modules/hydroponics/seed_datums.dm b/code/modules/hydroponics/seed_datums.dm
index 8e012d8517f..cbedbec6bc2 100644
--- a/code/modules/hydroponics/seed_datums.dm
+++ b/code/modules/hydroponics/seed_datums.dm
@@ -1635,7 +1635,7 @@ proc/populate_seed_list()
seed_noun = "data"
display_name = "runtimes"
packet_icon = "seed-replicapod"
- products = list(/mob/living/simple_animal/cat/Runtime)
+ products = list(/mob/living/simple_animal/cat/fluff/Runtime)
plant_icon = "replicapod"
requires_nutrients = 0
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 7913d23d7ed..19acdc03064 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -18,6 +18,7 @@
response_harm = "kicks"
var/turns_since_scan = 0
var/mob/living/simple_animal/mouse/movement_target
+ var/mob/flee_target
min_oxy = 16 //Require atleast 16kPA oxygen
minbodytemp = 223 //Below -50 Degrees Celcius
maxbodytemp = 323 //Above 50 Degrees Celcius
@@ -44,27 +45,71 @@
break
if(!stat && !resting && !buckled)
- handle_movement_target()
+ turns_since_scan++
+ if (turns_since_scan > 5)
+ walk_to(src,0)
+ turns_since_scan = 0
+
+ if (flee_target) //fleeing takes precendence
+ handle_flee_target()
+ else
+ handle_movement_target()
-/mob/living/simple_animal/cat/proc/handle_movement_target()
- turns_since_scan++
- if(turns_since_scan > 5)
- walk_to(src,0)
- turns_since_scan = 0
+/mob/living/simple_animal/cat/proc/handle_movement_target()
+ //if our target is neither inside a turf or inside a human(???), stop
+ if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) ))
+ movement_target = null
+ stop_automated_movement = 0
+ //if we have no target or our current one is out of sight/too far away
+ if( !movement_target || !(movement_target.loc in oview(src, 4)) )
+ movement_target = null
+ stop_automated_movement = 0
+ for(var/mob/living/simple_animal/mouse/snack in oview(src)) //search for a new target
+ if(isturf(snack.loc) && !snack.stat)
+ movement_target = snack
+ break
+
+ if(movement_target)
+ stop_automated_movement = 1
+ walk_to(src,movement_target,0,3)
- if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) ))
- movement_target = null
- stop_automated_movement = 0
- if( !movement_target || !(movement_target.loc in oview(src, 4)) )
- movement_target = null
- stop_automated_movement = 0
- for(var/mob/living/simple_animal/mouse/snack in oview(src))
- if(isturf(snack.loc) && !snack.stat)
- movement_target = snack
- break
- if(movement_target)
- stop_automated_movement = 1
- walk_to(src,movement_target,0,3)
+/mob/living/simple_animal/cat/proc/handle_flee_target()
+ //see if we should stop fleeing
+ if (flee_target && !(flee_target.loc in view(src)))
+ flee_target = null
+ stop_automated_movement = 0
+
+ if (flee_target)
+ if(prob(25)) say("HSSSSS")
+ stop_automated_movement = 1
+ walk_away(src, flee_target, 7, 2)
+
+/mob/living/simple_animal/cat/proc/set_flee_target(atom/A)
+ if(A)
+ flee_target = A
+ turns_since_scan = 5
+
+/mob/living/simple_animal/cat/attackby(var/obj/item/O, var/mob/user)
+ . = ..()
+ if(O.force)
+ set_flee_target(user? user : src.loc)
+
+/mob/living/simple_animal/cat/attack_hand(mob/living/carbon/human/M as mob)
+ . = ..()
+ if(M.a_intent == "hurt")
+ set_flee_target(M)
+
+/mob/living/simple_animal/cat/ex_act()
+ . = ..()
+ set_flee_target(src.loc)
+
+/mob/living/simple_animal/cat/bullet_act(var/obj/item/projectile/proj)
+ . = ..()
+ set_flee_target(proj.firer? proj.firer : src.loc)
+
+/mob/living/simple_animal/cat/hitby(atom/movable/AM)
+ . = ..()
+ set_flee_target(AM.thrower? AM.thrower : src.loc)
/mob/living/simple_animal/cat/MouseDrop(atom/over_object)
@@ -82,14 +127,91 @@
return //since the holder icon looks like a living cat
..()
+//Basic friend AI
+/mob/living/simple_animal/cat/fluff
+ var/mob/living/carbon/human/friend
+ var/befriend_job = null
+
+/mob/living/simple_animal/cat/fluff/handle_movement_target()
+ if (friend)
+ var/follow_dist = 5
+ if (friend.stat >= DEAD || friend.health <= config.health_threshold_softcrit) //danger
+ follow_dist = 1
+ else if (friend.stat || friend.health <= 50) //danger or just sleeping
+ follow_dist = 2
+ var/near_dist = max(follow_dist - 2, 1)
+ var/current_dist = get_dist(src, friend)
+
+ if (movement_target != friend)
+ if (current_dist > follow_dist && !istype(movement_target, /mob/living/simple_animal/mouse) && (friend in oview(src)))
+ //stop existing movement
+ walk_to(src,0)
+ turns_since_scan = 0
+
+ //walk to friend
+ stop_automated_movement = 1
+ movement_target = friend
+ walk_to(src, movement_target, near_dist, 4)
+
+ //already following and close enough, stop
+ else if (current_dist <= near_dist)
+ walk_to(src,0)
+ movement_target = null
+ stop_automated_movement = 0
+ if (prob(10))
+ say("Meow!")
+
+ if (!friend || movement_target != friend)
+ ..()
+
+/mob/living/simple_animal/cat/fluff/Life()
+ ..()
+ if (stat || !friend)
+ return
+ if (get_dist(src, friend) <= 1)
+ if (friend.stat >= DEAD || friend.health <= config.health_threshold_softcrit)
+ if (prob((friend.stat < DEAD)? 50 : 15))
+ var/verb = pick("meows", "mews", "mrowls")
+ audible_emote(pick("[verb] in distress.", "[verb] anxiously."))
+ else
+ if (prob(5))
+ visible_emote(pick("nuzzles [friend].",
+ "brushes against [friend].",
+ "rubs against [friend].",
+ "purrs."))
+ else if (friend.health <= 50)
+ if (prob(10))
+ var/verb = pick("meows", "mews", "mrowls")
+ audible_emote("[verb] anxiously.")
+
+/mob/living/simple_animal/cat/fluff/verb/friend()
+ set name = "Become Friends"
+ set category = "IC"
+ set src in view(1)
+
+ if(friend && usr == friend)
+ set_dir(get_dir(src, friend))
+ say("Meow!")
+ return
+
+ if (!(ishuman(usr) && befriend_job && usr.job == befriend_job))
+ usr << "[src] ignores you."
+ return
+
+ friend = usr
+
+ set_dir(get_dir(src, friend))
+ say("Meow!")
+
//RUNTIME IS ALIVE! SQUEEEEEEEE~
-/mob/living/simple_animal/cat/Runtime
+/mob/living/simple_animal/cat/fluff/Runtime
name = "Runtime"
desc = "Her fur has the look and feel of velvet, and her tail quivers occasionally."
gender = FEMALE
icon_state = "cat"
icon_living = "cat"
icon_dead = "cat_dead"
+ befriend_job = "Chief Medical Officer"
/mob/living/simple_animal/cat/kitten
name = "kitten"
@@ -97,4 +219,8 @@
icon_state = "kitten"
icon_living = "kitten"
icon_dead = "kitten_dead"
- gender = NEUTER
\ No newline at end of file
+ gender = NEUTER
+
+/mob/living/simple_animal/cat/kitten/New()
+ gender = pick(MALE, FEMALE)
+ ..()
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 74f6c85eb97..9fa5c1da9a5 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -265,7 +265,7 @@
adjustBruteLoss(-medical_pack.heal_brute)
visible_message("\The [user] applies the [medical_pack] to \the [src].")
else
- user << "\The [src] cannot benefit from medical items in its current state."
+ user << "\The [src] cannot benefit from medical items in \his current state."
return
else if(istype(O, /obj/item/weapon/kitchenknife) || istype(O, /obj/item/weapon/butch))
diff --git a/maps/exodus-1.dmm b/maps/exodus-1.dmm
index 31319bdbdfc..d8309c90723 100644
--- a/maps/exodus-1.dmm
+++ b/maps/exodus-1.dmm
@@ -4938,7 +4938,7 @@
"bQX" = (/turf/simulated/floor{icon_state = "white_2"},/area/medical/medbay)
"bQY" = (/obj/machinery/atmospherics/unary/outlet_injector{dir = 4; frequency = 1441; icon_state = "map_injector"; id = "n2_in"; use_power = 1},/turf/simulated/floor/engine,/area/rnd/misc_lab)
"bQZ" = (/turf/simulated/floor{tag = "icon-whiteblue (WEST)"; icon_state = "whiteblue"; dir = 8},/area/crew_quarters/heads/cmo)
-"bRa" = (/mob/living/simple_animal/cat/Runtime,/turf/simulated/floor{icon_state = "white"},/area/crew_quarters/heads/cmo)
+"bRa" = (/mob/living/simple_animal/cat/fluff/Runtime,/turf/simulated/floor{icon_state = "white"},/area/crew_quarters/heads/cmo)
"bRb" = (/obj/structure/bed/chair/office/dark{dir = 1},/obj/effect/landmark/start{name = "Chief Medical Officer"},/obj/machinery/door_control{desc = "A remote control-switch for shutters."; id = "cmooffice"; name = "CMO Privacy Shutters"; pixel_x = 38; pixel_y = 21},/obj/machinery/door_control{desc = "A remote control switch for the CMO's office."; id = "cmodoor"; name = "CMO Office Door Control"; normaldoorcontrol = 1; pixel_x = 28; pixel_y = 21},/obj/machinery/door_control{desc = "A remote control-switch for shutters."; id = "virologyquar"; name = "Virology Emergency Lockdown Control"; pixel_x = -15; pixel_y = 38; req_access_txt = "5"},/obj/machinery/door_control{desc = "A remote control-switch for shutters."; id = "medbayquar"; name = "Medbay Emergency Lockdown Control"; pixel_x = -15; pixel_y = 30; req_access_txt = "5"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor{icon_state = "white"},/area/crew_quarters/heads/cmo)
"bRc" = (/obj/structure/table,/obj/item/weapon/folder/white{pixel_y = 10},/obj/item/weapon/clipboard,/obj/item/weapon/paper_bin,/obj/item/weapon/pen,/obj/item/weapon/stamp/cmo,/turf/simulated/floor{tag = "icon-whiteblue (EAST)"; icon_state = "whiteblue"; dir = 4},/area/crew_quarters/heads/cmo)
"bRd" = (/obj/machinery/door/firedoor/border_only{dir = 4; name = "Firelock East"},/obj/machinery/door/blast/regular{density = 0; icon_state = "pdoor0"; id = "Biohazard"; name = "Biohazard Shutter"; opacity = 0},/obj/machinery/door/airlock/research{name = "Toxins Launch Room"; req_access_txt = "8"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor,/area/rnd/mixing)