"
- dat += "
Headset: [ears]" : "add_inv=ears'>Nothing"]"
-
- user << browse(dat, "window=mob[REF(src)];size=325x500")
- onclose(user, "window=mob[REF(src)]")
-
-
-/mob/living/simple_animal/parrot/Topic(href, href_list)
- if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- usr << browse(null, "window=mob[REF(src)]")
- usr.unset_machine()
- return
-
- //Removing from inventory
- if(href_list["remove_inv"])
- var/remove_from = href_list["remove_inv"]
- switch(remove_from)
- if("ears")
- if(!ears)
- to_chat(usr, "There is nothing to remove from its [remove_from]!")
- return
- if(!stat)
- say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
- ears.forceMove(drop_location())
- ears = null
- for(var/possible_phrase in speak)
- if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys)
- possible_phrase = copytext(possible_phrase,3)
-
- //Adding things to inventory
- else if(href_list["add_inv"])
- var/add_to = href_list["add_inv"]
- if(!usr.get_active_held_item())
- to_chat(usr, "You have nothing in your hand to put on its [add_to]!")
- return
- switch(add_to)
- if("ears")
- if(ears)
- to_chat(usr, "It's already wearing something!")
- return
- else
- var/obj/item/item_to_add = usr.get_active_held_item()
- if(!item_to_add)
- return
-
- if( !istype(item_to_add, /obj/item/radio/headset) )
- to_chat(usr, "This object won't fit!")
- return
-
- var/obj/item/radio/headset/headset_to_add = item_to_add
-
- if(!usr.transferItemToLoc(headset_to_add, src))
- return
- ears = headset_to_add
- to_chat(usr, "You fit the headset onto [src].")
-
- clearlist(available_channels)
- for(var/ch in headset_to_add.channels)
- switch(ch)
- if(RADIO_CHANNEL_ENGINEERING)
- available_channels.Add(RADIO_TOKEN_ENGINEERING)
- if(RADIO_CHANNEL_COMMAND)
- available_channels.Add(RADIO_TOKEN_COMMAND)
- if(RADIO_CHANNEL_SECURITY)
- available_channels.Add(RADIO_TOKEN_SECURITY)
- if(RADIO_CHANNEL_SCIENCE)
- available_channels.Add(RADIO_TOKEN_SCIENCE)
- if(RADIO_CHANNEL_MEDICAL)
- available_channels.Add(RADIO_TOKEN_MEDICAL)
- if(RADIO_CHANNEL_SUPPLY)
- available_channels.Add(RADIO_TOKEN_SUPPLY)
- if(RADIO_CHANNEL_SERVICE)
- available_channels.Add(RADIO_TOKEN_SERVICE)
-
- if(headset_to_add.translate_binary)
- available_channels.Add(MODE_TOKEN_BINARY)
- else
- return ..()
-
-
-/*
- * Attack responces
- */
-//Humans, monkeys, aliens
-/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M)
- ..()
- if(client)
- return
- if(!stat && M.a_intent == INTENT_HARM)
-
- icon_state = icon_living //It is going to be flying regardless of whether it flees or attacks
-
- if(parrot_state == PARROT_PERCH)
- parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
-
- parrot_interest = M
- parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction..
-
- if(health > 30) //Let's get in there and squawk it up!
- parrot_state |= PARROT_ATTACK
- else
- parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell!
- drop_held_item(0)
- if(stat != DEAD && M.a_intent == INTENT_HELP)
- handle_automated_speech(1) //assured speak/emote
- return
-
-/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M)
- return attack_hand(M)
-
-/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/M)
- return attack_hand(M)
-
-//Simple animals
-/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M)
- . = ..() //goodbye immortal parrots
-
- if(client)
- return
-
- if(parrot_state == PARROT_PERCH)
- parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
-
- if(M.melee_damage_upper > 0 && !stat)
- parrot_interest = M
- parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless
- icon_state = icon_living
-
-//Mobs with objects
-/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params)
- if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/reagent_containers/food/snacks/cracker))
- if(O.force)
- if(parrot_state == PARROT_PERCH)
- parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
-
- parrot_interest = user
- parrot_state = PARROT_SWOOP
- if(health > 30) //Let's get in there and squawk it up!
- parrot_state |= PARROT_ATTACK
- else
- parrot_state |= PARROT_FLEE
- icon_state = icon_living
- drop_held_item(0)
- else if(istype(O, /obj/item/reagent_containers/food/snacks/cracker)) //Poly wants a cracker.
- qdel(O)
- if(health < maxHealth)
- adjustBruteLoss(-10)
- speak_chance *= 1.27 // 20 crackers to go from 1% to 100%
- speech_shuffle_rate += 10
- to_chat(user, "[src] eagerly devours the cracker.")
- return // the cracker was deleted
- return ..()
-
-//Bullets
-/mob/living/simple_animal/parrot/bullet_act(obj/item/projectile/Proj)
- ..()
- if(!stat && !client)
- if(parrot_state == PARROT_PERCH)
- parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
-
- parrot_interest = null
- parrot_state = PARROT_WANDER | PARROT_FLEE //Been shot and survived! RUN LIKE HELL!
- //parrot_been_shot += 5
- icon_state = icon_living
- drop_held_item(0)
- return
-
-
-/*
- * AI - Not really intelligent, but I'm calling it AI anyway.
- */
-/mob/living/simple_animal/parrot/Life()
- ..()
-
- //Sprite update for when a parrot gets pulled
- if(pulledby && !stat && parrot_state != PARROT_WANDER)
- if(buckled)
- buckled.unbuckle_mob(src, TRUE)
- buckled = null
- icon_state = icon_living
- parrot_state = PARROT_WANDER
- pixel_x = initial(pixel_x)
- pixel_y = initial(pixel_y)
- return
-
-
-//-----SPEECH
- /* Parrot speech mimickry!
- Phrases that the parrot Hear()s get added to speach_buffer.
- Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. */
-/mob/living/simple_animal/parrot/handle_automated_speech()
- ..()
- if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one
- if(speak.len)
- speak.Remove(pick(speak))
-
- speak.Add(pick(speech_buffer))
-
-
-/mob/living/simple_animal/parrot/handle_automated_movement()
- if(!isturf(src.loc) || !canmove || buckled)
- return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove)
-
- if(client && stat == CONSCIOUS && parrot_state != icon_living)
- icon_state = icon_living
- //Because the most appropriate place to set icon_state is movement_delay(), clearly
-
-//-----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 in view(src))
- parrot_state = PARROT_SWOOP | PARROT_RETURN
- icon_state = icon_living
- return
- else
- parrot_state = PARROT_WANDER
- icon_state = icon_living
- return
-
- 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
-
- //Cycle through message modes for the headset
- if(speak.len)
- var/list/newspeak = list()
-
- if(available_channels.len && src.ears)
- for(var/possible_phrase in speak)
-
- //50/50 chance to not use the radio at all
- var/useradio = 0
- if(prob(50))
- useradio = 1
-
- if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys))
- possible_phrase = "[useradio?pick(available_channels):""][copytext(possible_phrase,3)]" //crop out the channel prefix
- else
- possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]"
-
- newspeak.Add(possible_phrase)
-
- else //If we have no headset or channels to use, dont try to use any!
- for(var/possible_phrase in speak)
- if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys))
- possible_phrase = copytext(possible_phrase,3) //crop out the channel prefix
- newspeak.Add(possible_phrase)
- speak = newspeak
-
- //Search for item to steal
- parrot_interest = search_for_item()
- if(parrot_interest)
- emote("me", 1, "looks in [parrot_interest]'s direction and takes flight.")
- parrot_state = PARROT_SWOOP | PARROT_STEAL
- icon_state = icon_living
- return
-
-//-----WANDERING - This is basically a 'I dont know what to do yet' state
- else if(parrot_state == PARROT_WANDER)
- //Stop movement, we'll set it later
- walk(src, 0)
- parrot_interest = null
-
- //Wander around aimlessly. This will help keep the loops from searches down
- //and possibly move the mob into a new are in view of something they can use
- if(prob(90))
- step(src, pick(GLOB.cardinals))
- return
-
- if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do.
- var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item
- if(AM)
- if(istype(AM, /obj/item) || isliving(AM)) //If stealable item
- parrot_interest = AM
- emote("me", 1, "turns and flies towards [parrot_interest].")
- parrot_state = PARROT_SWOOP | PARROT_STEAL
- return
- else //Else it's a perch
- parrot_perch = AM
- parrot_state = PARROT_SWOOP | PARROT_RETURN
- return
- return
-
- if(parrot_interest && parrot_interest in view(src))
- parrot_state = PARROT_SWOOP | PARROT_STEAL
- return
-
- if(parrot_perch && parrot_perch in view(src))
- 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
- return
-//-----STEALING
- else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL))
- walk(src,0)
- if(!parrot_interest || held_item)
- parrot_state = PARROT_SWOOP | PARROT_RETURN
- return
-
- if(!(parrot_interest in view(src)))
- parrot_state = PARROT_SWOOP | PARROT_RETURN
- return
-
- 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.forceMove(src)
- 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
- return
-
- walk_to(src, parrot_interest, 1, parrot_speed)
- if(isStuck())
- return
-
- return
-
-//-----RETURNING TO PERCH
- 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(Adjacent(parrot_perch))
- forceMove(parrot_perch.loc)
- drop_held_item()
- parrot_state = PARROT_PERCH
- icon_state = icon_sit
- return
-
- walk_to(src, parrot_perch, 1, parrot_speed)
- if(isStuck())
- return
-
- return
-
-//-----FLEEING
- else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE))
- walk(src,0)
- if(!parrot_interest || !isliving(parrot_interest)) //Sanity
- parrot_state = PARROT_WANDER
-
- walk_away(src, parrot_interest, 1, parrot_speed)
- if(isStuck())
- return
-
- return
-
-//-----ATTACKING
- 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
- parrot_state = PARROT_WANDER
- return
-
- var/mob/living/L = parrot_interest
- if(melee_damage_upper == 0)
- melee_damage_upper = parrot_damage_upper
- a_intent = INTENT_HARM
-
- //If the mob is close enough to interact with
- 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
- if(!held_item)
- 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.
- 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
- else
- parrot_state = PARROT_WANDER
- return
-
- attacktext = pick("claws at", "chomps")
- L.attack_animal(src)//Time for the hurt to begin!
- //Otherwise, fly towards the mob!
- else
- walk_to(src, parrot_interest, 1, parrot_speed)
- if(isStuck())
- return
-
- return
-//-----STATE MISHAP
- else //This should not happen. If it does lets reset everything and try again
- walk(src,0)
- parrot_interest = null
- parrot_perch = null
- drop_held_item()
- parrot_state = PARROT_WANDER
- return
-
-/*
- * Procs
- */
-
-/mob/living/simple_animal/parrot/proc/isStuck()
- //Check to see if the parrot is stuck due to things like windows or doors or windowdoors
- if(parrot_lastmove)
- if(parrot_lastmove == src.loc)
- if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander.
- parrot_state = PARROT_WANDER
- parrot_stuck = 0
- parrot_lastmove = null
- return 1
- else
- parrot_lastmove = null
- else
- parrot_lastmove = src.loc
- return 0
-
-/mob/living/simple_animal/parrot/proc/search_for_item()
- var/item
- for(var/atom/movable/AM in view(src))
- //Skip items we already stole or are wearing or are too big
- if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src)
- continue
- if(istype(AM, /obj/item))
- var/obj/item/I = AM
- if(I.w_class < WEIGHT_CLASS_SMALL)
- item = I
- else if(iscarbon(AM))
- var/mob/living/carbon/C = AM
- for(var/obj/item/I in C.held_items)
- if(I.w_class <= WEIGHT_CLASS_SMALL)
- item = I
- break
- if(item)
- if(!AStar(src, get_turf(item), /turf/proc/Distance_cardinal))
- item = null
- continue
- return item
-
- return null
-
-/mob/living/simple_animal/parrot/proc/search_for_perch()
- for(var/obj/O in view(src))
- for(var/path in desired_perches)
- if(istype(O, path))
- return O
- return null
-
-//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))
- for(var/perch_path in desired_perches)
- if(istype(AM, perch_path))
- return AM
-
- //Skip items we already stole or are wearing or are too big
- if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src)
- continue
-
- if(istype(AM, /obj/item))
- var/obj/item/I = AM
- if(I.w_class <= WEIGHT_CLASS_SMALL)
- return I
-
- if(iscarbon(AM))
- var/mob/living/carbon/C = AM
- for(var/obj/item/I in C.held_items)
- if(I.w_class <= WEIGHT_CLASS_SMALL)
- return C
- return null
-
-
-/*
- * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots.
- */
-/mob/living/simple_animal/parrot/proc/steal_from_ground()
- set name = "Steal from ground"
- set category = "Parrot"
- set desc = "Grabs a nearby item."
-
- if(stat)
- return -1
-
- if(held_item)
- to_chat(src, "You are already holding [held_item]!")
- return 1
-
- 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.forceMove(src)
- 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!")
- return 0
-
-/mob/living/simple_animal/parrot/proc/steal_from_mob()
- set name = "Steal from mob"
- set category = "Parrot"
- set desc = "Steals an item right out of a person's hand!"
-
- if(stat)
- return -1
-
- if(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/obj/item/I in C.held_items)
- if(I.w_class <= WEIGHT_CLASS_SMALL)
- stolen_item = I
- break
-
- if(stolen_item)
- C.transferItemToLoc(stolen_item, src, TRUE)
- held_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!")
- return 0
-
-/mob/living/simple_animal/parrot/verb/drop_held_item_player()
- set name = "Drop held item"
- set category = "Parrot"
- set desc = "Drop the item you're holding."
-
- if(stat)
- return
-
- src.drop_held_item()
-
- return
-
-/mob/living/simple_animal/parrot/proc/drop_held_item(drop_gently = 1)
- set name = "Drop held item"
- set category = "Parrot"
- set desc = "Drop the item you're holding."
-
- if(stat)
- return -1
-
- if(!held_item)
- if(src == usr) //So that other mobs wont make this message appear when they're bludgeoning you.
- to_chat(src, "You have nothing to drop!")
- return 0
-
-
-//parrots will eat crackers instead of dropping them
- if(istype(held_item, /obj/item/reagent_containers/food/snacks/cracker) && (drop_gently))
- qdel(held_item)
- held_item = null
- if(health < maxHealth)
- adjustBruteLoss(-10)
- emote("me", 1, "[src] eagerly downs the cracker.")
- return 1
-
-
- if(!drop_gently)
- if(istype(held_item, /obj/item/grenade))
- var/obj/item/grenade/G = held_item
- G.forceMove(drop_location())
- G.prime()
- to_chat(src, "You let go of [held_item]!")
- held_item = null
- return 1
-
- to_chat(src, "You drop [held_item].")
-
- held_item.forceMove(drop_location())
- held_item = null
- return 1
-
-/mob/living/simple_animal/parrot/proc/perch_player()
- set name = "Sit"
- set category = "Parrot"
- set desc = "Sit on a nice comfy perch."
-
- if(stat || !client)
- return
-
- if(icon_state == icon_living)
- for(var/atom/movable/AM in view(src,1))
- for(var/perch_path in desired_perches)
- if(istype(AM, perch_path))
- src.forceMove(AM.loc)
- icon_state = icon_sit
- parrot_state = PARROT_PERCH
- return
- to_chat(src, "There is no perch nearby to sit on!")
- return
-
-/mob/living/simple_animal/parrot/Moved(oldLoc, dir)
- . = ..()
- if(. && !stat && client && parrot_state == PARROT_PERCH)
- parrot_state = PARROT_WANDER
- icon_state = icon_living
- pixel_x = initial(pixel_x)
- pixel_y = initial(pixel_y)
-
-/mob/living/simple_animal/parrot/proc/perch_mob_player()
- set name = "Sit on Human's Shoulder"
- set category = "Parrot"
- set desc = "Sit on a nice comfy human being!"
-
- if(stat || !client)
- return
-
- if(!buckled)
- for(var/mob/living/carbon/human/H in view(src,1))
- if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime
- continue
- perch_on_human(H)
- return
- to_chat(src, "There is nobody nearby that you can sit on!")
- else
- icon_state = icon_living
- parrot_state = PARROT_WANDER
- if(buckled)
- to_chat(src, "You are no longer sitting on [buckled]'s shoulder.")
- buckled.unbuckle_mob(src, TRUE)
- buckled = null
- pixel_x = initial(pixel_x)
- pixel_y = initial(pixel_y)
-
-
-
-/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H)
- if(!H)
- return
- forceMove(get_turf(H))
- if(H.buckle_mob(src, TRUE))
- pixel_y = 9
- pixel_x = pick(-8,8) //pick left or right shoulder
- icon_state = icon_sit
- parrot_state = PARROT_PERCH
- to_chat(src, "You sit on [H]'s shoulder.")
-
-
-/mob/living/simple_animal/parrot/proc/toggle_mode()
- set name = "Toggle mode"
- set category = "Parrot"
- set desc = "Time to bear those claws!"
-
- if(stat || !client)
- return
-
- if(a_intent != INTENT_HELP)
- melee_damage_upper = 0
- a_intent = INTENT_HELP
- else
- melee_damage_upper = parrot_damage_upper
- a_intent = INTENT_HARM
- to_chat(src, "You will now [a_intent] others.")
- return
-
-/*
- * Sub-types
- */
-/mob/living/simple_animal/parrot/Poly
- name = "Poly"
- desc = "Poly the Parrot. An expert on quantum cracker theory."
- speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE")
- gold_core_spawnable = NO_SPAWN
- speak_chance = 3
- var/memory_saved = FALSE
- var/rounds_survived = 0
- var/longest_survival = 0
- var/longest_deathstreak = 0
-
-/mob/living/simple_animal/parrot/Poly/Initialize()
- ears = new /obj/item/radio/headset/headset_eng(src)
- available_channels = list(":e")
- Read_Memory()
- if(rounds_survived == longest_survival)
- speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?")
- desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]."
- speak_chance = 20 //His hubris has made him more annoying/easier to justify killing
- add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY)
- else if(rounds_survived == longest_deathstreak)
- speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!")
- desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..."
- add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY)
- else if(rounds_survived > 0)
- speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!")
- desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!"
- else
- speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!")
-
- . = ..()
-
-/mob/living/simple_animal/parrot/Poly/Life()
- if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
- Write_Memory(FALSE)
- memory_saved = TRUE
- ..()
-
-/mob/living/simple_animal/parrot/Poly/death(gibbed)
- if(!memory_saved)
- Write_Memory(TRUE)
- if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666))
- var/mob/living/simple_animal/parrot/Poly/ghost/G = new(loc)
- if(mind)
- mind.transfer_to(G)
- else
- G.key = key
- ..(gibbed)
-
-/mob/living/simple_animal/parrot/Poly/proc/Read_Memory()
- if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new
- var/savefile/S = new /savefile("data/npc_saves/Poly.sav")
- S["phrases"] >> speech_buffer
- S["roundssurvived"] >> rounds_survived
- S["longestsurvival"] >> longest_survival
- S["longestdeathstreak"] >> longest_deathstreak
- fdel("data/npc_saves/Poly.sav")
- else
- var/json_file = file("data/npc_saves/Poly.json")
- if(!fexists(json_file))
- return
- var/list/json = json_decode(file2text(json_file))
- speech_buffer = json["phrases"]
- rounds_survived = json["roundssurvived"]
- longest_survival = json["longestsurvival"]
- longest_deathstreak = json["longestdeathstreak"]
- if(!islist(speech_buffer))
- speech_buffer = list()
-
-/mob/living/simple_animal/parrot/Poly/proc/Write_Memory(dead)
- var/json_file = file("data/npc_saves/Poly.json")
- var/list/file_data = list()
- if(islist(speech_buffer))
- file_data["phrases"] = speech_buffer
- if(dead)
- file_data["roundssurvived"] = min(rounds_survived - 1, 0)
- file_data["longestsurvival"] = longest_survival
- if(rounds_survived - 1 < longest_deathstreak)
- file_data["longestdeathstreak"] = rounds_survived - 1
- else
- file_data["longestdeathstreak"] = longest_deathstreak
- else
- file_data["roundssurvived"] = rounds_survived + 1
- if(rounds_survived + 1 > longest_survival)
- file_data["longestsurvival"] = rounds_survived + 1
- else
- file_data["longestsurvival"] = longest_survival
- file_data["longestdeathstreak"] = longest_deathstreak
- fdel(json_file)
- WRITE_FILE(json_file, json_encode(file_data))
-
-/mob/living/simple_animal/parrot/Poly/ratvar_act()
- playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 75, TRUE)
- var/mob/living/simple_animal/parrot/clock_hawk/H = new(loc)
- H.setDir(dir)
- qdel(src)
-
-/mob/living/simple_animal/parrot/Poly/ghost
- name = "The Ghost of Poly"
- desc = "Doomed to squawk the Earth."
- color = "#FFFFFF77"
- speak_chance = 20
- status_flags = GODMODE
- incorporeal_move = INCORPOREAL_MOVE_BASIC
- butcher_results = list(/obj/item/ectoplasm = 1)
-
-/mob/living/simple_animal/parrot/Poly/ghost/Initialize()
- memory_saved = TRUE //At this point nothing is saved
- . = ..()
-
-/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_speech()
- if(ismob(loc))
- return
- ..()
-
-/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_movement()
- if(isliving(parrot_interest))
- if(!ishuman(parrot_interest))
- parrot_interest = null
- else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest))
- walk_to(src, parrot_interest, 0, parrot_speed)
- Possess(parrot_interest)
- ..()
-
-/mob/living/simple_animal/parrot/Poly/ghost/proc/Possess(mob/living/carbon/human/H)
- if(!ishuman(H))
- return
- var/datum/disease/parrot_possession/P = new
- P.parrot = src
- forceMove(H)
- H.ForceContractDisease(P)
- parrot_interest = null
- H.visible_message("[src] dive bombs into [H]'s chest and vanishes!", "[src] dive bombs into your chest, vanishing! This can't be good!")
-
-
-/mob/living/simple_animal/parrot/clock_hawk
- name = "clock hawk"
- desc = "Cbyl jnaan penpxre! Fdhnnnjx!"
- icon_state = "clock_hawk_fly"
- icon_living = "clock_hawk_fly"
- icon_sit = "clock_hawk_sit"
- speak = list("Penpxre!", "Ratvar vf n qhzo anzr naljnl!")
- speak_emote = list("squawks rustily", "says crassly", "yells brassly")
- emote_hear = list("squawks rustily.", "bawks metallically!")
- emote_see = list("flutters its metal wings.")
- faction = list("ratvar")
- gold_core_spawnable = NO_SPAWN
- del_on_death = TRUE
- death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg'
-
-/mob/living/simple_animal/parrot/clock_hawk/ratvar_act()
- return
+/* Parrots!
+ * Contains
+ * Defines
+ * Inventory (headset stuff)
+ * Attack responces
+ * AI
+ * Procs / Verbs (usable by players)
+ * Sub-types
+ * Hear & say (the things we do for gimmicks)
+ */
+
+/*
+ * Defines
+ */
+
+//Only a maximum of one action and one intent should be active at any given time.
+//Actions
+#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 (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 = "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"
+ var/icon_sit = "parrot_sit"
+ density = FALSE
+ health = 80
+ maxHealth = 80
+ pass_flags = PASSTABLE | PASSMOB
+
+ speak = list("Hi!","Hello!","Cracker?","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/ = 1)
+ melee_damage_upper = 10
+ melee_damage_lower = 5
+
+ response_help = "pets"
+ response_disarm = "gently moves aside"
+ response_harm = "swats"
+ stop_automated_movement = 1
+ a_intent = INTENT_HARM //parrots now start "aggressive" since only player parrots will nuzzle.
+ attacktext = "chomps"
+ friendly = "grooms"
+ mob_size = MOB_SIZE_SMALL
+ movement_type = FLYING
+ gold_core_spawnable = FRIENDLY_SPAWN
+
+ var/parrot_damage_upper = 10
+ var/parrot_state = PARROT_WANDER //Hunt for a perch when created
+ var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick.
+ var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down
+ var/parrot_dam_zone = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) //For humans, select a bodypart to attack
+
+ 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_lastmove = null //Updates/Stores position of the parrot while it's moving
+ var/parrot_stuck = 0 //If parrot_lastmove hasnt changed, this will increment until it reaches parrot_stuck_threshold
+ var/parrot_stuck_threshold = 10 //if this == parrot_stuck, it'll force the parrot back to wandering
+
+ var/list/speech_buffer = list()
+ var/speech_shuffle_rate = 20
+ var/list/available_channels = list()
+
+ //Headset for Poly to yell at engineers :)
+ var/obj/item/radio/headset/ears = null
+
+ //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from,
+ //mobs it wants to attack or mobs that have attacked it
+ var/atom/movable/parrot_interest = null
+
+ //Parrots will generally sit on their perch unless something catches their eye.
+ //These vars store their preffered perch and if they dont have one, what they can use as a perch
+ var/obj/parrot_perch = null
+ var/obj/desired_perches = list(/obj/structure/frame/computer, /obj/structure/displaycase, \
+ /obj/structure/filingcabinet, /obj/machinery/teleport, \
+ /obj/machinery/computer, /obj/machinery/clonepod, \
+ /obj/machinery/dna_scannernew, /obj/machinery/telecomms, \
+ /obj/machinery/nuclearbomb, /obj/machinery/particle_accelerator, \
+ /obj/machinery/recharge_station, /obj/machinery/smartfridge, \
+ /obj/machinery/suit_storage_unit)
+
+ //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding.
+ var/obj/item/held_item = null
+
+
+/mob/living/simple_animal/parrot/Initialize()
+ . = ..()
+ if(!ears)
+ var/headset = pick(/obj/item/radio/headset/headset_sec, \
+ /obj/item/radio/headset/headset_eng, \
+ /obj/item/radio/headset/headset_med, \
+ /obj/item/radio/headset/headset_sci, \
+ /obj/item/radio/headset/headset_cargo)
+ ears = new headset(src)
+
+ parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var
+
+ verbs.Add(/mob/living/simple_animal/parrot/proc/steal_from_ground, \
+ /mob/living/simple_animal/parrot/proc/steal_from_mob, \
+ /mob/living/simple_animal/parrot/verb/drop_held_item_player, \
+ /mob/living/simple_animal/parrot/proc/perch_player, \
+ /mob/living/simple_animal/parrot/proc/toggle_mode,
+ /mob/living/simple_animal/parrot/proc/perch_mob_player)
+
+
+/mob/living/simple_animal/parrot/examine(mob/user)
+ . = ..()
+ if(stat)
+ . += pick("This parrot is no more.", "This is a late parrot.", "This is an ex-parrot.")
+
+/mob/living/simple_animal/parrot/death(gibbed)
+ if(held_item)
+ held_item.forceMove(drop_location())
+ held_item = null
+ walk(src,0)
+
+ if(buckled)
+ buckled.unbuckle_mob(src,force=1)
+ buckled = null
+ pixel_x = initial(pixel_x)
+ pixel_y = initial(pixel_y)
+
+ ..(gibbed)
+
+/mob/living/simple_animal/parrot/Stat()
+ ..()
+ if(statpanel("Status"))
+ stat("Held Item", held_item)
+ stat("Mode",a_intent)
+
+/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
+ . = ..()
+ if(speaker != src && prob(50)) //Dont imitate ourselves
+ if(!radio_freq || prob(10))
+ if(speech_buffer.len >= 500)
+ speech_buffer -= pick(speech_buffer)
+ speech_buffer |= html_decode(raw_message)
+ if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes!
+ return message
+
+/mob/living/simple_animal/parrot/radio(message, message_mode, list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be.
+ . = ..()
+ if(. != 0)
+ return .
+
+ switch(message_mode)
+ if(MODE_HEADSET)
+ if (ears)
+ ears.talk_into(src, message, , spans, language)
+ return ITALICS | REDUCE_RANGE
+
+ if(MODE_DEPARTMENT)
+ if (ears)
+ ears.talk_into(src, message, message_mode, spans, language)
+ return ITALICS | REDUCE_RANGE
+
+ if(message_mode in GLOB.radiochannels)
+ if(ears)
+ ears.talk_into(src, message, message_mode, spans, language)
+ return ITALICS | REDUCE_RANGE
+
+ return 0
+
+/*
+ * Inventory
+ */
+/mob/living/simple_animal/parrot/show_inv(mob/user)
+ user.set_machine(src)
+
+ var/dat = "
"
+ dat += "
Headset: [ears]" : "add_inv=ears'>Nothing"]"
+
+ user << browse(dat, "window=mob[REF(src)];size=325x500")
+ onclose(user, "window=mob[REF(src)]")
+
+
+/mob/living/simple_animal/parrot/Topic(href, href_list)
+ if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ usr << browse(null, "window=mob[REF(src)]")
+ usr.unset_machine()
+ return
+
+ //Removing from inventory
+ if(href_list["remove_inv"])
+ var/remove_from = href_list["remove_inv"]
+ switch(remove_from)
+ if("ears")
+ if(!ears)
+ to_chat(usr, "There is nothing to remove from its [remove_from]!")
+ return
+ if(!stat)
+ say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
+ ears.forceMove(drop_location())
+ ears = null
+ for(var/possible_phrase in speak)
+ if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys)
+ possible_phrase = copytext(possible_phrase,3)
+
+ //Adding things to inventory
+ else if(href_list["add_inv"])
+ var/add_to = href_list["add_inv"]
+ if(!usr.get_active_held_item())
+ to_chat(usr, "You have nothing in your hand to put on its [add_to]!")
+ return
+ switch(add_to)
+ if("ears")
+ if(ears)
+ to_chat(usr, "It's already wearing something!")
+ return
+ else
+ var/obj/item/item_to_add = usr.get_active_held_item()
+ if(!item_to_add)
+ return
+
+ if( !istype(item_to_add, /obj/item/radio/headset) )
+ to_chat(usr, "This object won't fit!")
+ return
+
+ var/obj/item/radio/headset/headset_to_add = item_to_add
+
+ if(!usr.transferItemToLoc(headset_to_add, src))
+ return
+ ears = headset_to_add
+ to_chat(usr, "You fit the headset onto [src].")
+
+ clearlist(available_channels)
+ for(var/ch in headset_to_add.channels)
+ switch(ch)
+ if(RADIO_CHANNEL_ENGINEERING)
+ available_channels.Add(RADIO_TOKEN_ENGINEERING)
+ if(RADIO_CHANNEL_COMMAND)
+ available_channels.Add(RADIO_TOKEN_COMMAND)
+ if(RADIO_CHANNEL_SECURITY)
+ available_channels.Add(RADIO_TOKEN_SECURITY)
+ if(RADIO_CHANNEL_SCIENCE)
+ available_channels.Add(RADIO_TOKEN_SCIENCE)
+ if(RADIO_CHANNEL_MEDICAL)
+ available_channels.Add(RADIO_TOKEN_MEDICAL)
+ if(RADIO_CHANNEL_SUPPLY)
+ available_channels.Add(RADIO_TOKEN_SUPPLY)
+ if(RADIO_CHANNEL_SERVICE)
+ available_channels.Add(RADIO_TOKEN_SERVICE)
+
+ if(headset_to_add.translate_binary)
+ available_channels.Add(MODE_TOKEN_BINARY)
+ else
+ return ..()
+
+
+/*
+ * Attack responces
+ */
+//Humans, monkeys, aliens
+/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M)
+ ..()
+ if(client)
+ return
+ if(!stat && M.a_intent == INTENT_HARM)
+
+ icon_state = icon_living //It is going to be flying regardless of whether it flees or attacks
+
+ if(parrot_state == PARROT_PERCH)
+ parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
+
+ parrot_interest = M
+ parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction..
+
+ if(health > 30) //Let's get in there and squawk it up!
+ parrot_state |= PARROT_ATTACK
+ else
+ parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell!
+ drop_held_item(0)
+ if(stat != DEAD && M.a_intent == INTENT_HELP)
+ handle_automated_speech(1) //assured speak/emote
+ return
+
+/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M)
+ return attack_hand(M)
+
+/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/M)
+ return attack_hand(M)
+
+//Simple animals
+/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M)
+ . = ..() //goodbye immortal parrots
+
+ if(client)
+ return
+
+ if(parrot_state == PARROT_PERCH)
+ parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
+
+ if(M.melee_damage_upper > 0 && !stat)
+ parrot_interest = M
+ parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless
+ icon_state = icon_living
+
+//Mobs with objects
+/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params)
+ if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/reagent_containers/food/snacks/cracker))
+ if(O.force)
+ if(parrot_state == PARROT_PERCH)
+ parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
+
+ parrot_interest = user
+ parrot_state = PARROT_SWOOP
+ if(health > 30) //Let's get in there and squawk it up!
+ parrot_state |= PARROT_ATTACK
+ else
+ parrot_state |= PARROT_FLEE
+ icon_state = icon_living
+ drop_held_item(0)
+ else if(istype(O, /obj/item/reagent_containers/food/snacks/cracker)) //Poly wants a cracker.
+ qdel(O)
+ if(health < maxHealth)
+ adjustBruteLoss(-10)
+ speak_chance *= 1.27 // 20 crackers to go from 1% to 100%
+ speech_shuffle_rate += 10
+ to_chat(user, "[src] eagerly devours the cracker.")
+ return // the cracker was deleted
+ return ..()
+
+//Bullets
+/mob/living/simple_animal/parrot/bullet_act(obj/item/projectile/Proj)
+ ..()
+ if(!stat && !client)
+ if(parrot_state == PARROT_PERCH)
+ parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
+
+ parrot_interest = null
+ parrot_state = PARROT_WANDER | PARROT_FLEE //Been shot and survived! RUN LIKE HELL!
+ //parrot_been_shot += 5
+ icon_state = icon_living
+ drop_held_item(0)
+ return
+
+
+/*
+ * AI - Not really intelligent, but I'm calling it AI anyway.
+ */
+/mob/living/simple_animal/parrot/Life()
+ ..()
+
+ //Sprite update for when a parrot gets pulled
+ if(pulledby && !stat && parrot_state != PARROT_WANDER)
+ if(buckled)
+ buckled.unbuckle_mob(src, TRUE)
+ buckled = null
+ icon_state = icon_living
+ parrot_state = PARROT_WANDER
+ pixel_x = initial(pixel_x)
+ pixel_y = initial(pixel_y)
+ return
+
+
+//-----SPEECH
+ /* Parrot speech mimickry!
+ Phrases that the parrot Hear()s get added to speach_buffer.
+ Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. */
+/mob/living/simple_animal/parrot/handle_automated_speech()
+ ..()
+ if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one
+ if(speak.len)
+ speak.Remove(pick(speak))
+
+ speak.Add(pick(speech_buffer))
+
+
+/mob/living/simple_animal/parrot/handle_automated_movement()
+ if(!isturf(src.loc) || !canmove || buckled)
+ return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove)
+
+ if(client && stat == CONSCIOUS && parrot_state != icon_living)
+ icon_state = icon_living
+ //Because the most appropriate place to set icon_state is movement_delay(), clearly
+
+//-----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 in view(src))
+ parrot_state = PARROT_SWOOP | PARROT_RETURN
+ icon_state = icon_living
+ return
+ else
+ parrot_state = PARROT_WANDER
+ icon_state = icon_living
+ return
+
+ 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
+
+ //Cycle through message modes for the headset
+ if(speak.len)
+ var/list/newspeak = list()
+
+ if(available_channels.len && src.ears)
+ for(var/possible_phrase in speak)
+
+ //50/50 chance to not use the radio at all
+ var/useradio = 0
+ if(prob(50))
+ useradio = 1
+
+ if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys))
+ possible_phrase = "[useradio?pick(available_channels):""][copytext(possible_phrase,3)]" //crop out the channel prefix
+ else
+ possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]"
+
+ newspeak.Add(possible_phrase)
+
+ else //If we have no headset or channels to use, dont try to use any!
+ for(var/possible_phrase in speak)
+ if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys))
+ possible_phrase = copytext(possible_phrase,3) //crop out the channel prefix
+ newspeak.Add(possible_phrase)
+ speak = newspeak
+
+ //Search for item to steal
+ parrot_interest = search_for_item()
+ if(parrot_interest)
+ emote("me", 1, "looks in [parrot_interest]'s direction and takes flight.")
+ parrot_state = PARROT_SWOOP | PARROT_STEAL
+ icon_state = icon_living
+ return
+
+//-----WANDERING - This is basically a 'I dont know what to do yet' state
+ else if(parrot_state == PARROT_WANDER)
+ //Stop movement, we'll set it later
+ walk(src, 0)
+ parrot_interest = null
+
+ //Wander around aimlessly. This will help keep the loops from searches down
+ //and possibly move the mob into a new are in view of something they can use
+ if(prob(90))
+ step(src, pick(GLOB.cardinals))
+ return
+
+ if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do.
+ var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item
+ if(AM)
+ if(istype(AM, /obj/item) || isliving(AM)) //If stealable item
+ parrot_interest = AM
+ emote("me", 1, "turns and flies towards [parrot_interest].")
+ parrot_state = PARROT_SWOOP | PARROT_STEAL
+ return
+ else //Else it's a perch
+ parrot_perch = AM
+ parrot_state = PARROT_SWOOP | PARROT_RETURN
+ return
+ return
+
+ if(parrot_interest && parrot_interest in view(src))
+ parrot_state = PARROT_SWOOP | PARROT_STEAL
+ return
+
+ if(parrot_perch && parrot_perch in view(src))
+ 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
+ return
+//-----STEALING
+ else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL))
+ walk(src,0)
+ if(!parrot_interest || held_item)
+ parrot_state = PARROT_SWOOP | PARROT_RETURN
+ return
+
+ if(!(parrot_interest in view(src)))
+ parrot_state = PARROT_SWOOP | PARROT_RETURN
+ return
+
+ 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.forceMove(src)
+ 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
+ return
+
+ walk_to(src, parrot_interest, 1, parrot_speed)
+ if(isStuck())
+ return
+
+ return
+
+//-----RETURNING TO PERCH
+ 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(Adjacent(parrot_perch))
+ forceMove(parrot_perch.loc)
+ drop_held_item()
+ parrot_state = PARROT_PERCH
+ icon_state = icon_sit
+ return
+
+ walk_to(src, parrot_perch, 1, parrot_speed)
+ if(isStuck())
+ return
+
+ return
+
+//-----FLEEING
+ else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE))
+ walk(src,0)
+ if(!parrot_interest || !isliving(parrot_interest)) //Sanity
+ parrot_state = PARROT_WANDER
+
+ walk_away(src, parrot_interest, 1, parrot_speed)
+ if(isStuck())
+ return
+
+ return
+
+//-----ATTACKING
+ 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
+ parrot_state = PARROT_WANDER
+ return
+
+ var/mob/living/L = parrot_interest
+ if(melee_damage_upper == 0)
+ melee_damage_upper = parrot_damage_upper
+ a_intent = INTENT_HARM
+
+ //If the mob is close enough to interact with
+ 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
+ if(!held_item)
+ 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.
+ 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
+ else
+ parrot_state = PARROT_WANDER
+ return
+
+ attacktext = pick("claws at", "chomps")
+ L.attack_animal(src)//Time for the hurt to begin!
+ //Otherwise, fly towards the mob!
+ else
+ walk_to(src, parrot_interest, 1, parrot_speed)
+ if(isStuck())
+ return
+
+ return
+//-----STATE MISHAP
+ else //This should not happen. If it does lets reset everything and try again
+ walk(src,0)
+ parrot_interest = null
+ parrot_perch = null
+ drop_held_item()
+ parrot_state = PARROT_WANDER
+ return
+
+/*
+ * Procs
+ */
+
+/mob/living/simple_animal/parrot/proc/isStuck()
+ //Check to see if the parrot is stuck due to things like windows or doors or windowdoors
+ if(parrot_lastmove)
+ if(parrot_lastmove == src.loc)
+ if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander.
+ parrot_state = PARROT_WANDER
+ parrot_stuck = 0
+ parrot_lastmove = null
+ return 1
+ else
+ parrot_lastmove = null
+ else
+ parrot_lastmove = src.loc
+ return 0
+
+/mob/living/simple_animal/parrot/proc/search_for_item()
+ var/item
+ for(var/atom/movable/AM in view(src))
+ //Skip items we already stole or are wearing or are too big
+ if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src)
+ continue
+ if(istype(AM, /obj/item))
+ var/obj/item/I = AM
+ if(I.w_class < WEIGHT_CLASS_SMALL)
+ item = I
+ else if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ for(var/obj/item/I in C.held_items)
+ if(I.w_class <= WEIGHT_CLASS_SMALL)
+ item = I
+ break
+ if(item)
+ if(!AStar(src, get_turf(item), /turf/proc/Distance_cardinal))
+ item = null
+ continue
+ return item
+
+ return null
+
+/mob/living/simple_animal/parrot/proc/search_for_perch()
+ for(var/obj/O in view(src))
+ for(var/path in desired_perches)
+ if(istype(O, path))
+ return O
+ return null
+
+//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))
+ for(var/perch_path in desired_perches)
+ if(istype(AM, perch_path))
+ return AM
+
+ //Skip items we already stole or are wearing or are too big
+ if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src)
+ continue
+
+ if(istype(AM, /obj/item))
+ var/obj/item/I = AM
+ if(I.w_class <= WEIGHT_CLASS_SMALL)
+ return I
+
+ if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ for(var/obj/item/I in C.held_items)
+ if(I.w_class <= WEIGHT_CLASS_SMALL)
+ return C
+ return null
+
+
+/*
+ * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots.
+ */
+/mob/living/simple_animal/parrot/proc/steal_from_ground()
+ set name = "Steal from ground"
+ set category = "Parrot"
+ set desc = "Grabs a nearby item."
+
+ if(stat)
+ return -1
+
+ if(held_item)
+ to_chat(src, "You are already holding [held_item]!")
+ return 1
+
+ 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.forceMove(src)
+ 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!")
+ return 0
+
+/mob/living/simple_animal/parrot/proc/steal_from_mob()
+ set name = "Steal from mob"
+ set category = "Parrot"
+ set desc = "Steals an item right out of a person's hand!"
+
+ if(stat)
+ return -1
+
+ if(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/obj/item/I in C.held_items)
+ if(I.w_class <= WEIGHT_CLASS_SMALL)
+ stolen_item = I
+ break
+
+ if(stolen_item)
+ C.transferItemToLoc(stolen_item, src, TRUE)
+ held_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!")
+ return 0
+
+/mob/living/simple_animal/parrot/verb/drop_held_item_player()
+ set name = "Drop held item"
+ set category = "Parrot"
+ set desc = "Drop the item you're holding."
+
+ if(stat)
+ return
+
+ src.drop_held_item()
+
+ return
+
+/mob/living/simple_animal/parrot/proc/drop_held_item(drop_gently = 1)
+ set name = "Drop held item"
+ set category = "Parrot"
+ set desc = "Drop the item you're holding."
+
+ if(stat)
+ return -1
+
+ if(!held_item)
+ if(src == usr) //So that other mobs wont make this message appear when they're bludgeoning you.
+ to_chat(src, "You have nothing to drop!")
+ return 0
+
+
+//parrots will eat crackers instead of dropping them
+ if(istype(held_item, /obj/item/reagent_containers/food/snacks/cracker) && (drop_gently))
+ qdel(held_item)
+ held_item = null
+ if(health < maxHealth)
+ adjustBruteLoss(-10)
+ emote("me", 1, "[src] eagerly downs the cracker.")
+ return 1
+
+
+ if(!drop_gently)
+ if(istype(held_item, /obj/item/grenade))
+ var/obj/item/grenade/G = held_item
+ G.forceMove(drop_location())
+ G.prime()
+ to_chat(src, "You let go of [held_item]!")
+ held_item = null
+ return 1
+
+ to_chat(src, "You drop [held_item].")
+
+ held_item.forceMove(drop_location())
+ held_item = null
+ return 1
+
+/mob/living/simple_animal/parrot/proc/perch_player()
+ set name = "Sit"
+ set category = "Parrot"
+ set desc = "Sit on a nice comfy perch."
+
+ if(stat || !client)
+ return
+
+ if(icon_state == icon_living)
+ for(var/atom/movable/AM in view(src,1))
+ for(var/perch_path in desired_perches)
+ if(istype(AM, perch_path))
+ src.forceMove(AM.loc)
+ icon_state = icon_sit
+ parrot_state = PARROT_PERCH
+ return
+ to_chat(src, "There is no perch nearby to sit on!")
+ return
+
+/mob/living/simple_animal/parrot/Moved(oldLoc, dir)
+ . = ..()
+ if(. && !stat && client && parrot_state == PARROT_PERCH)
+ parrot_state = PARROT_WANDER
+ icon_state = icon_living
+ pixel_x = initial(pixel_x)
+ pixel_y = initial(pixel_y)
+
+/mob/living/simple_animal/parrot/proc/perch_mob_player()
+ set name = "Sit on Human's Shoulder"
+ set category = "Parrot"
+ set desc = "Sit on a nice comfy human being!"
+
+ if(stat || !client)
+ return
+
+ if(!buckled)
+ for(var/mob/living/carbon/human/H in view(src,1))
+ if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime
+ continue
+ perch_on_human(H)
+ return
+ to_chat(src, "There is nobody nearby that you can sit on!")
+ else
+ icon_state = icon_living
+ parrot_state = PARROT_WANDER
+ if(buckled)
+ to_chat(src, "You are no longer sitting on [buckled]'s shoulder.")
+ buckled.unbuckle_mob(src, TRUE)
+ buckled = null
+ pixel_x = initial(pixel_x)
+ pixel_y = initial(pixel_y)
+
+
+
+/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H)
+ if(!H)
+ return
+ forceMove(get_turf(H))
+ if(H.buckle_mob(src, TRUE))
+ pixel_y = 9
+ pixel_x = pick(-8,8) //pick left or right shoulder
+ icon_state = icon_sit
+ parrot_state = PARROT_PERCH
+ to_chat(src, "You sit on [H]'s shoulder.")
+
+
+/mob/living/simple_animal/parrot/proc/toggle_mode()
+ set name = "Toggle mode"
+ set category = "Parrot"
+ set desc = "Time to bear those claws!"
+
+ if(stat || !client)
+ return
+
+ if(a_intent != INTENT_HELP)
+ melee_damage_upper = 0
+ a_intent = INTENT_HELP
+ else
+ melee_damage_upper = parrot_damage_upper
+ a_intent = INTENT_HARM
+ to_chat(src, "You will now [a_intent] others.")
+ return
+
+/*
+ * Sub-types
+ */
+/mob/living/simple_animal/parrot/Poly
+ name = "Poly"
+ desc = "Poly the Parrot. An expert on quantum cracker theory."
+ speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE")
+ gold_core_spawnable = NO_SPAWN
+ speak_chance = 3
+ var/memory_saved = FALSE
+ var/rounds_survived = 0
+ var/longest_survival = 0
+ var/longest_deathstreak = 0
+
+/mob/living/simple_animal/parrot/Poly/Initialize()
+ ears = new /obj/item/radio/headset/headset_eng(src)
+ available_channels = list(":e")
+ Read_Memory()
+ if(rounds_survived == longest_survival)
+ speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?")
+ desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]."
+ speak_chance = 20 //His hubris has made him more annoying/easier to justify killing
+ add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY)
+ else if(rounds_survived == longest_deathstreak)
+ speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!")
+ desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..."
+ add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY)
+ else if(rounds_survived > 0)
+ speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!")
+ desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!"
+ else
+ speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!")
+
+ . = ..()
+
+/mob/living/simple_animal/parrot/Poly/Life()
+ if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
+ Write_Memory(FALSE)
+ memory_saved = TRUE
+ ..()
+
+/mob/living/simple_animal/parrot/Poly/death(gibbed)
+ if(!memory_saved)
+ Write_Memory(TRUE)
+ if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666))
+ var/mob/living/simple_animal/parrot/Poly/ghost/G = new(loc)
+ if(mind)
+ mind.transfer_to(G)
+ else
+ G.key = key
+ ..(gibbed)
+
+/mob/living/simple_animal/parrot/Poly/proc/Read_Memory()
+ if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new
+ var/savefile/S = new /savefile("data/npc_saves/Poly.sav")
+ S["phrases"] >> speech_buffer
+ S["roundssurvived"] >> rounds_survived
+ S["longestsurvival"] >> longest_survival
+ S["longestdeathstreak"] >> longest_deathstreak
+ fdel("data/npc_saves/Poly.sav")
+ else
+ var/json_file = file("data/npc_saves/Poly.json")
+ if(!fexists(json_file))
+ return
+ var/list/json = json_decode(file2text(json_file))
+ speech_buffer = json["phrases"]
+ rounds_survived = json["roundssurvived"]
+ longest_survival = json["longestsurvival"]
+ longest_deathstreak = json["longestdeathstreak"]
+ if(!islist(speech_buffer))
+ speech_buffer = list()
+
+/mob/living/simple_animal/parrot/Poly/proc/Write_Memory(dead)
+ var/json_file = file("data/npc_saves/Poly.json")
+ var/list/file_data = list()
+ if(islist(speech_buffer))
+ file_data["phrases"] = speech_buffer
+ if(dead)
+ file_data["roundssurvived"] = min(rounds_survived - 1, 0)
+ file_data["longestsurvival"] = longest_survival
+ if(rounds_survived - 1 < longest_deathstreak)
+ file_data["longestdeathstreak"] = rounds_survived - 1
+ else
+ file_data["longestdeathstreak"] = longest_deathstreak
+ else
+ file_data["roundssurvived"] = rounds_survived + 1
+ if(rounds_survived + 1 > longest_survival)
+ file_data["longestsurvival"] = rounds_survived + 1
+ else
+ file_data["longestsurvival"] = longest_survival
+ file_data["longestdeathstreak"] = longest_deathstreak
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
+
+/mob/living/simple_animal/parrot/Poly/ratvar_act()
+ playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 75, TRUE)
+ var/mob/living/simple_animal/parrot/clock_hawk/H = new(loc)
+ H.setDir(dir)
+ qdel(src)
+
+/mob/living/simple_animal/parrot/Poly/ghost
+ name = "The Ghost of Poly"
+ desc = "Doomed to squawk the Earth."
+ color = "#FFFFFF77"
+ speak_chance = 20
+ status_flags = GODMODE
+ incorporeal_move = INCORPOREAL_MOVE_BASIC
+ butcher_results = list(/obj/item/ectoplasm = 1)
+
+/mob/living/simple_animal/parrot/Poly/ghost/Initialize()
+ memory_saved = TRUE //At this point nothing is saved
+ . = ..()
+
+/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_speech()
+ if(ismob(loc))
+ return
+ ..()
+
+/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_movement()
+ if(isliving(parrot_interest))
+ if(!ishuman(parrot_interest))
+ parrot_interest = null
+ else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest))
+ walk_to(src, parrot_interest, 0, parrot_speed)
+ Possess(parrot_interest)
+ ..()
+
+/mob/living/simple_animal/parrot/Poly/ghost/proc/Possess(mob/living/carbon/human/H)
+ if(!ishuman(H))
+ return
+ var/datum/disease/parrot_possession/P = new
+ P.parrot = src
+ forceMove(H)
+ H.ForceContractDisease(P)
+ parrot_interest = null
+ H.visible_message("[src] dive bombs into [H]'s chest and vanishes!", "[src] dive bombs into your chest, vanishing! This can't be good!")
+
+
+/mob/living/simple_animal/parrot/clock_hawk
+ name = "clock hawk"
+ desc = "Cbyl jnaan penpxre! Fdhnnnjx!"
+ icon_state = "clock_hawk_fly"
+ icon_living = "clock_hawk_fly"
+ icon_sit = "clock_hawk_sit"
+ speak = list("Penpxre!", "Ratvar vf n qhzo anzr naljnl!")
+ speak_emote = list("squawks rustily", "says crassly", "yells brassly")
+ emote_hear = list("squawks rustily.", "bawks metallically!")
+ emote_see = list("flutters its metal wings.")
+ faction = list("ratvar")
+ gold_core_spawnable = NO_SPAWN
+ del_on_death = TRUE
+ death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg'
+
+/mob/living/simple_animal/parrot/clock_hawk/ratvar_act()
+ return
diff --git a/code/modules/mob/living/simple_animal/slime/say.dm b/code/modules/mob/living/simple_animal/slime/say.dm
index a2618b71..c48249d8 100644
--- a/code/modules/mob/living/simple_animal/slime/say.dm
+++ b/code/modules/mob/living/simple_animal/slime/say.dm
@@ -1,4 +1,4 @@
-/mob/living/simple_animal/slime/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode)
+/mob/living/simple_animal/slime/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source)
. = ..()
if(speaker != src && !radio_freq && !stat)
if (speaker in Friends)
diff --git a/modular_citadel/code/modules/mob/mob.dm b/modular_citadel/code/modules/mob/mob.dm
index e2e90ce9..b81344c5 100644
--- a/modular_citadel/code/modules/mob/mob.dm
+++ b/modular_citadel/code/modules/mob/mob.dm
@@ -16,13 +16,13 @@
else
return
-/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE)
+/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE, atom/movable/source)
. = ..()
- if(istype(speaker, /mob/living))
- var/turf/speakturf = get_turf(speaker)
- var/turf/sourceturf = get_turf(src)
- if(istype(speakturf) && istype(sourceturf) && !(speakturf in get_hear(5, sourceturf)))
- . = "