From 617e62b66f716cf0c6824988d9a84eecb6f61570 Mon Sep 17 00:00:00 2001 From: dearmochi Date: Wed, 10 Feb 2021 23:41:41 +0100 Subject: [PATCH] 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 --- code/__HELPERS/heap.dm | 12 +- code/defines/procs/AStar.dm | 48 +-- code/game/atoms.dm | 7 + code/game/objects/items.dm | 3 +- .../mob/living/simple_animal/parrot.dm | 288 ++++++++++-------- 5 files changed, 208 insertions(+), 150 deletions(-) diff --git a/code/__HELPERS/heap.dm b/code/__HELPERS/heap.dm index b878969571e..50a11e71abb 100644 --- a/code/__HELPERS/heap.dm +++ b/code/__HELPERS/heap.dm @@ -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 diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index 1d6fa5619e2..e2212ed8fd3 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -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 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 74fc4b12458..67675b24449 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -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. diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 5052874f5cc..138d156040a 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -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() diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index e2eee66e4aa..73a08ef2bfd 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -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 = {""} @@ -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, "This object won't fit.") 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]!", "You grab the [held_item]!", "You hear the sounds of wings flapping furiously.") + try_grab_item(parrot_interest) + visible_message("[src] grabs [held_item]!", "You grab [held_item]!", "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, "You are already holding the [held_item]") + to_chat(src, "You are already holding [held_item]") 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]!", "You grab the [held_item]!", "You hear the sounds of wings flapping furiously.") + try_grab_item(I) + visible_message("[src] grabs [held_item]!", "You grab [held_item]!", "You hear the sounds of wings flapping furiously.") return held_item to_chat(src, "There is nothing of interest to take.") @@ -597,23 +614,21 @@ return -1 if(held_item) - to_chat(src, "You are already holding the [held_item]") + to_chat(src, "You are already holding [held_item]") 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!", "You snag the [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.") + if(stolen_item && C.unEquip(stolen_item)) + try_grab_item(stolen_item) + visible_message("[src] grabs [held_item] out of [C]'s hand!", "You snag [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.") return held_item to_chat(src, "There is nothing of interest to take.") @@ -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, "There is no perch nearby to sit on.") 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