Fix and tweak parrot behaviour (#13652)

* Fix and tweak parrot behaviour

Parrot stolen item now renders under the parrot's icon to make it more clear when it holds something - remember to harm the parrot to make it drop the item
Parrot adjacency checks now take windows into account - this fixes issues where the parrot would teleport behind a window to perch/steal an item
Parrots no longer try to perch/steal an item in a turf that is inaccessible to them infinitely, causing them to be stuck
Add emotes when parrot drops item
Change default parrot name to not be improper
Clean up code where possible
Correct grammar errors in parrot messages
Fix attacking parrots in harm mode not triggering specific behaviour

* Code cleanup, CanAStarPassTo atom proc

* Fix Travis

* Address farie

* Address farie 2

* fix atkverb confusion

* use LAZYINITLIST

* remove some out of scope defaults
This commit is contained in:
dearmochi
2021-02-10 22:41:41 +00:00
committed by GitHub
parent c4b1c724aa
commit 617e62b66f
5 changed files with 208 additions and 150 deletions
+167 -121
View File
@@ -14,45 +14,45 @@
//Only a maximum of one action and one intent should be active at any given time.
//Actions
#define PARROT_PERCH 1 //Sitting/sleeping, not moving
#define PARROT_SWOOP 2 //Moving towards or away from a target
#define PARROT_WANDER 4 //Moving without a specific target in mind
#define PARROT_PERCH (1<<0) //Sitting/sleeping, not moving
#define PARROT_SWOOP (1<<1) //Moving towards or away from a target
#define PARROT_WANDER (1<<2) //Moving without a specific target in mind
//Intents
#define PARROT_STEAL 8 //Flying towards a target to steal it/from it
#define PARROT_ATTACK 16 //Flying towards a target to attack it
#define PARROT_RETURN 32 //Flying towards its perch
#define PARROT_FLEE 64 //Flying away from its attacker
#define PARROT_STEAL (1<<3) //Flying towards a target to steal it/from it
#define PARROT_ATTACK (1<<4) //Flying towards a target to attack it
#define PARROT_RETURN (1<<5) //Flying towards its perch
#define PARROT_FLEE (1<<6) //Flying away from its attacker
/mob/living/simple_animal/parrot
name = "\improper Parrot"
desc = "The parrot squaks, \"It's a Parrot! BAWWK!\""
name = "parrot"
desc = "The parrot squaks, \"It's a parrot! BAWWK!\""
icon = 'icons/mob/animal.dmi'
icon_state = "parrot_fly"
icon_living = "parrot_fly"
icon_dead = "parrot_dead"
pass_flags = PASSTABLE
can_collar = 1
can_collar = TRUE
var/list/clean_speak = list(
"Hi",
"Hello!",
"Cracker?",
"BAWWWWK george mellons griffing me")
speak_emote = list("squawks","says","yells")
emote_hear = list("squawks","bawks")
"BAWWWWK george mellons griffing me"
)
speak_emote = list("squawks", "says", "yells")
emote_hear = list("squawks", "bawks")
emote_see = list("flutters its wings")
speak_chance = 1//1% (1 in 100) chance every tick; So about once per 150 seconds, assuming an average tick is 1.5s
turns_per_move = 5
butcher_results = list(/obj/item/reagent_containers/food/snacks/cracker = 3)
response_help = "pets the"
response_disarm = "gently moves aside the"
response_harm = "swats the"
stop_automated_movement = 1
universal_speak = 1
response_help = "pets"
response_disarm = "gently moves aside"
response_harm = "swats"
stop_automated_movement = TRUE
universal_speak = TRUE
mob_size = MOB_SIZE_SMALL
var/parrot_state = PARROT_WANDER //Hunt for a perch when created
@@ -63,8 +63,8 @@
var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower.
var/parrot_been_shot = 0 //Parrots get a speed bonus after being shot. This will deincrement every process_ai() and at 0 the parrot will return to regular speed.
var/list/speech_buffer = list()
var/list/available_channels = list()
var/list/speech_buffer
var/list/available_channels
//Headset for Poly to yell at engineers :)
var/obj/item/radio/headset/ears = null
@@ -78,7 +78,7 @@
var/obj/parrot_perch = null
var/obj/desired_perches = list(/obj/structure/computerframe, /obj/structure/displaycase, \
/obj/structure/filingcabinet, /obj/machinery/teleport, \
/obj/machinery/suit_storage_unit, /obj/machinery/clonepod, \
/obj/machinery/suit_storage_unit, /obj/machinery/clonepod, \
/obj/machinery/dna_scannernew, /obj/machinery/tcomms, \
/obj/machinery/nuclearbomb, /obj/machinery/particle_accelerator, \
/obj/machinery/recharge_station, /obj/machinery/smartfridge, \
@@ -89,9 +89,10 @@
flying = TRUE
gold_core_spawnable = FRIENDLY_SPAWN
/mob/living/simple_animal/parrot/New()
..()
speech_buffer = list()
available_channels = list()
GLOB.hear_radio_list += src
if(!ears)
var/headset = pick(/obj/item/radio/headset/headset_sec, \
@@ -116,9 +117,9 @@
/mob/living/simple_animal/parrot/death(gibbed)
if(can_die())
if(held_item)
held_item.loc = src.loc
held_item = null
walk(src,0)
custom_emote(EMOTE_VISUAL, "lets go of [held_item]!")
drop_held_item()
walk(src, 0)
return ..()
/mob/living/simple_animal/parrot/Stat()
@@ -128,7 +129,7 @@
/*
* Inventory
*/
/mob/living/simple_animal/parrot/show_inv(mob/user as mob)
/mob/living/simple_animal/parrot/show_inv(mob/user)
user.set_machine(src)
var/dat = {"<table>"}
@@ -147,9 +148,8 @@
popup.open()
/mob/living/simple_animal/parrot/Topic(href, href_list)
//Can the usr physically do this?
if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr))
if(!usr.canmove || usr.stat || usr.restrained() || !usr.Adjacent(src))
return
//Is the usr's mob type able to do this?
@@ -185,7 +185,7 @@
if(!item_to_add)
return
if( !istype(item_to_add, /obj/item/radio/headset) )
if(!istype(item_to_add, /obj/item/radio/headset))
to_chat(usr, "<span class='warning'>This object won't fit.</span>")
return
@@ -221,16 +221,16 @@
else
..()
/*
* Attack responces
*/
//Humans, monkeys, aliens
/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M as mob)
/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M)
..()
if(client) return
if(!stat && M.a_intent == "hurt")
if(client)
return
if(!stat && M.a_intent == "harm")
icon_state = "parrot_fly" //It is going to be flying regardless of whether it flees or attacks
if(parrot_state == PARROT_PERCH)
@@ -242,12 +242,15 @@
if(M.health < 50) //Weakened mob? Fight back!
parrot_state |= PARROT_ATTACK
else
if(held_item)
custom_emote(EMOTE_VISUAL, "lets go of [held_item]!")
parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell!
drop_held_item(0)
drop_held_item(FALSE)
return
//Mobs with objects
/mob/living/simple_animal/parrot/attackby(var/obj/item/O as obj, var/mob/user as mob, params)
/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/user, params)
..()
if(!stat && !client && !istype(O, /obj/item/stack/medical))
if(O.force)
@@ -255,13 +258,13 @@
parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
parrot_interest = user
parrot_state = PARROT_SWOOP | PARROT_FLEE
parrot_state = PARROT_SWOOP|PARROT_FLEE
icon_state = "parrot_fly"
drop_held_item(0)
drop_held_item(FALSE)
return
//Bullets
/mob/living/simple_animal/parrot/bullet_act(var/obj/item/projectile/Proj)
/mob/living/simple_animal/parrot/bullet_act(obj/item/projectile/P)
..()
if(!stat && !client)
if(parrot_state == PARROT_PERCH)
@@ -271,10 +274,9 @@
parrot_state = PARROT_WANDER //OWFUCK, Been shot! RUN LIKE HELL!
parrot_been_shot += 5
icon_state = "parrot_fly"
drop_held_item(0)
drop_held_item(FALSE)
return
/*
* AI - Not really intelligent, but I'm calling it AI anyway.
*/
@@ -302,10 +304,9 @@
parrot_state = PARROT_WANDER
return
if(!isturf(src.loc) || !canmove || buckled)
if(!isturf(loc) || !canmove || buckled)
return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove)
//-----SPEECH
/* Parrot speech mimickry!
Phrases that the parrot hears in mob/living/say() get added to speach_buffer.
@@ -318,12 +319,11 @@
clean_speak += pick(speech_buffer)
speech_buffer.Cut()
//-----SLEEPING
if(parrot_state == PARROT_PERCH)
if(parrot_perch && parrot_perch.loc != src.loc) //Make sure someone hasnt moved our perch on us
if(parrot_perch && parrot_perch.loc != loc) //Make sure someone hasnt moved our perch on us
if(parrot_perch in view(src))
parrot_state = PARROT_SWOOP | PARROT_RETURN
parrot_state = PARROT_SWOOP|PARROT_RETURN
icon_state = "parrot_fly"
return
else
@@ -333,7 +333,6 @@
if(--parrot_sleep_dur) //Zzz
return
else
//This way we only call the stuff below once every [sleep_max] ticks.
parrot_sleep_dur = parrot_sleep_max
@@ -344,8 +343,8 @@
//Search for item to steal
parrot_interest = search_for_item()
if(parrot_interest)
custom_emote(1,"looks in [parrot_interest]'s direction and takes flight.")
parrot_state = PARROT_SWOOP | PARROT_STEAL
custom_emote(EMOTE_VISUAL, "looks in [parrot_interest]'s direction and takes flight.")
parrot_state = PARROT_SWOOP|PARROT_STEAL
icon_state = "parrot_fly"
return
@@ -366,88 +365,98 @@
if(AM)
if(istype(AM, /obj/item) || isliving(AM)) //If stealable item
parrot_interest = AM
custom_emote(1,"turns and flies towards [parrot_interest]")
parrot_state = PARROT_SWOOP | PARROT_STEAL
parrot_state = PARROT_SWOOP|PARROT_STEAL
face_atom(AM)
custom_emote(EMOTE_VISUAL, "turns and flies towards [parrot_interest].")
return
else //Else it's a perch
parrot_perch = AM
parrot_state = PARROT_SWOOP | PARROT_RETURN
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
return
if(parrot_interest && (parrot_interest in view(src)))
parrot_state = PARROT_SWOOP | PARROT_STEAL
parrot_state = PARROT_SWOOP|PARROT_STEAL
return
if(parrot_perch && (parrot_perch in view(src)))
parrot_state = PARROT_SWOOP | PARROT_RETURN
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
else //Have an item but no perch? Find one!
parrot_perch = search_for_perch()
if(parrot_perch)
parrot_state = PARROT_SWOOP | PARROT_RETURN
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
//-----STEALING
else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL))
walk(src,0)
if(!parrot_interest || held_item)
parrot_state = PARROT_SWOOP | PARROT_RETURN
else if(parrot_state == (PARROT_SWOOP|PARROT_STEAL))
walk(src, 0)
if(!parrot_interest || held_item || !(parrot_interest in view(src)))
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
if(!(parrot_interest in view(src)))
parrot_state = PARROT_SWOOP | PARROT_RETURN
return
if(in_range(src, parrot_interest))
if(Adjacent(parrot_interest))
if(isliving(parrot_interest))
steal_from_mob()
else //This should ensure that we only grab the item we want, and make sure it's not already collected on our perch
if(!parrot_perch || parrot_interest.loc != parrot_perch.loc)
held_item = parrot_interest
parrot_interest.loc = src
visible_message("[src] grabs the [held_item]!", "<span class='notice'>You grab the [held_item]!</span>", "You hear the sounds of wings flapping furiously.")
try_grab_item(parrot_interest)
visible_message("<span class='notice'>[src] grabs [held_item]!</span>", "<span class='notice'>You grab [held_item]!</span>", "You hear the sounds of wings flapping furiously.")
parrot_interest = null
parrot_state = PARROT_SWOOP | PARROT_RETURN
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
walk_to(src, parrot_interest, 1, parrot_speed)
var/list/path_to_take = get_path_to(src, get_turf(parrot_interest), /turf/proc/Distance_cardinal)
if(length(path_to_take) <= 1) // The target is below us
parrot_interest = null
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
walk_to(src, path_to_take[2], 0, parrot_speed)
return
//-----RETURNING TO PERCH
else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN))
else if(parrot_state == (PARROT_SWOOP|PARROT_RETURN))
walk(src, 0)
if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isnt inside of something else.
parrot_perch = null
parrot_state = PARROT_WANDER
return
if(in_range(src, parrot_perch))
src.loc = parrot_perch.loc
if(Adjacent(parrot_perch))
forceMove(parrot_perch.loc)
drop_held_item()
parrot_state = PARROT_PERCH
icon_state = "parrot_sit"
return
walk_to(src, parrot_perch, 1, parrot_speed)
var/list/path_to_take = get_path_to(src, get_turf(parrot_perch), /turf/proc/Distance_cardinal)
if(length(path_to_take) <= 1) // The target is below us
parrot_perch = null
parrot_state = PARROT_WANDER
return
walk_to(src, path_to_take[2], 0, parrot_speed)
return
//-----FLEEING
else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE))
walk(src,0)
if(!parrot_interest || !isliving(parrot_interest)) //Sanity
parrot_state = PARROT_WANDER
else if(parrot_state == (PARROT_SWOOP|PARROT_FLEE))
walk(src, 0)
walk_away(src, parrot_interest, 1, parrot_speed-parrot_been_shot)
if(!parrot_interest || !isliving(parrot_interest) || !Adjacent(parrot_interest)) //Sanity
parrot_state = PARROT_WANDER
parrot_interest = null
return
walk_away(src, parrot_interest, 0, parrot_speed - parrot_been_shot)
parrot_been_shot--
return
//-----ATTACKING
else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK))
else if(parrot_state == (PARROT_SWOOP|PARROT_ATTACK))
//If we're attacking a nothing, an object, a turf or a ghost for some stupid reason, switch to wander
if(!parrot_interest || !isliving(parrot_interest))
parrot_interest = null
@@ -457,8 +466,7 @@
var/mob/living/L = parrot_interest
//If the mob is close enough to interact with
if(in_range(src, parrot_interest))
if(Adjacent(parrot_interest))
//If the mob we've been chasing/attacking dies or falls into crit, check for loot!
if(L.stat)
parrot_interest = null
@@ -466,34 +474,34 @@
held_item = steal_from_ground()
if(!held_item)
held_item = steal_from_mob() //Apparently it's possible for dead mobs to hang onto items in certain circumstances.
update_held_icon()
if(parrot_perch in view(src)) //If we have a home nearby, go to it, otherwise find a new home
parrot_state = PARROT_SWOOP | PARROT_RETURN
parrot_state = PARROT_SWOOP|PARROT_RETURN
else
parrot_state = PARROT_WANDER
return
//Time for the hurt to begin!
var/damage = rand(5,10)
var/damage = rand(5, 10)
if(ishuman(parrot_interest))
var/mob/living/carbon/human/H = parrot_interest
var/obj/item/organ/external/affecting = H.get_organ(ran_zone(pick(parrot_dam_zone)))
H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp = 1)
custom_emote(1, pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons."))
H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp = TRUE)
custom_emote(EMOTE_VISUAL, pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons."))
else
L.adjustBruteLoss(damage)
custom_emote(1, pick("pecks at [L].", "claws [L]."))
custom_emote(EMOTE_VISUAL, pick("pecks at [L].", "claws [L]."))
return
//Otherwise, fly towards the mob!
else
// No AStar here because the parrot is pissed and isn't thinking rationally.
walk_to(src, parrot_interest, 1, parrot_speed)
return
//-----STATE MISHAP
else //This should not happen. If it does lets reset everything and try again
walk(src,0)
walk(src, 0)
parrot_interest = null
parrot_perch = null
drop_held_item()
@@ -516,6 +524,10 @@
if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src)
continue
// Can we find a path to it?
if(loc != AM.loc && !length(get_path_to(src, get_turf(AM), /turf/proc/Distance_cardinal)))
continue
if(istype(AM, /obj/item))
var/obj/item/I = AM
if(I.w_class < WEIGHT_CLASS_SMALL)
@@ -529,6 +541,10 @@
/mob/living/simple_animal/parrot/proc/search_for_perch()
for(var/obj/O in view(src))
// Can we find a path to it?
if(loc != O.loc && !length(get_path_to(src, get_turf(O), /turf/proc/Distance_cardinal)))
continue
for(var/path in desired_perches)
if(istype(O, path))
return O
@@ -537,6 +553,10 @@
//This proc was made to save on doing two 'in view' loops seperatly
/mob/living/simple_animal/parrot/proc/search_for_perch_and_item()
for(var/atom/movable/AM in view(src))
// Can we find a path to it?
if(loc != AM.loc && !length(get_path_to(src, get_turf(AM), /turf/proc/Distance_cardinal)))
continue
for(var/perch_path in desired_perches)
if(istype(AM, perch_path))
return AM
@@ -556,7 +576,6 @@
return C
return null
/*
* Verbs - These are actually procs, but can be used as verbs by player-controlled parrots.
*/
@@ -569,20 +588,18 @@
return -1
if(held_item)
to_chat(src, "<span class='warning'>You are already holding the [held_item]</span>")
to_chat(src, "<span class='warning'>You are already holding [held_item]</span>")
return 1
for(var/obj/item/I in view(1,src))
for(var/obj/item/I in view(1, src))
//Make sure we're not already holding it and it's small enough
if(I.loc != src && I.w_class <= WEIGHT_CLASS_SMALL)
//If we have a perch and the item is sitting on it, continue
if(!client && parrot_perch && I.loc == parrot_perch.loc)
continue
held_item = I
I.loc = src
visible_message("[src] grabs the [held_item]!", "<span class='notice'>You grab the [held_item]!</span>", "You hear the sounds of wings flapping furiously.")
try_grab_item(I)
visible_message("<span class='notice'>[src] grabs [held_item]!</span>", "<span class='notice'>You grab [held_item]!</span>", "You hear the sounds of wings flapping furiously.")
return held_item
to_chat(src, "<span class = 'warning'>There is nothing of interest to take.</span>")
@@ -597,23 +614,21 @@
return -1
if(held_item)
to_chat(src, "<span class='warning'>You are already holding the [held_item]</span>")
to_chat(src, "<span class='warning'>You are already holding [held_item]</span>")
return 1
var/obj/item/stolen_item = null
for(var/mob/living/carbon/C in view(1,src))
for(var/mob/living/carbon/C in view(1, src))
if(C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL)
stolen_item = C.l_hand
if(C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL)
stolen_item = C.r_hand
if(stolen_item)
C.unEquip(stolen_item)
held_item = stolen_item
stolen_item.loc = src
visible_message("[src] grabs the [held_item] out of [C]'s hand!", "<span class='notice'>You snag the [held_item] out of [C]'s hand!</span>", "You hear the sounds of wings flapping furiously.")
if(stolen_item && C.unEquip(stolen_item))
try_grab_item(stolen_item)
visible_message("<span class='notice'>[src] grabs [held_item] out of [C]'s hand!</span>", "<span class='notice'>You snag [held_item] out of [C]'s hand!</span>", "You hear the sounds of wings flapping furiously.")
return held_item
to_chat(src, "<span class='warning'>There is nothing of interest to take.</span>")
@@ -627,11 +642,10 @@
if(stat)
return
src.drop_held_item()
drop_held_item()
return
/mob/living/simple_animal/parrot/proc/drop_held_item(var/drop_gently = 1)
/mob/living/simple_animal/parrot/proc/drop_held_item(drop_gently = TRUE)
set name = "Drop held item"
set category = "Parrot"
set desc = "Drop the item you're holding."
@@ -646,16 +660,18 @@
if(!drop_gently)
if(istype(held_item, /obj/item/grenade))
var/obj/item/grenade/G = held_item
G.loc = src.loc
G.forceMove(loc)
G.prime()
to_chat(src, "You let go of the [held_item]!")
to_chat(src, "You let go of [held_item]!")
held_item = null
update_held_icon()
return 1
to_chat(src, "You drop the [held_item].")
to_chat(src, "You drop [held_item].")
held_item.loc = src.loc
held_item.forceMove(loc)
held_item = null
update_held_icon()
return 1
/mob/living/simple_animal/parrot/proc/perch_player()
@@ -667,15 +683,30 @@
return
if(icon_state == "parrot_fly")
for(var/atom/movable/AM in view(src,1))
for(var/atom/movable/AM in view(src, 1))
for(var/perch_path in desired_perches)
if(istype(AM, perch_path))
src.loc = AM.loc
forceMove(AM.loc)
icon_state = "parrot_sit"
return
to_chat(src, "<span class='warning'>There is no perch nearby to sit on.</span>")
return
/**
* Attempts to pick up an adjacent item
*
* Arguments:
* * I - The item to try and pick up
*/
/mob/living/simple_animal/parrot/proc/try_grab_item(obj/I)
if(!Adjacent(I))
return
if(held_item)
drop_held_item()
held_item = I
update_held_icon()
I.forceMove(src)
/*
* Sub-types
*/
@@ -706,7 +737,7 @@
available_channels = list(":e")
..()
/mob/living/simple_animal/parrot/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios)
/mob/living/simple_animal/parrot/handle_message_mode(message_mode, list/message_pieces, verb, used_radios)
if(message_mode && istype(ears))
ears.talk_into(src, message_pieces, message_mode, verb)
used_radios += ears
@@ -716,15 +747,30 @@
parrot_hear(html_decode(multilingual_to_message(message_pieces)))
..()
/mob/living/simple_animal/parrot/hear_radio(list/message_pieces, var/verb="says", var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0, var/atom/follow_target)
/mob/living/simple_animal/parrot/hear_radio(list/message_pieces, verb = "says", part_a, part_b, mob/speaker = null, hard_to_hear = 0, atom/follow_target)
if(speaker != src && prob(50))
parrot_hear(html_decode(multilingual_to_message(message_pieces)))
..()
/mob/living/simple_animal/parrot/proc/parrot_hear(var/message="")
/mob/living/simple_animal/parrot/proc/parrot_hear(message)
if(!message || stat)
return
speech_buffer.Add(message)
/mob/living/simple_animal/parrot/proc/update_held_icon()
underlays.Cut()
if(!held_item)
return
var/matrix/m180 = matrix(held_item.transform)
m180.Turn(180)
var/held_item_icon = image(held_item, pixel_y = -8)
animate(held_item_icon, transform = m180)
underlays += held_item_icon
/mob/living/simple_animal/parrot/CanAStarPassTo(ID, dir, obj/destination)
for(var/path in desired_perches)
if(istype(destination, path))
return TRUE