From a5478a5360b2cf8c232831b9c3380b2f1df580e2 Mon Sep 17 00:00:00 2001 From: NanakoAC Date: Mon, 12 Sep 2016 07:06:40 +0100 Subject: [PATCH] Animal Tweaks: Second attempt (#849) * Merge branch 'AnimalTweaks_Fix4' into AnimalTweaks_Fix5 # Conflicts: # code/modules/mob/living/living.dm * REMOVED VENT THINGS FROM CHANGELOG * switch statement for helper --- code/_onclick/cyborg.dm | 2 + code/datums/ai_law_sets.dm | 4 +- code/modules/assembly/mousetrap.dm | 158 +++++++++--------- code/modules/mob/holder.dm | 52 ++++-- .../living/silicon/robot/drone/drone_items.dm | 25 +-- .../mob/living/simple_animal/friendly/cat.dm | 73 +++++--- .../living/simple_animal/friendly/corgi.dm | 91 ++-------- .../mob/living/simple_animal/friendly/crab.dm | 3 +- .../simple_animal/friendly/farm_animals.dm | 11 +- .../mob/living/simple_animal/friendly/fox.dm | 2 +- .../living/simple_animal/friendly/lizard.dm | 6 + .../living/simple_animal/friendly/mouse.dm | 41 +++-- .../mob/living/simple_animal/simple_animal.dm | 123 +++++++++++++- code/modules/mob/mob.dm | 5 + code/modules/mob/mob_helpers.dm | 52 ++++++ code/modules/paperwork/faxmachine.dm | 2 +- .../reagents/dispenser/dispenser_presets.dm | 2 + .../reagent_containers/food/snacks.dm | 6 + html/changelogs/Nanako-Animals.yml | 46 +++++ icons/mob/mouse.dmi | Bin 2930 -> 3003 bytes 20 files changed, 472 insertions(+), 232 deletions(-) create mode 100644 html/changelogs/Nanako-Animals.yml 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 04d57873e28dac5ffab9aefd2341d6a59f935612..ee4a5648f7fa3d82f41b3ae978ec4050f1ecc05e 100644 GIT binary patch delta 2940 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 ahDAoz*Rv+`{U?7aow+H@q}Is&(Z2xPimv7W delta 2848 zcmZ9Oc{mh`8pdbF8pRxIlJy+SL5!-}im*@A=-ZO?X!*LrJb8#j7XDOhQ8VW6on8 z9i6GFmq9^6AP@+NM2?S-#{d8^dVsD((>dU<)-*w{EaI-*dhLQ5x4QTgED;K0DZXA1fr9v-f)uDQ9netv!k z1j5?d`boQ#YDGl_lgVssY<&OzeQRrLWMm``hwJX{uCK2j8ygFZ9}ETn0Kgz~gatrv zW$ON~;&T=lD2N&5>Qx3O5Qi)h_Aj`cTn&FBwARyUknH9ITH`ft+}rZu?=k7AZkkhG z=-$l8womc0tv|M!5I$Q)PcN88x0Slx=^YBYx(R(!d~U7RMJz-aXFLPD_&MYg&b~$p zZwPS}ex8Y4yr>XJP*ktq9oT`+&sLRo7*(9_%i{=ZY{T%TXr=%ZJN5QOU&pWT7^TQ@+NA`gcO}w+etEGlbs;PfY+lGxEjy(fz*UO-On{`NqIiGjg*3kzxmz6)bz4r8istxT?T&c;aJr<1+478x7dc zh-w3kfaU;GA@shTOHpz>1X1*g{j}{RePT4)*6^!&Kydx(L365 zPFFUNc*(;PdEGDBO`+CiTJ>Cu?-?oMxj!O8F0HjZAW5ctTCD)W5#F-IXcnNTcEAB< zc)C9w>Bvj3oSv{{IEfz}{ND;vcJE9tv@vJ8jj!aT(y;p?zS=)A%$4 zqba^dP#+PZ$oHn6qNGl_?DCRG*TgRoO>awR^Ue*`FPmUGW{+ZdRE&(K(e`=d&BN;S za=GxMrrxr~-b2jJfM6&(DARpBzg4y=Jhah)DKfP-r)x&(C|8ZoPsg74#IXda@ zsEouQ^wn#1LRR5u-goOAZTI?`XzUjZ6rQXMxV1C+`Dsq6B`=z~bnNg{*sYxH_DsV? z=MlZa6m>@ysvgJmn>VC(pRF(|3NubO3=IzjXWTGN6>n#;K#Lb#@_e3oIn#Z1l!%;t zSuxj+PVUlegQSp1y@x+vkmA;9AdRf?9LC2(Q3a%_j&9&;`sg??my5Gss?H)ce@tc<3-;#erTVit-<#1NZ%ao-UKh_$9%>|L zDEQYzpvT!F9-ZmF&`3jjJwuMzEOr3QH3ap#zAdolggm3ne|UqMF!D%x!OeX3+3T&p|S{hmu zX8$f?^ne$yLGq^3N*-o*_9;Gv3DqeI88p7&u~RTx43ZUN{i7qsTiU4@RaqIk(dU-^ z<kh9=x=iF{UYb>uPk&+6X-Ynu#Imvv1kf&^_Qsg8*XTCIqchz zpV0O+gO+SFhV-+L-TH(O1`PRIe6d-)x{ z>hKI>Copmc^0pL;}xx)<&(?ILx~| z>v=|4&Ylj`p6bpVOVpKNC6$9~`NX3*wXoYUgH+ylTvXUDW^*@?@Q=`wGI0DrJ4$U^ zfR%|(7PkGsACTi#k{4h>p4)c0Biv99652L$dY_mQ%DpH(FJmkqPB^`^RKH^q#X)-F zBw9anZ3bmgCj2)P*62b1?freBeFu2*Le3nV>Y?fJdo4p{lqJNH!sefruz{&&sZzHo z&bwkoISt&{Rj-o%o}UcE*%XqGVRLWbqPPa~agy_ZmY=05ZtV zvO-}8&VtJh!Y>YxS!TOmOw@L{xC($V