diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index ea6a0086c4f..2ebcd2fb867 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -76,6 +76,7 @@
return
// cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents)
+
if(A == loc || (A in loc) || (A in contents))
// No adjacency checks
next_move = world.time + 8
@@ -107,6 +108,7 @@
return
return
+
//Middle click cycles through selected modules.
/mob/living/silicon/robot/MiddleClickOn(var/atom/A)
cycle_modules()
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
index 8cd7117a77e..83bba36d8b5 100644
--- a/code/datums/ai_law_sets.dm
+++ b/code/datums/ai_law_sets.dm
@@ -94,8 +94,8 @@
/datum/ai_laws/drone/New()
add_inherent_law("Preserve, repair and improve the station to the best of your abilities.")
- add_inherent_law("Cause no harm to the station or anything on it.")
- add_inherent_law("Interact with no being that is not a fellow maintenance drone.")
+ add_inherent_law("Cause no harm to the station or crew.")
+ add_inherent_law("Interact with no humanoid or synthetic being. that is not a fellow maintenance drone.")
..()
/datum/ai_laws/construction_drone
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index a2cbe632c7c..32f87bf4b0f 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -20,94 +20,94 @@
if(holder)
holder.update_icon()
- proc/triggered(mob/target as mob, var/type = "feet")
- if(!armed)
+/obj/item/device/assembly/mousetrap/proc/triggered(mob/target as mob, var/type = "feet")
+ if(!armed)
+ return
+ var/obj/item/organ/external/affecting = null
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ switch(type)
+ if("feet")
+ if(!H.shoes)
+ affecting = H.get_organ(pick("l_leg", "r_leg"))
+ H.Weaken(3)
+ if("l_hand", "r_hand")
+ if(!H.gloves)
+ affecting = H.get_organ(type)
+ H.Stun(3)
+ if(affecting)
+ if(affecting.take_damage(rand(7,12), 0))
+ H.UpdateDamageIcon()
+ H.updatehealth()
+ else if(ismouse(target))
+ var/mob/living/simple_animal/mouse/M = target
+ visible_message("\red SPLAT!")
+ M.splat()
+ playsound(target.loc, 'sound/effects/snap.ogg', 50, 1)
+ layer = MOB_LAYER - 0.2
+ armed = 0
+ update_icon()
+ pulse(0)
+
+
+/obj/item/device/assembly/mousetrap/attack_self(mob/living/user as mob)
+ if(!armed)
+ user << "You arm [src]."
+ else
+ if(((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)))
+ var/which_hand = "l_hand"
+ if(!user.hand)
+ which_hand = "r_hand"
+ triggered(user, which_hand)
+ user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
+ "You accidentally trigger [src]!")
return
- var/obj/item/organ/external/affecting = null
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- switch(type)
- if("feet")
- if(!H.shoes)
- affecting = H.get_organ(pick("l_leg", "r_leg"))
- H.Weaken(3)
- if("l_hand", "r_hand")
- if(!H.gloves)
- affecting = H.get_organ(type)
- H.Stun(3)
- if(affecting)
- if(affecting.take_damage(1, 0))
- H.UpdateDamageIcon()
- H.updatehealth()
- else if(ismouse(target))
- var/mob/living/simple_animal/mouse/M = target
- visible_message("\red SPLAT!")
- M.splat()
- playsound(target.loc, 'sound/effects/snap.ogg', 50, 1)
- layer = MOB_LAYER - 0.2
- armed = 0
- update_icon()
- pulse(0)
+ user << "You disarm [src]."
+ armed = !armed
+ update_icon()
+ playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3)
- attack_self(mob/living/user as mob)
- if(!armed)
- user << "You arm [src]."
- else
- if(((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)))
- var/which_hand = "l_hand"
- if(!user.hand)
- which_hand = "r_hand"
- triggered(user, which_hand)
- user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
- "You accidentally trigger [src]!")
- return
- user << "You disarm [src]."
- armed = !armed
- update_icon()
- playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3)
+/obj/item/device/assembly/mousetrap/attack_hand(mob/living/user as mob)
+ if(armed)
+ if(((user.getBrainLoss() >= 60 || CLUMSY in user.mutations)) && prob(50))
+ var/which_hand = "l_hand"
+ if(!user.hand)
+ which_hand = "r_hand"
+ triggered(user, which_hand)
+ user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
+ "You accidentally trigger [src]!")
+ return
+ ..()
- attack_hand(mob/living/user as mob)
- if(armed)
- if(((user.getBrainLoss() >= 60 || CLUMSY in user.mutations)) && prob(50))
- var/which_hand = "l_hand"
- if(!user.hand)
- which_hand = "r_hand"
- triggered(user, which_hand)
- user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
- "You accidentally trigger [src]!")
- return
- ..()
+/obj/item/device/assembly/mousetrap/Crossed(AM as mob|obj)
+ if(armed)
+ if(ishuman(AM))
+ var/mob/living/carbon/H = AM
+ if(H.m_intent == "run")
+ triggered(H)
+ H.visible_message("[H] accidentally steps on [src].", \
+ "You accidentally step on [src]")
+ if(ismouse(AM))
+ triggered(AM)
+ ..()
- Crossed(AM as mob|obj)
- if(armed)
- if(ishuman(AM))
- var/mob/living/carbon/H = AM
- if(H.m_intent == "run")
- triggered(H)
- H.visible_message("[H] accidentally steps on [src].", \
- "You accidentally step on [src]")
- if(ismouse(AM))
- triggered(AM)
- ..()
+/obj/item/device/assembly/mousetrap/on_found(mob/finder as mob)
+ if(armed)
+ finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \
+ "You accidentally trigger [src]!")
+ triggered(finder, finder.hand ? "l_hand" : "r_hand")
+ return 1 //end the search!
+ return 0
- on_found(mob/finder as mob)
- if(armed)
- finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \
- "You accidentally trigger [src]!")
- triggered(finder, finder.hand ? "l_hand" : "r_hand")
- return 1 //end the search!
- return 0
-
-
- hitby(A as mob|obj)
- if(!armed)
- return ..()
- visible_message("[src] is triggered by [A].")
- triggered(null)
+/obj/item/device/assembly/mousetrap/hitby(A as mob|obj)
+ if(!armed)
+ return ..()
+ visible_message("[src] is triggered by [A].")
+ triggered(null)
/obj/item/device/assembly/mousetrap/armed
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index 67d35d04065..b8434c68dfa 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -15,7 +15,6 @@
var/last_loc_general//This stores a general location of the object. Ie, a container or a mob
var/last_loc_specific//This stores specific extra information about the location, pocket, hand, worn on head, etc. Only relevant to mobs
-
/obj/item/weapon/holder/New()
if (!item_state)
item_state = icon_state
@@ -29,24 +28,21 @@
processing_objects.Remove(src)
..()
+/obj/item/weapon/holder/examine(mob/user)
+ if (contained)
+ contained.examine(user)
+ else
+ ..()
+
/obj/item/weapon/holder/process()
if(!get_holding_mob() || contained.loc != src)
if (is_unsafe_container(loc) && contained.loc == src)
return
+ release_mob()
- for(var/mob/M in contents)
- var/atom/movable/mob_container
- mob_container = M
- mob_container.forceMove(src.loc)//if the holder was placed into a disposal, this should place the animal in the disposal
- M.reset_view()
- var/mob/L = get_holding_mob()
- if (L)
- L.drop_from_inventory(src)
-
- qdel(src)
return
if (isalive && contained.stat == DEAD)
held_death(1)//If we get here, it means the mob died sometime after we picked it up. We pass in 1 so that we can play its deathmessage
@@ -63,6 +59,23 @@
return 0
+//Releases all mobs inside the holder, then deletes it.
+//is_unsafe_container should be checked before calling this
+/obj/item/weapon/holder/proc/release_mob()
+ for(var/mob/M in contents)
+ var/atom/movable/mob_container
+ mob_container = M
+ mob_container.forceMove(src.loc)//if the holder was placed into a disposal, this should place the animal in the disposal
+ M.reset_view()
+ M.Released()
+
+ var/mob/L = get_holding_mob()
+ if (L)
+ L.drop_from_inventory(src)
+
+ qdel(src)
+
+
/obj/item/weapon/holder/attackby(obj/item/weapon/W as obj, mob/user as mob)
for(var/mob/M in src.contents)
M.attackby(W,user)
@@ -73,6 +86,13 @@
//once with it on the floor, and then once in the container
//This conditional allows us to ignore that first one. Handling of mobs dropped on the floor is done in process
if (istype(loc, /turf))
+ spawn(3)
+ //Repeat this check
+ //If we're still on the turf a few frames later, then we have actually been dropped or thrown
+ //Release the mob accordingly
+ if (istype(loc, /turf))
+ release_mob()
+
return
if (istype(loc, /obj/item/weapon/storage)) //The second drop reads the container its placed into as the location
@@ -144,8 +164,12 @@
if(!holder_type || buckled || pinned.len || !Adjacent(grabber))
return
- if ((grabber.hand == 0 && grabber.r_hand) || (grabber.hand == 1 && grabber.l_hand))//Checking if the hand is full
- grabber << "Your hand is full!"
+ if (user == src)
+ if (grabber.r_hand && grabber.l_hand)
+ user << "\red They have no free hands!"
+ return
+ else if ((grabber.hand == 0 && grabber.r_hand) || (grabber.hand == 1 && grabber.l_hand))//Checking if the hand is full
+ grabber << "\red Your hand is full!"
return
src.verbs += /mob/living/proc/get_holder_location//This has to be before we move the mob into the holder
@@ -284,7 +308,7 @@
icon_state = "mouse_brown_sleep"
item_state = "mouse_brown"
icon_state_dead = "mouse_brown_dead"
- //slot_flags = SLOT_EARS //Re-enable this once superballs finishes the sprites
+ slot_flags = SLOT_EARS
contained_sprite = 1
origin_tech = "biotech=2"
w_class = 1
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
index f885933a259..aa4d952679b 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
@@ -300,17 +300,7 @@
var/grabbed_something = 0
for(var/mob/M in T)
- if(istype(M,/mob/living/simple_animal/lizard) || istype(M,/mob/living/simple_animal/mouse))
- src.loc.visible_message("[src.loc] sucks [M] into its decompiler. There's a horrible crunching noise.","It's a bit of a struggle, but you manage to suck [M] into your decompiler. It makes a series of visceral crunching noises.")
- new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
- qdel(M)
- if(wood)
- wood.add_charge(2000)
- if(plastic)
- plastic.add_charge(2000)
- return
-
- else if(istype(M,/mob/living/silicon/robot/drone) && !M.client)
+ if(istype(M,/mob/living/silicon/robot/drone) && !M.client)
var/mob/living/silicon/robot/D = src.loc
@@ -337,6 +327,16 @@
wood.add_charge(2000)
if(plastic)
plastic.add_charge(1000)
+
+
+ else if(istype(M,/mob/living/simple_animal/lizard) || istype(M,/mob/living/simple_animal/mouse))
+ src.loc.visible_message("[src.loc] sucks [M] into its decompiler. There's a horrible crunching noise.","It's a bit of a struggle, but you manage to suck [M] into your decompiler. It makes a series of visceral crunching noises.")
+ new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
+ qdel(M)
+ if(wood)
+ wood.add_charge(2000)
+ if(plastic)
+ plastic.add_charge(2000)
return
else
continue
@@ -403,6 +403,9 @@
user << "Nothing on \the [T] is useful to you."
return
+
+
+
//PRETTIER TOOL LIST.
/mob/living/silicon/robot/drone/installed_modules()
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index e526ed07329..3b5fd2d9fbf 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -16,46 +16,51 @@
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
- var/turns_since_scan = 0
- var/mob/living/simple_animal/mouse/movement_target
+ //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
holder_type = /obj/item/weapon/holder/cat
mob_size = 2.5
+ scan_range = 3//less aggressive about stealing food
+ metabolic_factor = 0.75
density = 0
+ var/mob/living/simple_animal/mouse/mousetarget = null
+ seek_speed = 5
+ pass_flags = PASSTABLE
/mob/living/simple_animal/cat/Life()
//MICE!
- if((src.loc) && isturf(src.loc))
- if(!stat && !resting && !buckled)
- for(var/mob/living/simple_animal/mouse/M in loc)
- if(!M.stat)
- M.splat()
- visible_emote(pick("bites \the [M]!","toys with \the [M].","chomps on \the [M]!"))
- movement_target = null
- stop_automated_movement = 0
- break
+
..()
- for(var/mob/living/simple_animal/mouse/snack in oview(src,5))
- if(snack.stat < DEAD && prob(15))
- audible_emote(pick("hisses and spits!","mrowls fiercely!","eyes [snack] hungrily."))
- break
+ for(var/mob/living/simple_animal/mouse/snack in oview(src,7))
+ if(snack.stat != DEAD && prob(65))//The probability allows her to not get stuck target the first mouse, reducing exploits
+ mousetarget = snack
+ movement_target = snack
+ foodtarget = 0//chasing mice takes precedence over eating food
+ if(prob(15))
+ audible_emote(pick("hisses and spits!","mrowls fiercely!","eyes [snack] hungrily."))
+ break
if(!stat && !resting && !buckled)
- turns_since_scan++
- if (turns_since_scan > 5)
+ if (turns_since_move > 5 || (flee_target || mousetarget))
walk_to(src,0)
- turns_since_scan = 0
+ turns_since_move = 0
if (flee_target) //fleeing takes precendence
handle_flee_target()
else
handle_movement_target()
+ if (!movement_target)
+ walk_to(src,0)
+
+ spawn(2)
+ attack_mice()
+
if(prob(2)) //spooky
var/mob/dead/observer/spook = locate() in range(src,5)
if(spook)
@@ -77,14 +82,32 @@
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)
+ walk_to(src,movement_target,0,seek_move_delay)
+
+/mob/living/simple_animal/cat/proc/attack_mice()
+ if((src.loc) && isturf(src.loc))
+ if(!stat && !resting && !buckled)
+ for(var/mob/living/simple_animal/mouse/M in oview(src,1))
+ if(M.stat != DEAD)
+ M.splat()
+ visible_emote(pick("bites \the [M]!","toys with \the [M].","chomps on \the [M]!"))
+ movement_target = null
+ stop_automated_movement = 0
+ if (prob(75))
+ break//usually only kill one mouse per proc
+
+/mob/living/simple_animal/cat/beg(var/atom/thing, var/atom/holder)
+ visible_emote("licks [get_pronoun(POSESSIVE_ADJECTIVE)] lips and hungrily glares at [holder]'s [thing.name]")
+
+/mob/living/simple_animal/cat/Released()
+ //A thrown cat will immediately attack mice near where it lands
+ handle_movement_target()
+ spawn(3)
+ attack_mice()
+ ..()
/mob/living/simple_animal/cat/death()
.=..()
@@ -105,7 +128,7 @@
/mob/living/simple_animal/cat/proc/set_flee_target(atom/A)
if(A)
flee_target = A
- turns_since_scan = 5
+ turns_since_move = 5
/mob/living/simple_animal/cat/attackby(var/obj/item/O, var/mob/user)
. = ..()
@@ -154,7 +177,7 @@
//walk to friend
stop_automated_movement = 1
movement_target = friend
- walk_to(src, movement_target, near_dist, 4)
+ walk_to(src, movement_target, near_dist, seek_move_delay)
//already following and close enough, stop
else if (current_dist <= near_dist)
diff --git a/code/modules/mob/living/simple_animal/friendly/corgi.dm b/code/modules/mob/living/simple_animal/friendly/corgi.dm
index 4392bfc391c..e9823dc7850 100644
--- a/code/modules/mob/living/simple_animal/friendly/corgi.dm
+++ b/code/modules/mob/living/simple_animal/friendly/corgi.dm
@@ -19,22 +19,18 @@
response_harm = "kicks"
see_in_dark = 5
mob_size = 5
- max_nutrition = 300//Dogs are insatiable eating monsters. This scales with their mob size too
+ max_nutrition = 250//Dogs are insatiable eating monsters. This scales with their mob size too
stomach_size_mult = 30
+ seek_speed = 6
var/obj/item/inventory_head
var/obj/item/inventory_back
var/facehugger
- var/foodtarget = 0//If the corgi's current target is food, set this to 1
- var/scanInterval = 1//Used to control how often ian scans for nearby food
- //It gradually increases up to 10 when hes left alone, to save performance
- //It will drop back to 1 if he spies any food.
- //This short time makes him more responsive to interactions and more fun to play with
/mob/living/simple_animal/corgi/New()
..()
nutrition = max_nutrition * 0.3//Ian doesn't start with a full belly so will be hungry at roundstart
- nutrition_step = mob_size * 0.15
+ nutrition_step = mob_size * 0.12
//IAN! SQUEEEEEEEEE~
/mob/living/simple_animal/corgi/Ian
@@ -42,8 +38,7 @@
real_name = "Ian" //Intended to hold the name without altering it.
gender = MALE
desc = "It's a corgi."
- var/turns_since_scan = 0
- var/obj/movement_target
+ //var/obj/movement_target
response_help = "pets"
response_disarm = "bops"
response_harm = "kicks"
@@ -51,72 +46,7 @@
/mob/living/simple_animal/corgi/Ian/Life()
..()
- //Feeding, chasing food, FOOOOODDDD
if(!stat && !resting && !buckled)
- turns_since_scan++
- if(turns_since_scan > scanInterval)
- turns_since_scan = 0
- if((movement_target) && (!(isturf(movement_target.loc) || ishuman(movement_target.loc))) || (foodtarget && !can_eat() ))
- movement_target = null
- foodtarget = 0
- stop_automated_movement = 0
- if( !movement_target || !(movement_target.loc in oview(src, 7)) )
- movement_target = null
- foodtarget = 0
- stop_automated_movement = 0
- if (can_eat())
- for(var/obj/item/weapon/reagent_containers/food/snacks/S in oview(src,7))
- if(isturf(S.loc) || ishuman(S.loc))
- movement_target = S
- foodtarget = 1
- break
-
- //Ian looks for food in people's hand
- if (!movement_target)
- var/obj/item/weapon/reagent_containers/food/snacks/F = null
- for(var/mob/living/carbon/human/H in oview(src,7))
- if(istype(H.l_hand, /obj/item/weapon/reagent_containers/food/snacks))
- F = H.l_hand
-
- if(istype(H.r_hand, /obj/item/weapon/reagent_containers/food/snacks))
- F = H.r_hand
-
- if (F)
- movement_target = F
- foodtarget = 1
- break
-
- if(movement_target)
- scanInterval = 1
- stop_automated_movement = 1
- step_to(src,movement_target,1)
- sleep(3)
- step_to(src,movement_target,1)
- sleep(3)
- step_to(src,movement_target,1)
-
- if(movement_target) //Not redundant due to sleeps, Item can be gone in 6 decisecomds
- if (movement_target.loc.x < src.x)
- set_dir(WEST)
- else if (movement_target.loc.x > src.x)
- set_dir(EAST)
- else if (movement_target.loc.y < src.y)
- set_dir(SOUTH)
- else if (movement_target.loc.y > src.y)
- set_dir(NORTH)
- else
- set_dir(SOUTH)
-
- if(isturf(movement_target.loc))
- for(var/direction in alldirs)
- var/turf/T = get_step(src, direction)
- if(T == movement_target.loc)
- UnarmedAttack(movement_target)
- else if(ishuman(movement_target.loc) && prob(20))
- visible_emote("stares at the [movement_target] that [movement_target.loc] has with sad puppy eyes.")
- else
- scanInterval = min(scanInterval+1, 10)//If nothing is happening, ian's scanning frequency slows down to save processing
-
if(prob(1))
visible_emote(pick("dances around","chases their tail"))
spawn(0)
@@ -124,6 +54,13 @@
set_dir(i)
sleep(1)
+
+
+/mob/living/simple_animal/corgi/beg(var/atom/thing, var/atom/holder)
+ visible_emote("stares at the [thing] that [holder] has with sad puppy eyes.")
+
+
+
/obj/item/weapon/reagent_containers/food/snacks/meat/corgi
name = "Corgi meat"
desc = "Tastes like... well you know..."
@@ -134,6 +71,11 @@
for(var/mob/M in viewers(user, null))
if ((M.client && !( M.blinded )))
M.show_message("\blue [user] baps [name] on the nose with the rolled up [O]")
+ scan_interval = max_scan_interval//discipline your dog to make it stop stealing food for a while
+ movement_target = null
+ foodtarget = 0
+ stop_automated_movement = 0
+ turns_since_scan = 0
spawn(0)
for(var/i in list(1,2,4,8,4,2,1,2))
set_dir(i)
@@ -199,7 +141,6 @@
response_help = "pets"
response_disarm = "bops"
response_harm = "kicks"
- var/turns_since_scan = 0
var/puppies = 0
//Lisa already has a cute bow!
diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm
index 616cb8db862..76cacd0ae4c 100644
--- a/code/modules/mob/living/simple_animal/friendly/crab.dm
+++ b/code/modules/mob/living/simple_animal/friendly/crab.dm
@@ -17,7 +17,8 @@
response_harm = "stomps"
stop_automated_movement = 1
friendly = "pinches"
- mob_size = 3
+ mob_size = 5
+ hunger_enabled = 0
var/obj/item/inventory_head
var/obj/item/inventory_mask
diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
index 55e186050b7..35df6be047f 100644
--- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
+++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
@@ -30,6 +30,9 @@
udder.my_atom = src
..()
+/mob/living/simple_animal/hostile/retaliate/goat/beg(var/atom/thing, var/atom/holder)
+ visible_emote("butts insistently at [holder]'s legs and reaches towards their [thing].")
+
/mob/living/simple_animal/hostile/retaliate/goat/Life()
. = ..()
if(.)
@@ -104,7 +107,9 @@
response_disarm = "gently pushes aside"
response_harm = "kicks"
attacktext = "kicked"
- maxHealth = 250//more robust because large
+ health = 250
+ autoseek_food = 0
+ beg_for_food = 0
var/datum/reagents/udder = null
mob_size = 20//based on mass of holstein fresian dairy cattle, what the sprite is based on
@@ -171,6 +176,8 @@
pass_flags = PASSTABLE | PASSGRILLE
small = 1
holder_type = /obj/item/weapon/holder/chick
+ autoseek_food = 0
+ beg_for_food = 0
density = 0
mob_size = 0.75//just a rough estimate, the real value should be way lower
@@ -209,7 +216,7 @@ var/global/chicken_count = 0
speak_chance = 2
turns_per_move = 3
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
- meat_amount = 2
+ meat_amount = 4
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
diff --git a/code/modules/mob/living/simple_animal/friendly/fox.dm b/code/modules/mob/living/simple_animal/friendly/fox.dm
index f29b3eb869f..1870454e935 100644
--- a/code/modules/mob/living/simple_animal/friendly/fox.dm
+++ b/code/modules/mob/living/simple_animal/friendly/fox.dm
@@ -17,7 +17,7 @@
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
- mob_size = 8
+ mob_size = 4
//Captain fox
/mob/living/simple_animal/corgi/fox/Chauncey
name = "Chauncey"
diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm
index 352bf1aa2a8..717d7cae9e8 100644
--- a/code/modules/mob/living/simple_animal/friendly/lizard.dm
+++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm
@@ -18,6 +18,12 @@
mob_size = 1
holder_type = /obj/item/weapon/holder/lizard
density = 0
+ seek_speed = 0.75
+
+/mob/living/simple_animal/lizard/New()
+ ..()
+
+ nutrition = rand(max_nutrition*0.25, max_nutrition*0.75)
/mob/living/simple_animal/lizard/attack_hand(mob/living/carbon/human/M as mob)
if (src.stat == DEAD)//If the creature is dead, we don't pet it, we just pickup the corpse on click
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index f6f059da462..75d57824077 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -31,6 +31,7 @@
response_disarm = "gently pushes aside"
response_harm = "stamps on"
density = 0
+ meat_amount = 1
var/body_color //brown, gray and white, leave blank for random
layer = MOB_LAYER
min_oxy = 16 //Require atleast 16kPA oxygen
@@ -41,6 +42,9 @@
mob_size = 1
holder_type = /obj/item/weapon/holder/mouse
digest_factor = 0.05
+ min_scan_interval = 2
+ max_scan_interval = 20
+ seek_speed = 1
/mob/living/simple_animal/mouse/Life()
..()
@@ -105,7 +109,9 @@
/mob/living/simple_animal/mouse/speak_audio()
squeak_soft(0)
-
+/mob/living/simple_animal/mouse/beg(var/atom/thing, var/atom/holder)
+ squeak_soft(0)
+ visible_emote("squeaks timidly, sniffs the air and gazes longingly up at \the [thing.name].")
/mob/living/simple_animal/mouse/attack_hand(mob/living/carbon/human/M as mob)
if (src.stat == DEAD)//If the mouse is dead, we don't pet it, we just pickup the corpse on click
@@ -132,34 +138,37 @@
//Plays a sound.
//This is triggered when a mob steps on an NPC mouse, or manually by a playermouse
/mob/living/simple_animal/mouse/proc/squeak(var/manual = 1)
- playsound(src, 'sound/effects/mousesqueek.ogg', 70, 1)
- if (manual)
- log_say("[key_name(src)] squeaks! ")
+ if (stat == CONSCIOUS)
+ playsound(src, 'sound/effects/mousesqueek.ogg', 70, 1)
+ if (manual)
+ log_say("[key_name(src)] squeaks! ")
//Plays a random selection of four sounds, at a low volume
//This is triggered randomly periodically by any mouse, or manually
/mob/living/simple_animal/mouse/proc/squeak_soft(var/manual = 1)
- var/list/new_squeaks = last_softsqueak ? soft_squeaks - last_softsqueak : soft_squeaks
- var/sound = pick(new_squeaks)
+ if (stat == CONSCIOUS)
+ var/list/new_squeaks = last_softsqueak ? soft_squeaks - last_softsqueak : soft_squeaks
+ var/sound = pick(new_squeaks)
- last_softsqueak = sound
- playsound(src, sound, 6, 1)
+ last_softsqueak = sound
+ playsound(src, sound, 5, 1, -4.6)
- if (manual)
- log_say("[key_name(src)] squeaks softly! ")
+ if (manual)
+ log_say("[key_name(src)] squeaks softly! ")
//Plays a loud sound
//Triggered manually, when a mouse dies, or rarely when its stepped on
/mob/living/simple_animal/mouse/proc/squeak_loud(var/manual = 0)
- if (squeals > 0 || !manual)
- playsound(src, 'sound/effects/creatures/mouse_squeak_loud.ogg', 50, 1)
- squeals --
- log_say("[key_name(src)] squeals! ")
- else
- src << "\red Your hoarse mousey throat can't squeal just now, stop and take a breath!"
+ if (stat == CONSCIOUS)
+ if (squeals > 0 || !manual)
+ playsound(src, 'sound/effects/creatures/mouse_squeak_loud.ogg', 50, 1)
+ squeals --
+ log_say("[key_name(src)] squeals! ")
+ else
+ src << "\red Your hoarse mousey throat can't squeal just now, stop and take a breath!"
//Wrapper verbs for the squeak functions
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 34ede21d6cf..dab72323ec0 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -27,6 +27,8 @@
var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals.
var/wander = 1 // Does the mob wander around when idle?
var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it.
+ var/atom/movement_target = null//Thing we're moving towards
+ var/turns_since_scan = 0
//Interaction
var/response_help = "tries to help"
@@ -69,20 +71,44 @@
//Hunger/feeding vars
var/hunger_enabled = 1//If set to 0, a creature ignores hunger
- var/max_nutrition = 75
- var/nutrition_step = 0.2 //nutrition lost per tick and per step, calculated from mob_size, 0.4 is a fallback
+ var/max_nutrition = 50
+ var/metabolic_factor = 1//A multiplier on how fast nutrition is lost. used to tweak the rates on a per-animal basis
+ var/nutrition_step = 0.2 //nutrition lost per tick and per step, calculated from mob_size, 0.2 is a fallback
var/bite_factor = 0.4
var/digest_factor = 0.2 //A multiplier on how quickly reagents are digested
var/stomach_size_mult = 5
+ //Food behaviour vars
+ var/autoseek_food = 1//If 0. this animal will not automatically eat
+ var/beg_for_food = 1//If 0, this animal will not show interest in food held by a person
+ var/min_scan_interval = 1//Minimum and maximum number of procs between a foodscan. Animals will slow down if there's no food around for a while
+ var/max_scan_interval = 15
+ var/scan_interval = 5//current scan interval, clamped between min and max
+ //It gradually increases up to max when its left alone, to save performance
+ //It will drop back to 1 if it spies any food.
+ //This short time makes animals more responsive to interactions and more fun to play with
+
+ var/seek_speed = 2//How many tiles per second the animal will move towards food
+ var/seek_move_delay
+ var/scan_range = 6//How far around the animal will look for food
+ var/foodtarget = 0
+ //Used to control how often ian scans for nearby food
+
+
+
+/mob/living/simple_animal/proc/beg(var/atom/thing, var/atom/holder)
+ visible_emote("gazes longingly at [holder]'s [thing]")
+
/mob/living/simple_animal/New()
..()
+ seek_move_delay = (1 / seek_speed) / (world.tick_lag / 10)//number of ticks between moves
+ turns_since_scan = rand(min_scan_interval, max_scan_interval)//Randomise this at the start so animals don't sync up
verbs -= /mob/verb/observe
health = maxHealth
if (mob_size)
- nutrition_step = mob_size * 0.05
+ nutrition_step = mob_size * 0.03 * metabolic_factor
bite_factor = mob_size * 0.3
- max_nutrition *= 1 + (nutrition_step*3)//Max nutrition scales faster than costs, so bigger creatures eat less often
+ max_nutrition *= 1 + (nutrition_step*4)//Max nutrition scales faster than costs, so bigger creatures eat less often
reagents = new/datum/reagents(stomach_size_mult*mob_size, src)
else
reagents = new/datum/reagents(20, src)
@@ -94,6 +120,11 @@
if(src.nutrition && src.stat != DEAD && hunger_enabled)
src.nutrition -= nutrition_step
+/mob/living/simple_animal/Released()
+ //These will cause mobs to immediately do things when released.
+ scan_interval = min_scan_interval
+ turns_since_move = turns_per_move
+ ..()
/mob/living/simple_animal/Login()
if(src && src.client)
@@ -115,6 +146,8 @@
..()
//Health
+ if(stat == DEAD)
+ return 0
if(health <= 0)
@@ -130,10 +163,13 @@
update_canmove()
handle_supernatural()
process_food()
+
+ handle_foodscanning()
//Movement
+ turns_since_move++
if(!client && !stop_automated_movement && wander && !anchored)
if(isturf(src.loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc.
- turns_since_move++
+
if(turns_since_move >= turns_per_move)
if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled
/var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND
@@ -410,6 +446,8 @@
death()
/mob/living/simple_animal/death(gibbed, deathmessage = "dies!")
+ walk_to(src,0)
+ movement_target = null
icon_state = icon_dead
density = 0
return ..(gibbed,deathmessage)
@@ -488,6 +526,81 @@
gib()
+//Code to handle finding and nomming nearby food items
+/mob/living/simple_animal/proc/handle_foodscanning()
+ if (client || !hunger_enabled || !autoseek_food)
+ return 0
+
+ //Feeding, chasing food, FOOOOODDDD
+ if(!stat && !resting && !buckled)
+
+ turns_since_scan++
+ if(turns_since_scan >= scan_interval)
+ turns_since_scan = 0
+ if((movement_target) && (!(isturf(movement_target.loc) || ishuman(movement_target.loc))) || (foodtarget && !can_eat() ))
+ movement_target = null
+ foodtarget = 0
+ stop_automated_movement = 0
+ if( !movement_target || !(movement_target.loc in oview(src, 7)) )
+ walk_to(src,0)
+ movement_target = null
+ foodtarget = 0
+ stop_automated_movement = 0
+ if (can_eat())
+ for(var/obj/item/weapon/reagent_containers/food/snacks/S in oview(src,7))
+ if(isturf(S.loc) || ishuman(S.loc))
+ movement_target = S
+ foodtarget = 1
+ break
+
+ //Look for food in people's hand
+ if (!movement_target && beg_for_food)
+ var/obj/item/weapon/reagent_containers/food/snacks/F = null
+ for(var/mob/living/carbon/human/H in oview(src,scan_range))
+ if(istype(H.l_hand, /obj/item/weapon/reagent_containers/food/snacks))
+ F = H.l_hand
+
+ if(istype(H.r_hand, /obj/item/weapon/reagent_containers/food/snacks))
+ F = H.r_hand
+
+ if (F)
+ movement_target = F
+ foodtarget = 1
+ break
+
+ if(movement_target)
+ scan_interval = min_scan_interval
+ stop_automated_movement = 1
+
+ if (istype(movement_target.loc, /turf))
+ walk_to(src,movement_target,0, seek_move_delay)//Stand ontop of food
+ else
+ walk_to(src,movement_target.loc,1, seek_move_delay)//Don't stand ontop of people
+
+
+
+ if(movement_target) //Not redundant due to sleeps, Item can be gone in 6 decisecomds
+ if (movement_target.loc.x < src.x)
+ set_dir(WEST)
+ else if (movement_target.loc.x > src.x)
+ set_dir(EAST)
+ else if (movement_target.loc.y < src.y)
+ set_dir(SOUTH)
+ else if (movement_target.loc.y > src.y)
+ set_dir(NORTH)
+ else
+ set_dir(SOUTH)
+
+ if(isturf(movement_target.loc) && Adjacent(get_turf(movement_target), src))
+ UnarmedAttack(movement_target)
+ if (get_turf(movement_target) == src.loc)
+ set_dir(pick(1,2,4,8,1,1))//Face a random direction when eating, but mostly upwards
+ else if(ishuman(movement_target.loc) && Adjacent(src, get_turf(movement_target)) && prob(15))
+ beg(movement_target, movement_target.loc)
+ else
+ scan_interval = max(min_scan_interval, min(scan_interval+1, max_scan_interval))//If nothing is happening, ian's scanning frequency slows down to save processing
+
+
//For picking up small animals
/mob/living/simple_animal/MouseDrop(atom/over_object)
if (holder_type)//we need a defined holder type in order for picking up to work
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index a9e1449cdbc..291a58dcc9a 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1065,6 +1065,11 @@ mob/proc/yank_out_object()
return 0
+/mob/proc/Released()
+ //This is called when the mob is let out of a holder
+ //Override for mob-specific functionality
+ return
+
/mob/proc/updateicon()
return
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index b0647e6a5ab..f5781bef512 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -1127,4 +1127,56 @@ var/list/wierd_mobs_inclusive = list( /mob/living/simple_animal/construct,
else return null
+
+#define POSESSIVE_PRONOUN 0
+#define POSESSIVE_ADJECTIVE 1
+#define REFLEXIVE 2
+#define SUBJECTIVE_PERSONAL 3
+#define OBJECTIVE_PERSONAL 4
+/mob/proc/get_pronoun(var/type)
+ switch (type)
+ if (POSESSIVE_PRONOUN)
+ switch(gender)
+ if (MALE)
+ return "his"
+ if (FEMALE)
+ return "hers"
+ else
+ return "theirs"
+ if (POSESSIVE_ADJECTIVE)
+ switch(gender)
+ if (MALE)
+ return "his"
+ if (FEMALE)
+ return "her"
+ else
+ return "their"
+ if (REFLEXIVE)
+ switch(gender)
+ if (MALE)
+ return "himself"
+ if (FEMALE)
+ return "herself"
+ else
+ return "themselves"
+ if (SUBJECTIVE_PERSONAL)
+ switch(gender)
+ if (MALE)
+ return "he"
+ if (FEMALE)
+ return "she"
+ else
+ return "they"
+ if (OBJECTIVE_PERSONAL)
+ switch(gender)
+ if (MALE)
+ return "him"
+ if (FEMALE)
+ return "her"
+ else
+ return "them"
+
+ else
+ return "its"//Something went wrong
+
#undef SAFE_PERP
diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm
index 63391965734..326ae34dac4 100644
--- a/code/modules/paperwork/faxmachine.dm
+++ b/code/modules/paperwork/faxmachine.dm
@@ -11,7 +11,7 @@ var/list/sent_faxes = list() //cache for faxes that have been sent by the admins
icon_state = "fax"
insert_anim = "faxsend"
req_one_access = list(access_lawyer, access_heads, access_armory) //Warden needs to be able to Fax solgov too.
-
+ density = 0//It's a small machine that sits on a table, this allows small things to walk under that table
use_power = 1
idle_power_usage = 30
active_power_usage = 200
diff --git a/code/modules/reagents/dispenser/dispenser_presets.dm b/code/modules/reagents/dispenser/dispenser_presets.dm
index b41864791c9..161716efd86 100644
--- a/code/modules/reagents/dispenser/dispenser_presets.dm
+++ b/code/modules/reagents/dispenser/dispenser_presets.dm
@@ -63,6 +63,7 @@
icon_state = "soda_dispenser"
ui_title = "Soda Dispenser"
accept_drinking = 1
+ density = 0//It's a half-height machine that sits on a table, this allows small things to walk under that table
/obj/machinery/chemical_dispenser/bar_soft/full
spawn_cartridges = list(
@@ -91,6 +92,7 @@
icon_state = "booze_dispenser"
ui_title = "Booze Dispenser"
accept_drinking = 1
+ density = 0//It's a half-height machine that sits on a table, this allows small things to walk under that table
/obj/machinery/chemical_dispenser/bar_alc/full
spawn_cartridges = list(
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index adc16443a6a..1b64a6c79e7 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -110,6 +110,8 @@
else if (isanimal(M))
var/mob/living/simple_animal/SA = M
+ SA.scan_interval = SA.min_scan_interval//Feeding an animal will make it suddenly care about food
+
var/m_bitesize = bitesize * SA.bite_factor//Modified bitesize based on creature size
var/amount_eaten = m_bitesize
@@ -127,8 +129,12 @@
bitecount++
if (amount_eaten >= m_bitesize)
user.visible_message("[user] feeds [M] [src].")
+ if (!istype(M.loc, /turf))//held mobs don't see visible messages
+ M << "[user] feeds you [src]."
else
user.visible_message("[user] feeds [M] a tiny bit of [src]. It looks full.")
+ if (!istype(M.loc, /turf))
+ M << "[user] feeds you a tiny bit of [src]. You feel pretty full!"
On_Consume(M)
return 1
else
diff --git a/html/changelogs/Nanako-Animals.yml b/html/changelogs/Nanako-Animals.yml
new file mode 100644
index 00000000000..cfb9f9d7e86
--- /dev/null
+++ b/html/changelogs/Nanako-Animals.yml
@@ -0,0 +1,46 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Nanako
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "Added automatic feeding to most animals which can eat. They will eat food nearby, and beg for food held by others. Animals may take a while to notice food near them. Also tweaked a few animal sizes, metabolisms and meat amounts. Animals are a little less needy for food. Except dogs."
+ - tweak: "Disciplining a dog with a rolled up newspaper will make it stop stealing food for a little while."
+ - bugfix: "Dead mice don't squeak."
+ - tweak: "Adjusted feedback messages for animals climbing onto people, and animals being fed by people."
+ - bugfix: "Improved animal AI, and fixed issues of runtime moving around while dead."
+ - imageadd: "Mice can now be worn on your shoulder (ear slot). Special thanks to superballs for making sprites"
+ - tweak: "Maintenance drone lawset has been altered based on administrative feedback."
+ - rscadd: "Drink dispensers and fax machines on tables, can now be walked under by small animals. Including cats."
+ - rscadd: "Examining a held animal now works properly."
+
+
\ No newline at end of file
diff --git a/icons/mob/mouse.dmi b/icons/mob/mouse.dmi
index 04d57873e28..ee4a5648f7f 100644
Binary files a/icons/mob/mouse.dmi and b/icons/mob/mouse.dmi differ