Animal Sleeping and Familiars (#1881)

A small PR with a couple of effects:

Makes simple animal sleeping a bit more robust, and fixes mice never waking up. Also adds napping support for NPC cats. Sadly it doesnt work for kittens as theyre lacking a sleep animation. Napping support can be easily added to any other creatures though many are lacking suitable sprites

The rest verb for animals is moved to the abilities tab. its more streamlined to have it there since thats where all their other things are

Tweaks a couple of wizard familiars, swaps the mouse and goat role+spells, and reclasses the mouse familiar to inherit from normal mice, so as to preserve base mouse functionality (squeek). Wizard familiars are unreleased content so no changelog

And adds a small feature for mice to shift their pixel offset around gradually as they move. So that they dont stack up and look like one when theres many of them, it adds a little more visual variety.
This commit is contained in:
NanakoAC
2017-03-19 00:24:38 +02:00
committed by skull132
parent 67bb388a5a
commit 301baf8559
6 changed files with 266 additions and 159 deletions
@@ -13,7 +13,7 @@
min_oxy = 1 //still require a /bit/ of air.
max_co2 = 0
unsuitable_atoms_damage = 1
hunger_enabled = 0
supernatural = 1
@@ -21,7 +21,7 @@
/mob/living/simple_animal/familiar/New()
..()
add_language("Ceti Basic")
add_language(LANGUAGE_TCB)
for(var/spell in wizardy_spells)
src.add_spell(new spell, "const_spell_ready")
@@ -104,26 +104,27 @@
/mob/living/simple_animal/familiar/goat
name = "goat"
desc = "An elder looking goat."
desc = "A sprightly looking goat."
icon_state = "goat"
icon_living = "goat"
icon_dead = "goat_dead"
speak_emote = list("brays")
attacktext = "kicked"
mob_size = MOB_SMALL
health = 80
maxHealth = 80
health = 50
maxHealth = 50
melee_damage_lower = 8
melee_damage_upper = 12
mob_size = 4.5 //weight based on Chanthangi goats
density = 0
wizardy_spells = list(/spell/aoe_turf/smoke)
wizardy_spells = list(/spell/targeted/heal_target,
/spell/targeted/heal_target/area)
/mob/living/simple_animal/familiar/pet //basically variants of normal animals with spells.
icon = 'icons/mob/animal.dmi'
var/icon_rest //so that we can have resting little guys.
/mob/living/simple_animal/familiar/pet/MouseDrop(atom/over_object)
var/mob/living/carbon/H = over_object
@@ -135,44 +136,8 @@
else
return ..()
/mob/living/simple_animal/familiar/pet/Life()
..()
if(!icon_rest)
return
if(stat == UNCONSCIOUS || resting)
icon_state = icon_rest
/mob/living/simple_animal/familiar/pet/mouse
name = "mouse"
desc = "A small rodent. It looks very old."
icon_state = "mouse_gray"
icon_living = "mouse_gray"
icon_dead = "mouse_gray_dead"
icon_rest = "mouse_gray_sleep"
speak_emote = list("squeeks")
holder_type = /obj/item/weapon/holder/mouse
pass_flags = PASSTABLE
mob_size = MOB_MINISCULE
see_in_dark = 8
see_invisible = SEE_INVISIBLE_NOLIGHTING
response_harm = "stamps on"
health = 15
maxHealth = 15
melee_damage_lower = 1
melee_damage_upper = 1
attacktext = "nibbled"
wizardy_spells = list(/spell/aoe_turf/smoke)
/mob/living/simple_animal/familiar/pet/mouse/New()
..()
verbs += /mob/living/proc/ventcrawl
verbs += /mob/living/proc/hide
/mob/living/simple_animal/familiar/pet/cat
name = "black cat"
@@ -181,6 +146,7 @@
icon_living = "cat3"
icon_dead = "cat3_dead"
icon_rest = "cat3_rest"
can_nap = 1
see_in_dark = 8
see_invisible = SEE_INVISIBLE_NOLIGHTING
@@ -189,10 +155,44 @@
holder_type = /obj/item/weapon/holder/cat
mob_size = MOB_SMALL
health = 25
maxHealth = 25
health = 45
maxHealth = 45
melee_damage_lower = 3
melee_damage_upper = 4
attacktext = "clawed"
density = 0
wizardy_spells = list(/spell/targeted/subjugation)
/mob/living/simple_animal/mouse/familiar
name = "ancient mouse"
desc = "A small rodent. It looks very old."
body_color = "gray"
see_in_dark = 8
see_invisible = SEE_INVISIBLE_NOLIGHTING
health = 25
maxHealth = 25
melee_damage_lower = 1
melee_damage_upper = 1
attacktext = "nibbled"
universal_speak = 1
universal_understand = 1
min_oxy = 1 //still require a /bit/ of air.
max_co2 = 0
unsuitable_atoms_damage = 1
supernatural = 1
/mob/living/simple_animal/mouse/familiar/New()
..()
add_spell(new /spell/targeted/heal_target, "const_spell_ready")
add_spell(new /spell/targeted/heal_target/area, "const_spell_ready")
add_language(LANGUAGE_TCB)
name = initial(name)
desc = initial(desc)
@@ -6,6 +6,8 @@
item_state = "cat2"
icon_living = "cat2"
icon_dead = "cat2_dead"
icon_rest = "cat2_rest"
can_nap = 1
speak = list("Meow!","Esp!","Purr!","HSSSSS")
speak_emote = list("purrs", "meows")
emote_hear = list("meows","mews")
@@ -38,42 +40,43 @@
..()
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)
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)
if (turns_since_move > 5 || (flee_target || mousetarget))
if(!buckled)
if (turns_since_move > 5 || (flee_target || mousetarget))
walk_to(src,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)
turns_since_move = 0
if (flee_target) //fleeing takes precendence
handle_flee_target()
else
handle_movement_target()
spawn(2)
attack_mice()
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)
var/turf/T = spook.loc
var/list/visible = list()
for(var/obj/O in T.contents)
if(!O.invisibility && O.name)
visible += O
if(visible.len)
var/atom/A = pick(visible)
visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].")
if(prob(2)) //spooky
var/mob/dead/observer/spook = locate() in range(src,5)
if(spook)
var/turf/T = spook.loc
var/list/visible = list()
for(var/obj/O in T.contents)
if(!O.invisibility && O.name)
visible += O
if(visible.len)
var/atom/A = pick(visible)
visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].")
/mob/living/simple_animal/cat/proc/handle_movement_target()
//if our target is neither inside a turf or inside a human(???), stop
@@ -240,6 +243,8 @@
item_state = "cat"
icon_living = "cat"
icon_dead = "cat_dead"
icon_rest = "cat_rest"
can_nap = 1
befriend_job = "Chief Medical Officer"
holder_type = /obj/item/weapon/holder/cat/black
@@ -254,6 +259,7 @@
item_state = "kitten"
icon_living = "kitten"
icon_dead = "kitten_dead"
can_nap = 0 //No resting sprite
gender = NEUTER
holder_type = /obj/item/weapon/holder/cat/kitten
@@ -269,6 +275,8 @@
item_state = "cat3"
icon_living = "cat3"
icon_dead = "cat3_dead"
icon_rest = "cat3_rest"
can_nap = 1
var/friend_name = "Erstatz Vryroxes"
holder_type = /obj/item/weapon/holder/cat/black
@@ -1,12 +1,15 @@
/mob/living/simple_animal/mouse
name = "mouse"
real_name = "mouse"
desc = "It's a small rodent."
desc = "It's a small rodent, often seen hiding in maintenance areas and making a nuisance of itself."
icon = 'icons/mob/mouse.dmi'
icon_state = "mouse_gray"
item_state = "mouse_gray"
icon_living = "mouse_gray"
icon_dead = "mouse_gray_dead"
icon_rest = "mouse_gray_sleep"
can_nap = 1
speak = list("Squeek!","SQUEEK!","Squeek?")
speak_emote = list("squeeks","squeeks","squiks")
emote_hear = list("squeeks","squeaks","squiks")
@@ -53,6 +56,7 @@
kitchen_tag = "rodent"
/mob/living/simple_animal/mouse/Life()
if(..())
@@ -73,25 +77,19 @@
squeals++
last_squealgain = world.time
if(!ckey && stat == CONSCIOUS && prob(0.5))
stat = UNCONSCIOUS
icon_state = "mouse_[body_color]_sleep"
wander = 0
speak_chance = 0
//snuffles
else if(stat == UNCONSCIOUS)
if(ckey || prob(1))
stat = CONSCIOUS
icon_state = "mouse_[body_color]"
wander = 1
speak_chance = initial(speak_chance)
else if(prob(5))
audible_emote("snuffles.")
else
if ((world.time - timeofdeath) > decompose_time)
dust()
//Pixel offsetting as they scamper around
/mob/living/simple_animal/mouse/Move()
if(..())
if (prob(50))
pixel_x += rand(-2,2)
pixel_x = Clamp(pixel_x, -10, 10)
else
pixel_y += rand(-2,2)
pixel_y = Clamp(pixel_y, -4, 14)
/mob/living/simple_animal/mouse/New()
..()
@@ -109,8 +107,8 @@
icon_state = "mouse_[body_color]"
item_state = "mouse_[body_color]"
icon_living = "mouse_[body_color]"
icon_rest = "mouse_[body_color]_sleep"
icon_dead = "mouse_[body_color]_dead"
desc = "It's a small [body_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself."
if (body_color == "brown")
holder_type = /obj/item/weapon/holder/mouse/brown
if (body_color == "gray")
@@ -144,17 +142,6 @@
if(client)
client.time_died_as_mouse = world.time
/mob/living/simple_animal/mouse/MouseDrop(atom/over_object)
var/mob/living/carbon/H = over_object
if(!istype(H) || !Adjacent(H)) return ..()
if(H.a_intent == "help")
get_scooped(H, usr)
return
else
return ..()
//Plays a sound.
@@ -170,7 +157,7 @@
//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)
if (stat == CONSCIOUS)
if (stat != DEAD) //Soft squeaks are allowed while sleeping
var/list/new_squeaks = last_softsqueak ? soft_squeaks - last_softsqueak : soft_squeaks
var/sound = pick(new_squeaks)
@@ -215,6 +202,7 @@
if(!stat)
var/mob/M = AM
M << "\blue \icon[src] Squeek!"
poke(1) //Wake up if stepped on
if (prob(95))
squeak(0)
else
@@ -233,15 +221,9 @@
/mob/living/simple_animal/mouse/dust()
..(anim = "dust_[body_color]", remains = /obj/effect/decal/remains/mouse, iconfile = 'icons/mob/mouse.dmi')
/mob/living/simple_animal/mouse/lay_down()
set name = "Rest"
set category = "IC"
resting = !resting
icon_state = resting ? "mouse_[body_color]_sleep" : "mouse_[body_color]"
src << "\blue You are now [resting ? "resting" : "getting up"]"
canmove = !resting
/*
@@ -251,16 +233,19 @@
/mob/living/simple_animal/mouse/white
body_color = "white"
icon_state = "mouse_white"
icon_rest = "mouse_white_sleep"
holder_type = /obj/item/weapon/holder/mouse/white
/mob/living/simple_animal/mouse/gray
body_color = "gray"
icon_state = "mouse_gray"
icon_rest = "mouse_gray_sleep"
holder_type = /obj/item/weapon/holder/mouse/gray
/mob/living/simple_animal/mouse/brown
body_color = "brown"
icon_state = "mouse_brown"
icon_rest = "mouse_brown_sleep"
holder_type = /obj/item/weapon/holder/mouse/brown
//TOM IS ALIVE! SQUEEEEEEEE~K :)
@@ -96,6 +96,10 @@
var/kitchen_tag = "animal" //Used for cooking with animals
//Napping
var/can_nap = 0
var/icon_rest = null
/mob/living/simple_animal/proc/beg(var/atom/thing, var/atom/holder)
visible_emote("gazes longingly at [holder]'s [thing]")
@@ -115,6 +119,9 @@
reagents = new/datum/reagents(20, src)
nutrition = max_nutrition
if (can_nap)
verbs += /mob/living/simple_animal/lay_down
/mob/living/simple_animal/Move(NewLoc, direct)
. = ..()
if(.)
@@ -142,6 +149,8 @@
user << "<span class='notice'>It looks hungry.</span>"
else if ((reagents.total_volume > 0 && nutrition > max_nutrition *0.75) || nutrition > max_nutrition *0.9)
user << "It looks full and contented."
if (stat == DEAD)
user << "<span class='danger'>It looks dead.</span>"
if (health < maxHealth * 0.5)
user << "<span class='danger'>It looks badly wounded.</span>"
else if (health < maxHealth)
@@ -169,51 +178,58 @@
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.
if (!client)
if(!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.
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
moving_to = pick(cardinal)
dir = moving_to //How about we turn them the direction they are moving, yay.
Move(get_step(src,moving_to))
turns_since_move = 0
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
moving_to = pick(cardinal)
dir = moving_to //How about we turn them the direction they are moving, yay.
Move(get_step(src,moving_to))
turns_since_move = 0
//Speaking
if(!client && speak_chance)
if(rand(0,200) < speak_chance)
if(speak && speak.len)
if((emote_hear && emote_hear.len) || (emote_see && emote_see.len))
var/length = speak.len
if(emote_hear && emote_hear.len)
length += emote_hear.len
if(emote_see && emote_see.len)
length += emote_see.len
var/randomValue = rand(1,length)
if(randomValue <= speak.len)
say(pick(speak))
//Speaking
if(speak_chance)
if(rand(0,200) < speak_chance)
if(speak && speak.len)
if((emote_hear && emote_hear.len) || (emote_see && emote_see.len))
var/length = speak.len
if(emote_hear && emote_hear.len)
length += emote_hear.len
if(emote_see && emote_see.len)
length += emote_see.len
var/randomValue = rand(1,length)
if(randomValue <= speak.len)
say(pick(speak))
else
randomValue -= speak.len
if(emote_see && randomValue <= emote_see.len)
visible_emote("[pick(emote_see)].")
else
audible_emote("[pick(emote_hear)].")
else
randomValue -= speak.len
if(emote_see && randomValue <= emote_see.len)
say(pick(speak))
else
if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len))
visible_emote("[pick(emote_see)].")
if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len))
audible_emote("[pick(emote_hear)].")
if((emote_hear && emote_hear.len) && (emote_see && emote_see.len))
var/length = emote_hear.len + emote_see.len
var/pick = rand(1,length)
if(pick <= emote_see.len)
visible_emote("[pick(emote_see)].")
else
audible_emote("[pick(emote_hear)].")
else
say(pick(speak))
else
if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len))
visible_emote("[pick(emote_see)].")
if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len))
audible_emote("[pick(emote_hear)].")
if((emote_hear && emote_hear.len) && (emote_see && emote_see.len))
var/length = emote_hear.len + emote_see.len
var/pick = rand(1,length)
if(pick <= emote_see.len)
visible_emote("[pick(emote_see)].")
else
audible_emote("[pick(emote_hear)].")
speak_audio()
speak_audio()
if (can_nap)
if (!resting && prob(1))
fall_asleep()
else if (resting && prob(0.5))
wake_up()
//Atmos
@@ -346,16 +362,17 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
/mob/living/simple_animal/attack_hand(mob/living/carbon/human/M as mob)
..()
switch(M.a_intent)
if(I_HELP)
if (health > 0)
M.visible_message("\blue [M] [response_help] \the [src]")
poke()
if(I_DISARM)
M.visible_message("\blue [M] [response_disarm] \the [src]")
M.do_attack_animation(src)
poke(1)
//TODO: Push the mob away or something
if(I_GRAB)
@@ -377,17 +394,20 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
M.visible_message("\red [M] has grabbed [src] passively!")
M.do_attack_animation(src)
poke(1)
if(I_HURT)
apply_damage(harm_intent_damage, BRUTE, used_weapon = "Attack by [M.name]")
M.visible_message("\red [M] [response_harm] \the [src]")
M.do_attack_animation(src)
poke(1)
return
/mob/living/simple_animal/attackby(var/obj/item/O, var/mob/user)
if(istype(O, /obj/item/weapon/reagent_containers) || istype(O, /obj/item/stack/medical) || istype(O,/obj/item/weapon/gripper/))
..()
poke()
else if(meat_type && (stat == DEAD)) //if the animal has a meat, and if it is dead.
if(istype(O, /obj/item/weapon/material/knife) || istype(O, /obj/item/weapon/material/kitchen/utensil/knife ))
@@ -400,6 +420,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
if(!O.force)
visible_message("<span class='notice'>[user] gently taps [src] with \the [O].</span>")
poke()
return 0
if(O.force > resistance)
@@ -411,8 +432,10 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
purge = 3
apply_damage(damage, O.damtype, used_weapon = "[O.name]")
poke(1)
else
usr << "<span class='danger'>This weapon is ineffective, it does no damage.</span>"
poke()
visible_message("<span class='danger'>\The [src] has been attacked with the [O] by [user].</span>")
user.do_attack_animation(src)
@@ -437,6 +460,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
if(statpanel("Status") && show_stat_health)
stat(null, "Health: [round((health / maxHealth) * 100)]%")
stat(null, "Nutrition: [nutrition]/[max_nutrition]%")
/mob/living/simple_animal/updatehealth()
..()
@@ -583,7 +607,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
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))
else if(ishuman(movement_target.loc) && Adjacent(src, get_turf(movement_target)) && prob(10))
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
@@ -609,3 +633,55 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
return
/mob/living/simple_animal/ExtinguishMob()
return
//I wanted to call this proc alert but it already exists.
//Basically makes the mob pay attention to the world, resets sleep timers, awakens it from a sleeping state sometimes
/mob/living/simple_animal/proc/poke(var/force_wake = 0)
if (stat != DEAD)
scan_interval = min_scan_interval
if (force_wake || (!client && prob(30)))
wake_up()
//Puts the mob to sleep
/mob/living/simple_animal/proc/fall_asleep()
if (stat != DEAD)
resting = 1
stat = UNCONSCIOUS
canmove = 0
wander = 0
walk_to(src,0)
movement_target = null
foodtarget = 0
update_icons()
//Wakes the mob up from sleeping
/mob/living/simple_animal/proc/wake_up()
if (stat != DEAD)
stat = CONSCIOUS
resting = 0
canmove = 1
wander = 1
update_icons()
/mob/living/simple_animal/update_icons()
if (stat == DEAD)
icon_state = icon_dead
else if ((stat == UNCONSCIOUS || resting) && icon_rest)
icon_state = icon_rest
else if (icon_living)
icon_state = icon_living
/mob/living/simple_animal/lay_down()
set name = "Rest"
set category = "Abilities"
if (resting)
wake_up()
else
fall_asleep()
src << span("notice","You are now [resting ? "resting" : "getting up"]")
update_icons()
+3 -3
View File
@@ -9,17 +9,17 @@
var/uses = 1
var/temp = null
var/list/monster = list(/mob/living/simple_animal/familiar/pet/cat,
/mob/living/simple_animal/familiar/pet/mouse,
/mob/living/simple_animal/mouse/familiar,
/mob/living/simple_animal/familiar/carcinus,
/mob/living/simple_animal/familiar/horror,
/mob/living/simple_animal/familiar/goat,
/mob/living/simple_animal/familiar/pike
)
var/list/monster_info = list( "It is well known that the blackest of cats make good familiars.",
"Mice are full of mischief and magic. A simple animal, yes, but one of the wizard's finest.",
"Mice are small but fragile creatures. This one is gifted with unending life, and the ability to renew others.",
"A mortal decendant of the original Carcinus, it is said their shells are near impenetrable and their claws as sharp as knives.",
"A creature from other plane, its very own presence is enough to shatter the sanity of men.",
"A magical goat known for its healing powers and nearly-pacifist ways.",
"A stubborn and mischievous creature, this goat delights in stirring trouble.",
"The more carnivorous and knowledge hungry cousin of the space carp. Keep away from books."
)