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
+6 -6
View File
@@ -24,7 +24,7 @@
//(i.e the max or the min dependant on the comparison function)
/datum/heap/proc/Pop()
if(!L.len)
return 0
return null
. = L[1]
L[1] = L[L.len]
@@ -36,8 +36,8 @@
/datum/heap/proc/Swim(var/index)
var/parent = round(index * 0.5)
while(parent > 0 && (call(cmp)(L[index],L[parent]) > 0))
L.Swap(index,parent)
while(parent > 0 && (call(cmp)(L[index], L[parent]) > 0))
L.Swap(index, parent)
index = parent
parent = round(index * 0.5)
@@ -45,8 +45,8 @@
/datum/heap/proc/Sink(var/index)
var/g_child = GetGreaterChild(index)
while(g_child > 0 && (call(cmp)(L[index],L[g_child]) < 0))
L.Swap(index,g_child)
while(g_child > 0 && (call(cmp)(L[index], L[g_child]) < 0))
L.Swap(index, g_child)
index = g_child
g_child = GetGreaterChild(index)
@@ -59,7 +59,7 @@
if(index * 2 + 1 > L.len)
return index * 2
if(call(cmp)(L[index * 2],L[index * 2 + 1]) < 0)
if(call(cmp)(L[index * 2], L[index * 2 + 1]) < 0)
return index * 2 + 1
else
return index * 2
+26 -22
View File
@@ -62,24 +62,23 @@ Actual Adjacent procs :
return b.f - a.f
//wrapper that returns an empty list if A* failed to find a path
/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1)
/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id = null, turf/exclude = null, simulated_only = TRUE)
var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only)
if(!path)
path = list()
return path
//the actual algorithm
/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1)
/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id = null, turf/exclude = null, simulated_only = TRUE)
//sanitation
var/start = get_turf(caller)
if(!start)
return 0
return null
if(maxnodes)
//if start turf is farther than maxnodes from end turf, no need to do anything
if(call(start, dist)(end) > maxnodes)
return 0
return null
maxnodedepth = maxnodes //no need to consider path longer than maxnodes
var/datum/heap/open = new /datum/heap(/proc/HeapPathWeightCompare) //the open list
@@ -92,7 +91,6 @@ Actual Adjacent procs :
//then run the main loop
while(!open.IsEmpty() && !path)
{
//get the lower f node on the open list
cur = open.Pop() //get the lower f turf in the open list
closed.Add(cur.source) //and tell we've processed it
@@ -100,7 +98,7 @@ Actual Adjacent procs :
//if we only want to get near the target, check if we're close enough
var/closeenough
if(mintargetdist)
closeenough = call(cur.source,dist)(end) <= mintargetdist
closeenough = call(cur.source, dist)(end) <= mintargetdist
//if too many steps, abandon that path
if(maxnodedepth && (cur.nt > maxnodedepth))
@@ -118,12 +116,13 @@ Actual Adjacent procs :
break
//get adjacents turfs using the adjacent proc, checking for access with id
var/list/L = call(cur.source,adjacent)(caller, id, simulated_only)
for(var/turf/T in L)
var/list/L = call(cur.source, adjacent)(caller, id, simulated_only)
for(var/t in L)
var/turf/T = t
if(T == exclude || (T in closed))
continue
var/newg = cur.g + call(cur.source,dist)(T)
var/newg = cur.g + call(cur.source, dist)(T)
if(!T.PNode) //is not already in open list, so add it
open.Insert(new /datum/pathnode(T,cur,newg,call(T,dist)(end),cur.nt+1))
else //is already in open list, check if it's a better way from the current turf
@@ -134,18 +133,17 @@ Actual Adjacent procs :
T.PNode.nt = cur.nt + 1
open.ReSort(T.PNode)//reorder the changed element in the list
}
//cleaning after us
for(var/datum/pathnode/PN in open.L)
PN.source.PNode = null
for(var/turf/T in closed)
for(var/t in closed)
var/turf/T = t
T.PNode = null
//reverse the path to get it from start to finish
if(path)
for(var/i = 1; i <= path.len/2; i++)
path.Swap(i,path.len-i+1)
for(var/i in 1 to path.len / 2)
path.Swap(i, path.len - i + 1)
return path
@@ -156,10 +154,10 @@ Actual Adjacent procs :
var/turf/simulated/T
for(var/dir in GLOB.cardinal)
T = get_step(src,dir)
T = get_step(src, dir)
if(!T || (simulated_only && !istype(T)))
continue
if(!T.density && !LinkBlockedWithAccess(T,caller, ID))
if(!T.density && !LinkBlockedWithAccess(T, caller, ID))
L.Add(T)
return L
@@ -170,15 +168,21 @@ Actual Adjacent procs :
/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID)
var/adir = get_dir(src, T)
var/rdir = get_dir(T, src)
var/atom/caller_atom = caller
if(!istype(caller_atom))
caller_atom = null
for(var/obj/structure/window/W in src)
if(!W.CanAStarPass(ID, adir))
return 1
return TRUE
for(var/obj/machinery/door/window/W in src)
if(!W.CanAStarPass(ID, adir))
return 1
return TRUE
for(var/obj/O in T)
if(!O.CanAStarPass(ID, rdir, caller))
return 1
var/pass_through = FALSE
if(caller_atom)
pass_through = caller_atom.CanAStarPassTo(ID, adir, O)
if(!O.CanAStarPass(ID, rdir, caller) && !pass_through)
return TRUE
return 0
return FALSE
+7
View File
@@ -1066,6 +1066,13 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons)
color = C
return
/*
Checks whether this atom can traverse the destination object when used as source for AStar.
This should only be used as an override to /obj/proc/CanAStarPass. Aka don't use this unless you can't change the object's proc.
Returning TRUE here will override the above proc's result.
*/
/atom/proc/CanAStarPassTo(ID, dir, obj/destination)
return TRUE
/** Call this when you want to present a renaming prompt to the user.
+2 -1
View File
@@ -21,9 +21,9 @@ GLOBAL_DATUM_INIT(fire_overlay, /image, image("icon" = 'icons/goonstation/effect
can_be_hit = FALSE
suicidal_hands = TRUE
var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
var/hitsound = null
var/usesound = null
var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
var/throwhitsound
var/w_class = WEIGHT_CLASS_NORMAL
var/slot_flags = 0 //This is used to determine on which slots an item can fit.
@@ -115,6 +115,7 @@ GLOBAL_DATUM_INIT(fire_overlay, /image, image("icon" = 'icons/goonstation/effect
hitsound = 'sound/items/welder.ogg'
if(damtype == "brute")
hitsound = "swing_hit"
LAZYINITLIST(attack_verb)
if(!move_resist)
determine_move_resist()
+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