"
- if(ears)
- dat += "
Headset: [ears] (Remove)"
- else
- dat += "
Headset: Nothing"
-
- user << browse(dat, text("window=mob[];size=325x500", name))
- onclose(user, "mob[real_name]")
- return
-
-/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))
- return
-
- //Is the usr's mob type able to do this?
- if(ishuman(usr) || issmall(usr) || isrobot(usr))
-
- //Removing from inventory
- if(href_list["remove_inv"])
- var/remove_from = href_list["remove_inv"]
- switch(remove_from)
- if("ears")
- if(ears)
- if(available_channels.len)
- src.say("[pick(available_channels)] BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
- else
- src.say("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
- ears.loc = src.loc
- ears = null
- for(var/possible_phrase in speak)
- if(copytext(possible_phrase,1,3) in department_radio_keys)
- possible_phrase = copytext(possible_phrase,3,length(possible_phrase))
- else
- usr << "\red There is nothing to remove from its [remove_from]."
- return
-
- //Adding things to inventory
- else if(href_list["add_inv"])
- var/add_to = href_list["add_inv"]
- if(!usr.get_active_hand())
- usr << "\red You have nothing in your hand to put on its [add_to]."
- return
- switch(add_to)
- if("ears")
- if(ears)
- usr << "\red It's already wearing something."
- return
- else
- var/obj/item/item_to_add = usr.get_active_hand()
- if(!item_to_add)
- return
-
- if( !istype(item_to_add, /obj/item/device/radio/headset) )
- usr << "\red This object won't fit."
- return
-
- var/obj/item/device/radio/headset/headset_to_add = item_to_add
-
- usr.drop_item()
- headset_to_add.loc = src
- src.ears = headset_to_add
- usr << "You fit the headset onto [src]."
-
- clearlist(available_channels)
- for(var/ch in headset_to_add.channels)
- switch(ch)
- if("Engineering")
- available_channels.Add(":e")
- if("Command")
- available_channels.Add(":c")
- if("Security")
- available_channels.Add(":s")
- if("Science")
- available_channels.Add(":n")
- if("Medical")
- available_channels.Add(":m")
- if("Mining")
- available_channels.Add(":d")
- if("Cargo")
- available_channels.Add(":q")
-
- if(headset_to_add.translate_binary)
- available_channels.Add(":b")
- else
- ..()
-
-
-/*
- * Attack responces
- */
-//Humans, monkeys, aliens
-/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M as mob)
- ..()
- if(client) return
- if(!stat && M.a_intent == I_HURT)
-
- icon_state = "parrot_fly" //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(M.health < 50) //Weakened mob? Fight back!
- parrot_state |= PARROT_ATTACK
- else
- parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell!
- drop_held_item(0)
- return
-
-//Mobs with objects
-/mob/living/simple_animal/parrot/attackby(var/obj/item/O as obj, var/mob/user as mob)
- ..()
- if(!stat && !client && !istype(O, /obj/item/stack/medical))
- 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 | PARROT_FLEE
- icon_state = "parrot_fly"
- drop_held_item(0)
- return
-
-//Bullets
-/mob/living/simple_animal/parrot/bullet_act(var/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 //OWFUCK, Been shot! RUN LIKE HELL!
- parrot_been_shot += 5
- icon_state = "parrot_fly"
- drop_held_item(0)
- return
-
-
-/*
- * AI - Not really intelligent, but I'm calling it AI anyway.
- */
-/mob/living/simple_animal/parrot/Life()
- ..()
-
- //Sprite and AI update for when a parrot gets pulled
- if(pulledby && stat == CONSCIOUS)
- icon_state = "parrot_fly"
- if(!client)
- parrot_state = PARROT_WANDER
- return
-
- if(client || stat)
- return //Lets not force players or dead/incap parrots to move
-
- 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)
-
-
-//-----SPEECH
- /* Parrot speech mimickry!
- Phrases that the parrot hears in mob/living/say() 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.
- Then it clears the buffer to make sure they dont magically remember something from hours ago. */
- if(speech_buffer.len && prob(10))
- if(speak.len)
- speak.Remove(pick(speak))
-
- speak.Add(pick(speech_buffer))
- clearlist(speech_buffer)
-
-
-//-----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 = "parrot_fly"
- return
- else
- parrot_state = PARROT_WANDER
- icon_state = "parrot_fly"
- 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,3) in department_radio_keys)
- possible_phrase = "[useradio?pick(available_channels):""] [copytext(possible_phrase,3,length(possible_phrase)+1)]" //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,3) in department_radio_keys)
- possible_phrase = "[copytext(possible_phrase,3,length(possible_phrase)+1)]" //crop out the channel prefix
- newspeak.Add(possible_phrase)
- speak = newspeak
-
- //Search for item to steal
- parrot_interest = search_for_item()
- if(parrot_interest)
- visible_emote("looks in [parrot_interest]'s direction and takes flight")
- parrot_state = PARROT_SWOOP | PARROT_STEAL
- icon_state = "parrot_fly"
- 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(cardinal))
- 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
- visible_emote("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(in_range(src, 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]!", "\blue You grab the [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)
- 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(in_range(src, parrot_perch))
- src.loc = parrot_perch.loc
- drop_held_item()
- parrot_state = PARROT_PERCH
- icon_state = "parrot_sit"
- return
-
- walk_to(src, parrot_perch, 1, 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
-
- walk_away(src, parrot_interest, 1, parrot_speed-parrot_been_shot)
- parrot_been_shot--
- 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 the mob is close enough to interact with
- if(in_range(src, 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
-
- //Time for the hurt to begin!
- 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)
- visible_emote(pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons."))
-
- else
- L.adjustBruteLoss(damage)
- visible_emote(pick("pecks at [L].", "claws [L]."))
- return
-
- //Otherwise, fly towards the mob!
- else
- 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)
- parrot_interest = null
- parrot_perch = null
- drop_held_item()
- parrot_state = PARROT_WANDER
- return
-
-/*
- * Procs
- */
-
-/mob/living/simple_animal/parrot/movement_delay()
- if(client && stat == CONSCIOUS && parrot_state != "parrot_fly")
- icon_state = "parrot_fly"
- ..()
-
-/mob/living/simple_animal/parrot/proc/search_for_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 < ITEMSIZE_SMALL)
- return I
-
- if(iscarbon(AM))
- var/mob/living/carbon/C = AM
- if((C.l_hand && C.l_hand.w_class <= ITEMSIZE_SMALL) || (C.r_hand && C.r_hand.w_class <= ITEMSIZE_SMALL))
- return C
- 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 <= ITEMSIZE_SMALL)
- return I
-
- if(iscarbon(AM))
- var/mob/living/carbon/C = AM
- if(C.l_hand && C.l_hand.w_class <= ITEMSIZE_SMALL || C.r_hand && C.r_hand.w_class <= ITEMSIZE_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)
- src << "\red You are already holding the [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 <= ITEMSIZE_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]!", "\blue You grab the [held_item]!", "You hear the sounds of wings flapping furiously.")
- return held_item
-
- src << "\red 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)
- src << "\red You are already holding the [held_item]"
- return 1
-
- var/obj/item/stolen_item = null
-
- for(var/mob/living/carbon/C in view(1,src))
- if(C.l_hand && C.l_hand.w_class <= ITEMSIZE_SMALL)
- stolen_item = C.l_hand
-
- if(C.r_hand && C.r_hand.w_class <= ITEMSIZE_SMALL)
- stolen_item = C.r_hand
-
- if(stolen_item)
- C.remove_from_mob(stolen_item)
- held_item = stolen_item
- stolen_item.loc = src
- visible_message("[src] grabs the [held_item] out of [C]'s hand!", "\blue You snag the [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.")
- return held_item
-
- src << "\red 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(var/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)
- usr << "\red You have nothing to drop!"
- return 0
-
- if(!drop_gently)
- if(istype(held_item, /obj/item/weapon/grenade))
- var/obj/item/weapon/grenade/G = held_item
- G.loc = src.loc
- G.prime()
- src << "You let go of the [held_item]!"
- held_item = null
- return 1
-
- src << "You drop the [held_item]."
-
- held_item.loc = src.loc
- 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 == "parrot_fly")
- 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
- icon_state = "parrot_sit"
- return
- src << "\red There is no perch nearby to sit on."
- 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 singlo, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS FREE CALL THE SHUTTLE")
-
-/mob/living/simple_animal/parrot/Poly/New()
- ears = new /obj/item/device/radio/headset/headset_eng(src)
- available_channels = list(":e")
- ..()
-
-/mob/living/simple_animal/parrot/say(var/message)
-
- if(stat)
- return
-
- var/verb = "says"
- if(speak_emote.len)
- verb = pick(speak_emote)
-
-
- var/message_mode=""
- if(copytext(message,1,2) == ";")
- message_mode = "headset"
- message = copytext(message,2)
-
- if(length(message) >= 2)
- var/channel_prefix = copytext(message, 1 ,3)
- message_mode = department_radio_keys[channel_prefix]
-
- if(copytext(message,1,2) == ":")
- var/positioncut = 3
- message = trim(copytext(message,positioncut))
-
- message = capitalize(trim_left(message))
-
- if(message_mode)
- if(message_mode in radiochannels)
- if(ears && istype(ears,/obj/item/device/radio))
- ears.talk_into(src,sanitize(message), message_mode, verb, null)
-
-
- ..(message)
-
-
-/mob/living/simple_animal/parrot/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null)
- if(prob(50))
- parrot_hear(message)
- ..(message,verb,language,alt_name,italics,speaker)
-
-
-
-/mob/living/simple_animal/parrot/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/part_c, var/mob/speaker = null, var/hard_to_hear = 0)
- if(prob(50))
- parrot_hear("[pick(available_channels)] [message]")
- ..(message,verb,language,part_a,part_b,speaker,hard_to_hear)
-
-
-/mob/living/simple_animal/parrot/proc/parrot_hear(var/message="")
- if(!message || stat)
- return
- speech_buffer.Add(message)
-
-/mob/living/simple_animal/parrot/attack_generic(var/mob/user, var/damage, var/attack_message)
-
- var/success = ..()
-
- if(client)
- return success
-
- if(parrot_state == PARROT_PERCH)
- parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
-
- if(!success)
- return 0
-
- parrot_interest = user
- parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless
- icon_state = "parrot_fly"
+/* Parrots!
+ * Contains
+ * Defines
+ * Inventory (headset stuff)
+ * Attack responces
+ * AI
+ * Procs / Verbs (usable by players)
+ * Sub-types
+ */
+
+/*
+ * Defines
+ */
+
+//Parrot is too snowflake for me to rewrite right now, someone should make it use the new
+//simple_animal movement stuff. -Aro
+
+//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
+
+//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
+
+
+/mob/living/simple_animal/parrot
+ name = "\improper 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"
+
+ turns_per_move = 5
+ pass_flags = PASSTABLE
+ mob_size = MOB_SMALL
+
+ response_help = "pets"
+ response_disarm = "gently moves aside"
+ response_harm = "swats"
+ stop_automated_movement = 1
+ universal_speak = 1
+
+ speak_chance = 2
+ 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")
+
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/cracker
+
+ 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(BP_TORSO, BP_HEAD, BP_L_ARM, BP_R_ARM, BP_L_LEG, BP_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_been_shot = 0 //Parrots get a speed bonus after being shot. This will deincrement every Life() and at 0 the parrot will return to regular speed.
+
+ var/list/speech_buffer = list()
+ var/list/available_channels = list()
+
+ //Headset for Poly to yell at engineers :)
+ var/obj/item/device/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 pertch 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, /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/New()
+ ..()
+ if(!ears)
+ var/headset = pick(/obj/item/device/radio/headset/headset_sec, \
+ /obj/item/device/radio/headset/headset_eng, \
+ /obj/item/device/radio/headset/headset_med, \
+ /obj/item/device/radio/headset/headset_sci, \
+ /obj/item/device/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/death()
+ if(held_item)
+ held_item.forceMove(src.loc)
+ held_item = null
+ walk(src,0)
+ ..()
+
+/mob/living/simple_animal/parrot/Stat()
+ ..()
+ stat("Held Item", held_item)
+
+/*
+ * Inventory
+ */
+/mob/living/simple_animal/parrot/show_inv(mob/user as mob)
+ user.set_machine(src)
+ if(user.stat) return
+
+ var/dat = "
"
+ if(ears)
+ dat += "
Headset: [ears] (Remove)"
+ else
+ dat += "
Headset: Nothing"
+
+ user << browse(dat, text("window=mob[];size=325x500", name))
+ onclose(user, "mob[real_name]")
+ return
+
+/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))
+ return
+
+ //Is the usr's mob type able to do this?
+ if(ishuman(usr) || issmall(usr) || isrobot(usr))
+
+ //Removing from inventory
+ if(href_list["remove_inv"])
+ var/remove_from = href_list["remove_inv"]
+ switch(remove_from)
+ if("ears")
+ if(ears)
+ if(available_channels.len)
+ src.say("[pick(available_channels)] BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
+ else
+ src.say("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
+ ears.forceMove(src.loc)
+ ears = null
+ for(var/possible_phrase in speak)
+ if(copytext(possible_phrase,1,3) in department_radio_keys)
+ possible_phrase = copytext(possible_phrase,3,length(possible_phrase))
+ else
+ usr << "\red There is nothing to remove from its [remove_from]."
+ return
+
+ //Adding things to inventory
+ else if(href_list["add_inv"])
+ var/add_to = href_list["add_inv"]
+ if(!usr.get_active_hand())
+ usr << "\red You have nothing in your hand to put on its [add_to]."
+ return
+ switch(add_to)
+ if("ears")
+ if(ears)
+ usr << "\red It's already wearing something."
+ return
+ else
+ var/obj/item/item_to_add = usr.get_active_hand()
+ if(!item_to_add)
+ return
+
+ if( !istype(item_to_add, /obj/item/device/radio/headset) )
+ usr << "\red This object won't fit."
+ return
+
+ var/obj/item/device/radio/headset/headset_to_add = item_to_add
+
+ usr.drop_item()
+ headset_to_add.forceMove(src)
+ src.ears = headset_to_add
+ usr << "You fit the headset onto [src]."
+
+ clearlist(available_channels)
+ for(var/ch in headset_to_add.channels)
+ switch(ch)
+ if("Engineering")
+ available_channels.Add(":e")
+ if("Command")
+ available_channels.Add(":c")
+ if("Security")
+ available_channels.Add(":s")
+ if("Science")
+ available_channels.Add(":n")
+ if("Medical")
+ available_channels.Add(":m")
+ if("Mining")
+ available_channels.Add(":d")
+ if("Cargo")
+ available_channels.Add(":q")
+
+ if(headset_to_add.translate_binary)
+ available_channels.Add(":b")
+ else
+ ..()
+
+
+/*
+ * Attack responces
+ */
+//Humans, monkeys, aliens
+/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M as mob)
+ ..()
+ if(client) return
+ if(!stat && M.a_intent == I_HURT)
+
+ icon_state = "parrot_fly" //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(M.health < 50) //Weakened mob? Fight back!
+ parrot_state |= PARROT_ATTACK
+ else
+ parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell!
+ drop_held_item(0)
+ return
+
+//Mobs with objects
+/mob/living/simple_animal/parrot/attackby(var/obj/item/O as obj, var/mob/user as mob)
+ ..()
+ if(!stat && !client && !istype(O, /obj/item/stack/medical))
+ 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 | PARROT_FLEE
+ icon_state = "parrot_fly"
+ drop_held_item(0)
+ return
+
+//Bullets
+/mob/living/simple_animal/parrot/bullet_act(var/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 //OWFUCK, Been shot! RUN LIKE HELL!
+ parrot_been_shot += 5
+ icon_state = "parrot_fly"
+ drop_held_item(0)
+ return
+
+
+/*
+ * AI - Not really intelligent, but I'm calling it AI anyway.
+ */
+/mob/living/simple_animal/parrot/Life()
+ ..()
+
+ //Sprite and AI update for when a parrot gets pulled
+ if(pulledby && stat == CONSCIOUS)
+ icon_state = "parrot_fly"
+ if(!client)
+ parrot_state = PARROT_WANDER
+ return
+
+ if(client || stat)
+ return //Lets not force players or dead/incap parrots to move
+
+ 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)
+
+
+//-----SPEECH
+ /* Parrot speech mimickry!
+ Phrases that the parrot hears in mob/living/say() 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.
+ Then it clears the buffer to make sure they dont magically remember something from hours ago. */
+ if(speech_buffer.len && prob(10))
+ if(speak.len)
+ speak.Remove(pick(speak))
+
+ speak.Add(pick(speech_buffer))
+ clearlist(speech_buffer)
+
+
+//-----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 = "parrot_fly"
+ return
+ else
+ parrot_state = PARROT_WANDER
+ icon_state = "parrot_fly"
+ 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,3) in department_radio_keys)
+ possible_phrase = "[useradio?pick(available_channels):""] [copytext(possible_phrase,3,length(possible_phrase)+1)]" //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,3) in department_radio_keys)
+ possible_phrase = "[copytext(possible_phrase,3,length(possible_phrase)+1)]" //crop out the channel prefix
+ newspeak.Add(possible_phrase)
+ speak = newspeak
+
+ //Search for item to steal
+ parrot_interest = search_for_item()
+ if(parrot_interest)
+ visible_emote("looks in [parrot_interest]'s direction and takes flight")
+ parrot_state = PARROT_SWOOP | PARROT_STEAL
+ icon_state = "parrot_fly"
+ 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(cardinal))
+ 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
+ visible_emote("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(in_range(src, 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 the [held_item]!", "\blue You grab the [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)
+ 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(in_range(src, parrot_perch))
+ src.forceMove(parrot_perch.loc)
+ drop_held_item()
+ parrot_state = PARROT_PERCH
+ icon_state = "parrot_sit"
+ return
+
+ walk_to(src, parrot_perch, 1, 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
+
+ walk_away(src, parrot_interest, 1, parrot_speed-parrot_been_shot)
+ parrot_been_shot--
+ 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 the mob is close enough to interact with
+ if(in_range(src, 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
+
+ //Time for the hurt to begin!
+ 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)
+ visible_emote(pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons."))
+
+ else
+ L.adjustBruteLoss(damage)
+ visible_emote(pick("pecks at [L].", "claws [L]."))
+ return
+
+ //Otherwise, fly towards the mob!
+ else
+ 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)
+ parrot_interest = null
+ parrot_perch = null
+ drop_held_item()
+ parrot_state = PARROT_WANDER
+ return
+
+/*
+ * Procs
+ */
+
+/mob/living/simple_animal/parrot/movement_delay()
+ if(client && stat == CONSCIOUS && parrot_state != "parrot_fly")
+ icon_state = "parrot_fly"
+ ..()
+
+/mob/living/simple_animal/parrot/proc/search_for_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 < ITEMSIZE_SMALL)
+ return I
+
+ if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ if((C.l_hand && C.l_hand.w_class <= ITEMSIZE_SMALL) || (C.r_hand && C.r_hand.w_class <= ITEMSIZE_SMALL))
+ return C
+ 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 <= ITEMSIZE_SMALL)
+ return I
+
+ if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ if(C.l_hand && C.l_hand.w_class <= ITEMSIZE_SMALL || C.r_hand && C.r_hand.w_class <= ITEMSIZE_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)
+ src << "\red You are already holding the [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 <= ITEMSIZE_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 the [held_item]!", "\blue You grab the [held_item]!", "You hear the sounds of wings flapping furiously.")
+ return held_item
+
+ src << "\red 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)
+ src << "\red You are already holding the [held_item]"
+ return 1
+
+ var/obj/item/stolen_item = null
+
+ for(var/mob/living/carbon/C in view(1,src))
+ if(C.l_hand && C.l_hand.w_class <= ITEMSIZE_SMALL)
+ stolen_item = C.l_hand
+
+ if(C.r_hand && C.r_hand.w_class <= ITEMSIZE_SMALL)
+ stolen_item = C.r_hand
+
+ if(stolen_item)
+ C.remove_from_mob(stolen_item)
+ held_item = stolen_item
+ stolen_item.forceMove(src)
+ visible_message("[src] grabs the [held_item] out of [C]'s hand!", "\blue You snag the [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.")
+ return held_item
+
+ src << "\red 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(var/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)
+ usr << "\red You have nothing to drop!"
+ return 0
+
+ if(!drop_gently)
+ if(istype(held_item, /obj/item/weapon/grenade))
+ var/obj/item/weapon/grenade/G = held_item
+ G.forceMove(src.loc)
+ G.prime()
+ src << "You let go of the [held_item]!"
+ held_item = null
+ return 1
+
+ src << "You drop the [held_item]."
+
+ held_item.forceMove(src.loc)
+ 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 == "parrot_fly")
+ 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 = "parrot_sit"
+ return
+ src << "\red There is no perch nearby to sit on."
+ 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 singlo, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS FREE CALL THE SHUTTLE")
+
+/mob/living/simple_animal/parrot/Poly/New()
+ ears = new /obj/item/device/radio/headset/headset_eng(src)
+ available_channels = list(":e")
+ ..()
+
+/mob/living/simple_animal/parrot/say(var/message)
+
+ if(stat)
+ return
+
+ var/verb = "says"
+ if(speak_emote.len)
+ verb = pick(speak_emote)
+
+
+ var/message_mode=""
+ if(copytext(message,1,2) == ";")
+ message_mode = "headset"
+ message = copytext(message,2)
+
+ if(length(message) >= 2)
+ var/channel_prefix = copytext(message, 1 ,3)
+ message_mode = department_radio_keys[channel_prefix]
+
+ if(copytext(message,1,2) == ":")
+ var/positioncut = 3
+ message = trim(copytext(message,positioncut))
+
+ message = capitalize(trim_left(message))
+
+ if(message_mode)
+ if(message_mode in radiochannels)
+ if(ears && istype(ears,/obj/item/device/radio))
+ ears.talk_into(src,sanitize(message), message_mode, verb, null)
+
+
+ ..(message)
+
+
+/mob/living/simple_animal/parrot/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null)
+ if(prob(50))
+ parrot_hear(message)
+ ..(message,verb,language,alt_name,italics,speaker)
+
+
+
+/mob/living/simple_animal/parrot/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/part_c, var/mob/speaker = null, var/hard_to_hear = 0)
+ if(prob(50))
+ parrot_hear("[pick(available_channels)] [message]")
+ ..(message,verb,language,part_a,part_b,speaker,hard_to_hear)
+
+
+/mob/living/simple_animal/parrot/proc/parrot_hear(var/message="")
+ if(!message || stat)
+ return
+ speech_buffer.Add(message)
+
+/mob/living/simple_animal/parrot/attack_generic(var/mob/user, var/damage, var/attack_message)
+
+ var/success = ..()
+
+ if(client)
+ return success
+
+ if(parrot_state == PARROT_PERCH)
+ parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
+
+ if(!success)
+ return 0
+
+ parrot_interest = user
+ parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless
+ icon_state = "parrot_fly"
return success
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/friendly/penguin.dm b/code/modules/mob/living/simple_animal/animals/penguin.dm
similarity index 94%
rename from code/modules/mob/living/simple_animal/friendly/penguin.dm
rename to code/modules/mob/living/simple_animal/animals/penguin.dm
index b843013c396..40c085a72f7 100644
--- a/code/modules/mob/living/simple_animal/friendly/penguin.dm
+++ b/code/modules/mob/living/simple_animal/animals/penguin.dm
@@ -5,15 +5,21 @@
icon_living = "penguin"
icon_dead = "penguin_dead"
icon_gib = "generic_gib"
- speak_chance = 0
- turns_per_move = 5
+
maxHealth = 20
health = 20
- meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
+
+ turns_per_move = 5
+
response_help = "pets"
response_disarm = "pushes aside"
response_harm = "hits"
+
harm_intent_damage = 5
- melee_damage_upper = 15
melee_damage_lower = 10
- attacktext = "pecked"
\ No newline at end of file
+ melee_damage_upper = 15
+ attacktext = "pecked"
+
+ speak_chance = 0
+
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
diff --git a/code/modules/mob/living/simple_animal/friendly/slime.dm b/code/modules/mob/living/simple_animal/animals/slime.dm
similarity index 96%
rename from code/modules/mob/living/simple_animal/friendly/slime.dm
rename to code/modules/mob/living/simple_animal/animals/slime.dm
index 77d52741ae2..7d5fa16dab2 100644
--- a/code/modules/mob/living/simple_animal/friendly/slime.dm
+++ b/code/modules/mob/living/simple_animal/animals/slime.dm
@@ -1,59 +1,69 @@
-/mob/living/simple_animal/slime
- name = "pet slime"
- desc = "A lovable, domesticated slime."
- icon = 'icons/mob/slimes.dmi'
- icon_state = "grey baby slime"
- icon_living = "grey baby slime"
- icon_dead = "grey baby slime dead"
- speak_emote = list("chirps")
- health = 100
- maxHealth = 100
- response_help = "pets"
- response_disarm = "shoos"
- response_harm = "stomps on"
- emote_see = list("jiggles", "bounces in place")
- var/colour = "grey"
-
-/mob/living/simple_animal/slime/science
- name = "Kendrick"
- colour = "rainbow"
- icon_state = "rainbow baby slime"
- icon_living = "rainbow baby slime"
- icon_dead = "rainbow baby slime dead"
-
-/mob/living/simple_animal/slime/science/initialize()
- ..()
- overlays.Cut()
- overlays += "aslime-:33"
-
-/mob/living/simple_animal/adultslime
- name = "pet slime"
- desc = "A lovable, domesticated slime."
- icon = 'icons/mob/slimes.dmi'
- health = 200
- maxHealth = 200
- icon_state = "grey adult slime"
- icon_living = "grey adult slime"
- icon_dead = "grey baby slime dead"
- response_help = "pets"
- response_disarm = "shoos"
- response_harm = "stomps on"
- emote_see = list("jiggles", "bounces in place")
- var/colour = "grey"
-
-/mob/living/simple_animal/adultslime/New()
- ..()
- overlays += "aslime-:33"
-
-/mob/living/simple_animal/adultslime/death()
- var/mob/living/simple_animal/slime/S1 = new /mob/living/simple_animal/slime (src.loc)
- S1.icon_state = "[src.colour] baby slime"
- S1.icon_living = "[src.colour] baby slime"
- S1.icon_dead = "[src.colour] baby slime dead"
- S1.colour = "[src.colour]"
- var/mob/living/simple_animal/slime/S2 = new /mob/living/simple_animal/slime (src.loc)
- S2.icon_state = "[src.colour] baby slime"
- S2.icon_living = "[src.colour] baby slime"
- S2.icon_dead = "[src.colour] baby slime dead"
- S2.colour = "[src.colour]"
+/mob/living/simple_animal/slime
+ name = "pet slime"
+ desc = "A lovable, domesticated slime."
+ icon = 'icons/mob/slimes.dmi'
+ icon_state = "grey baby slime"
+ icon_living = "grey baby slime"
+ icon_dead = "grey baby slime dead"
+
+ maxHealth = 100
+ health = 100
+
+ response_help = "pets"
+ response_disarm = "shoos"
+ response_harm = "stomps on"
+
+ speak_chance = 1
+ speak_emote = list("chirps")
+ emote_see = list("jiggles", "bounces in place")
+
+ var/colour = "grey"
+
+/mob/living/simple_animal/slime/science
+ name = "Kendrick"
+ colour = "rainbow"
+ icon_state = "rainbow baby slime"
+ icon_living = "rainbow baby slime"
+ icon_dead = "rainbow baby slime dead"
+
+/mob/living/simple_animal/slime/science/initialize()
+ ..()
+ overlays.Cut()
+ overlays += "aslime-:33"
+
+/mob/living/simple_animal/adultslime
+ name = "pet slime"
+ desc = "A lovable, domesticated slime."
+ icon = 'icons/mob/slimes.dmi'
+ icon_state = "grey adult slime"
+ icon_living = "grey adult slime"
+ icon_dead = "grey baby slime dead"
+
+ maxHealth = 200
+ health = 200
+
+ response_help = "pets"
+ response_disarm = "shoos"
+ response_harm = "stomps on"
+
+ speak_chance = 1
+ emote_see = list("jiggles", "bounces in place")
+
+ var/colour = "grey"
+
+/mob/living/simple_animal/adultslime/New()
+ ..()
+ overlays += "aslime-:33"
+
+/mob/living/simple_animal/adultslime/death()
+ var/mob/living/simple_animal/slime/S1 = new /mob/living/simple_animal/slime (src.loc)
+ S1.icon_state = "[src.colour] baby slime"
+ S1.icon_living = "[src.colour] baby slime"
+ S1.icon_dead = "[src.colour] baby slime dead"
+ S1.colour = "[src.colour]"
+ var/mob/living/simple_animal/slime/S2 = new /mob/living/simple_animal/slime (src.loc)
+ S2.icon_state = "[src.colour] baby slime"
+ S2.icon_living = "[src.colour] baby slime"
+ S2.icon_dead = "[src.colour] baby slime dead"
+ S2.colour = "[src.colour]"
qdel(src)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm b/code/modules/mob/living/simple_animal/animals/spiderbot.dm
similarity index 97%
rename from code/modules/mob/living/simple_animal/friendly/spiderbot.dm
rename to code/modules/mob/living/simple_animal/animals/spiderbot.dm
index 6ef066a2574..1bb202d1a03 100644
--- a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
+++ b/code/modules/mob/living/simple_animal/animals/spiderbot.dm
@@ -1,11 +1,35 @@
/mob/living/simple_animal/spiderbot
+ name = "spider-bot"
+ desc = "A skittering robotic friend!"
+ icon = 'icons/mob/robots.dmi'
+ icon_state = "spiderbot-chassis"
+ icon_living = "spiderbot-chassis"
+ icon_dead = "spiderbot-smashed"
+
+ health = 10
+ maxHealth = 10
+
+ wander = 0
+ speed = -1 //Spiderbots gotta go fast.
+ pass_flags = PASSTABLE
+ mob_size = MOB_SMALL
+
+ response_help = "pets"
+ response_disarm = "shoos"
+ response_harm = "stomps on"
+
+ melee_damage_lower = 1
+ melee_damage_upper = 3
+ attacktext = "shocked"
min_oxy = 0
max_tox = 0
max_co2 = 0
minbodytemp = 0
maxbodytemp = 500
- mob_size = MOB_SMALL
+
+ speak_chance = 1
+ speak_emote = list("beeps","clicks","chirps")
var/obj/item/device/radio/borg/radio = null
var/mob/living/silicon/ai/connected_ai = null
@@ -15,31 +39,8 @@
var/list/req_access = list(access_robotics) //Access needed to pop out the brain.
var/positronic
- name = "spider-bot"
- desc = "A skittering robotic friend!"
- icon = 'icons/mob/robots.dmi'
- icon_state = "spiderbot-chassis"
- icon_living = "spiderbot-chassis"
- icon_dead = "spiderbot-smashed"
-
- wander = 0
-
- health = 10
- maxHealth = 10
-
- attacktext = "shocked"
- melee_damage_lower = 1
- melee_damage_upper = 3
-
- response_help = "pets"
- response_disarm = "shoos"
- response_harm = "stomps on"
-
var/emagged = 0
var/obj/item/held_item = null //Storage for single item they can hold.
- speed = -1 //Spiderbots gotta go fast.
- pass_flags = PASSTABLE
- speak_emote = list("beeps","clicks","chirps")
/mob/living/simple_animal/spiderbot/New()
..()
@@ -87,7 +88,7 @@
src.mmi = O
src.transfer_personality(O)
- O.loc = src
+ O.forceMove(src)
src.update_icon()
return 1
@@ -122,7 +123,7 @@
user << "You swipe your access card and pop the brain out of \the [src]."
eject_brain()
if(held_item)
- held_item.loc = src.loc
+ held_item.forceMove(src.loc)
held_item = null
return 1
else
@@ -171,7 +172,7 @@
if(mmi)
var/turf/T = get_turf(loc)
if(T)
- mmi.loc = T
+ mmi.forceMove(T)
if(mind) mind.transfer_to(mmi.brainmob)
mmi = null
real_name = initial(real_name)
@@ -201,7 +202,7 @@
if(camera)
camera.status = 0
- held_item.loc = src.loc
+ held_item.forceMove(src.loc)
held_item = null
gibs(loc, null, null, /obj/effect/gibspawner/robot) //TODO: use gib() or refactor spiderbots into synthetics.
@@ -226,7 +227,7 @@
"You launch \the [held_item]!", \
"You hear a skittering noise and a thump!")
var/obj/item/weapon/grenade/G = held_item
- G.loc = src.loc
+ G.forceMove(src.loc)
G.prime()
held_item = null
return 1
@@ -235,7 +236,7 @@
"You drop \the [held_item].", \
"You hear a skittering noise and a soft thump.")
- held_item.loc = src.loc
+ held_item.forceMove(src.loc)
held_item = null
return 1
@@ -264,7 +265,7 @@
for(var/obj/item/I in view(1, src))
if(selection == I)
held_item = selection
- selection.loc = src
+ selection.forceMove(src)
visible_message("\The [src] scoops up \the [held_item].", \
"You grab \the [held_item].", \
"You hear a skittering noise and a clink.")
@@ -284,4 +285,4 @@
return
/mob/living/simple_animal/spiderbot/binarycheck()
- return positronic
\ No newline at end of file
+ return positronic
diff --git a/code/modules/mob/living/simple_animal/friendly/tomato.dm b/code/modules/mob/living/simple_animal/animals/tomato.dm
similarity index 83%
rename from code/modules/mob/living/simple_animal/friendly/tomato.dm
rename to code/modules/mob/living/simple_animal/animals/tomato.dm
index e0574ba9254..ea133a85818 100644
--- a/code/modules/mob/living/simple_animal/friendly/tomato.dm
+++ b/code/modules/mob/living/simple_animal/animals/tomato.dm
@@ -1,18 +1,22 @@
-/mob/living/simple_animal/tomato
- name = "tomato"
- desc = "It's a horrifyingly enormous beef tomato, and it's packing extra beef!"
- icon_state = "tomato"
- icon_living = "tomato"
- icon_dead = "tomato_dead"
- speak_chance = 0
- turns_per_move = 5
- maxHealth = 15
- health = 15
- meat_type = /obj/item/weapon/reagent_containers/food/snacks/tomatomeat
- response_help = "prods"
- response_disarm = "pushes aside"
- response_harm = "smacks"
- harm_intent_damage = 5
- melee_damage_upper = 15
- melee_damage_lower = 10
- attacktext = "mauled"
\ No newline at end of file
+/mob/living/simple_animal/hostile/tomato
+ name = "tomato"
+ desc = "It's a horrifyingly enormous beef tomato, and it's packing extra beef!"
+ icon_state = "tomato"
+ icon_living = "tomato"
+ icon_dead = "tomato_dead"
+
+ faction = "plants"
+ maxHealth = 15
+ health = 15
+ turns_per_move = 5
+
+ response_help = "prods"
+ response_disarm = "pushes aside"
+ response_harm = "smacks"
+
+ harm_intent_damage = 5
+ melee_damage_upper = 15
+ melee_damage_lower = 10
+ attacktext = "mauled"
+
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/tomatomeat
diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/animals/tree.dm
similarity index 85%
rename from code/modules/mob/living/simple_animal/hostile/tree.dm
rename to code/modules/mob/living/simple_animal/animals/tree.dm
index 1c438d2bc8b..bf97641248e 100644
--- a/code/modules/mob/living/simple_animal/hostile/tree.dm
+++ b/code/modules/mob/living/simple_animal/animals/tree.dm
@@ -1,56 +1,57 @@
-/mob/living/simple_animal/hostile/tree
- name = "pine tree"
- desc = "A pissed off tree-like alien. It seems annoyed with the festivities..."
- icon = 'icons/obj/flora/pinetrees.dmi'
- icon_state = "pine_1"
- icon_living = "pine_1"
- icon_dead = "pine_1"
- icon_gib = "pine_1"
- speak_chance = 0
- turns_per_move = 5
- meat_type = /obj/item/weapon/reagent_containers/food/snacks/carpmeat
- response_help = "brushes"
- response_disarm = "pushes"
- response_harm = "hits"
- speed = -1
- maxHealth = 250
- health = 250
-
- pixel_x = -16
-
- harm_intent_damage = 5
- melee_damage_lower = 8
- melee_damage_upper = 12
- attacktext = "bitten"
- attack_sound = 'sound/weapons/bite.ogg'
-
- //Space carp aren't affected by atmos.
- min_oxy = 0
- max_oxy = 0
- min_tox = 0
- max_tox = 0
- min_co2 = 0
- max_co2 = 0
- min_n2 = 0
- max_n2 = 0
- minbodytemp = 0
-
- faction = "carp"
-
-/mob/living/simple_animal/hostile/tree/FindTarget()
- . = ..()
- if(.)
- audible_emote("growls at [.]")
-
-/mob/living/simple_animal/hostile/tree/AttackingTarget()
- . =..()
- var/mob/living/L = .
- if(istype(L))
- if(prob(15))
- L.Weaken(3)
- L.visible_message("\the [src] knocks down \the [L]!")
-
-/mob/living/simple_animal/hostile/tree/death()
- ..(null,"is hacked into pieces!")
- new /obj/item/stack/material/wood(loc)
+/mob/living/simple_animal/hostile/tree
+ name = "pine tree"
+ desc = "A pissed off tree-like alien. It seems annoyed with the festivities..."
+ icon = 'icons/obj/flora/pinetrees.dmi'
+ icon_state = "pine_1"
+ icon_living = "pine_1"
+ icon_dead = "pine_1"
+ icon_gib = "pine_1"
+
+ faction = "carp" //Trees can be carp friends?
+ maxHealth = 250
+ health = 250
+ speed = -1
+
+ turns_per_move = 5
+
+ response_help = "brushes"
+ response_disarm = "pushes"
+ response_harm = "hits"
+
+ harm_intent_damage = 5
+ melee_damage_lower = 8
+ melee_damage_upper = 12
+ attacktext = "bitten"
+ attack_sound = 'sound/weapons/bite.ogg'
+
+ min_oxy = 0
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 0
+ min_co2 = 0
+ max_co2 = 0
+ min_n2 = 0
+ max_n2 = 0
+ minbodytemp = 0
+
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/carpmeat
+
+ pixel_x = -16
+
+/mob/living/simple_animal/hostile/tree/FindTarget()
+ . = ..()
+ if(.)
+ audible_emote("growls at [.]")
+
+/mob/living/simple_animal/hostile/tree/PunchTarget()
+ . =..()
+ var/mob/living/L = .
+ if(istype(L))
+ if(prob(15))
+ L.Weaken(3)
+ L.visible_message("\the [src] knocks down \the [L]!")
+
+/mob/living/simple_animal/hostile/tree/death()
+ ..(null,"is hacked into pieces!")
+ new /obj/item/stack/material/wood(loc)
qdel(src)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/worm.dm b/code/modules/mob/living/simple_animal/animals/worm.dm
similarity index 95%
rename from code/modules/mob/living/simple_animal/worm.dm
rename to code/modules/mob/living/simple_animal/animals/worm.dm
index e8663339c10..dfb8a5a195b 100644
--- a/code/modules/mob/living/simple_animal/worm.dm
+++ b/code/modules/mob/living/simple_animal/animals/worm.dm
@@ -1,200 +1,198 @@
-/mob/living/simple_animal/space_worm
- name = "space worm segment"
- desc = "A part of a space worm."
- icon = 'icons/mob/animal.dmi'
- icon_state = "spaceworm"
- icon_living = "spaceworm"
- icon_dead = "spacewormdead"
- status_flags = 0
-
- speak_emote = list("transmits") //not supposed to be used under AI control
- emote_hear = list("transmits") //I'm just adding it so it doesn't runtime if controlled by player who speaks
-
- response_help = "touches"
- response_disarm = "flails at"
- response_harm = "punches the"
-
- harm_intent_damage = 2
-
- maxHealth = 30
- health = 30
-
- universal_speak =1
-
- stop_automated_movement = 1
- animate_movement = SYNC_STEPS
-
- minbodytemp = 0
- maxbodytemp = 350
- min_oxy = 0
- max_co2 = 0
- max_tox = 0
-
- a_intent = I_HURT //so they don't get pushed around
-
- environment_smash = 2
-
- speed = -1
-
- var/mob/living/simple_animal/space_worm/previous //next/previous segments, correspondingly
- var/mob/living/simple_animal/space_worm/next //head is the nextest segment
-
- var/stomachProcessProbability = 50
- var/digestionProbability = 20
- var/flatPlasmaValue = 5 //flat plasma amount given for non-items
-
- var/atom/currentlyEating //what the worm is currently eating
- var/eatingDuration = 0 //how long he's been eating it for
-
- head
- name = "space worm head"
- icon_state = "spacewormhead"
- icon_living = "spacewormhead"
- icon_dead = "spacewormdead"
-
- maxHealth = 20
- health = 20
-
- melee_damage_lower = 10
- melee_damage_upper = 15
- attacktext = "bitten"
-
- animate_movement = SLIDE_STEPS
-
- New(var/location, var/segments = 6)
- ..()
-
- var/mob/living/simple_animal/space_worm/current = src
-
- for(var/i = 1 to segments)
- var/mob/living/simple_animal/space_worm/newSegment = new /mob/living/simple_animal/space_worm(loc)
- current.Attach(newSegment)
- current = newSegment
-
- update_icon()
- if(stat == CONSCIOUS || stat == UNCONSCIOUS)
- icon_state = "spacewormhead[previous?1:0]"
- if(previous)
- set_dir(get_dir(previous,src))
- else
- icon_state = "spacewormheaddead"
-
- Life()
- ..()
-
- if(next && !(next in view(src,1)))
- Detach()
-
- if(stat == DEAD) //dead chunks fall off and die immediately
- if(previous)
- previous.Detach()
- if(next)
- Detach(1)
-
- if(prob(stomachProcessProbability))
- ProcessStomach()
-
- update_icon()
-
- return
-
- Destroy() //if a chunk a destroyed, make a new worm out of the split halves
- if(previous)
- previous.Detach()
- ..()
-
- Move()
- var/attachementNextPosition = loc
- if(..())
- if(previous)
- previous.Move(attachementNextPosition)
- update_icon()
-
- Bump(atom/obstacle)
- if(currentlyEating != obstacle)
- currentlyEating = obstacle
- eatingDuration = 0
-
- if(!AttemptToEat(obstacle))
- eatingDuration++
- else
- currentlyEating = null
- eatingDuration = 0
-
- return
-
- update_icon() //only for the sake of consistency with the other update icon procs
- if(stat == CONSCIOUS || stat == UNCONSCIOUS)
- if(previous) //midsection
- icon_state = "spaceworm[get_dir(src,previous) | get_dir(src,next)]" //see 3 lines below
- else //tail
- icon_state = "spacewormtail"
- set_dir(get_dir(src,next)) //next will always be present since it's not a head and if it's dead, it goes in the other if branch
- else
- icon_state = "spacewormdead"
-
- return
-
- proc/AttemptToEat(var/atom/target)
- if(istype(target,/turf/simulated/wall))
- var/turf/simulated/wall/W = target
- if((!W.reinf_material && eatingDuration >= 100) || eatingDuration >= 200) //need 20 ticks to eat an rwall, 10 for a regular one
- W.dismantle_wall()
- return 1
- else if(istype(target,/atom/movable))
- if(istype(target,/mob) || eatingDuration >= 50) //5 ticks to eat stuff like airlocks
- var/atom/movable/objectOrMob = target
- contents += objectOrMob
- return 1
-
- return 0
-
- proc/Attach(var/mob/living/simple_animal/space_worm/attachement)
- if(!attachement)
- return
-
- previous = attachement
- attachement.next = src
-
- return
-
- proc/Detach(die = 0)
- var/mob/living/simple_animal/space_worm/newHead = new /mob/living/simple_animal/space_worm/head(loc,0)
- var/mob/living/simple_animal/space_worm/newHeadPrevious = previous
-
- previous = null //so that no extra heads are spawned
-
- newHead.Attach(newHeadPrevious)
-
- if(die)
- newHead.death()
-
- qdel(src)
-
- proc/ProcessStomach()
- for(var/atom/movable/stomachContent in contents)
- if(prob(digestionProbability))
- if(istype(stomachContent,/obj/item/stack)) //converts to plasma, keeping the stack value
- if(!istype(stomachContent,/obj/item/stack/material/phoron))
- var/obj/item/stack/oldStack = stomachContent
- new /obj/item/stack/material/phoron(src, oldStack.get_amount())
- qdel(oldStack)
- continue
- else if(istype(stomachContent,/obj/item)) //converts to plasma, keeping the w_class
- var/obj/item/oldItem = stomachContent
- new /obj/item/stack/material/phoron(src, oldItem.w_class)
- qdel(oldItem)
- continue
- else
- new /obj/item/stack/material/phoron(src, flatPlasmaValue) //just flat amount
- qdel(stomachContent)
- continue
-
- if(previous)
- for(var/atom/movable/stomachContent in contents) //transfer it along the digestive tract
- previous.contents += stomachContent
- else
- for(var/atom/movable/stomachContent in contents) //or poop it out
- loc.contents += stomachContent
-
- return
+/mob/living/simple_animal/space_worm
+ name = "space worm segment"
+ desc = "A part of a space worm."
+ icon = 'icons/mob/animal.dmi'
+ icon_state = "spaceworm"
+ icon_living = "spaceworm"
+ icon_dead = "spacewormdead"
+
+
+ maxHealth = 30
+ health = 30
+ speed = -1
+
+ status_flags = 0
+ universal_speak = 1
+ stop_automated_movement = 1
+ wander = 0
+ animate_movement = SYNC_STEPS
+
+ response_help = "touches"
+ response_disarm = "flails at"
+ response_harm = "punches the"
+
+ harm_intent_damage = 2
+
+ minbodytemp = 0
+ maxbodytemp = 350
+ min_oxy = 0
+ max_co2 = 0
+ max_tox = 0
+
+ environment_smash = 2
+
+ speak_emote = list("transmits") //not supposed to be used under AI control
+ emote_hear = list("transmits") //I'm just adding it so it doesn't runtime if controlled by player who speaks
+
+ var/mob/living/simple_animal/space_worm/previous //next/previous segments, correspondingly
+ var/mob/living/simple_animal/space_worm/next //head is the nextest segment
+
+ var/stomachProcessProbability = 50
+ var/digestionProbability = 20
+ var/flatPlasmaValue = 5 //flat plasma amount given for non-items
+
+ var/atom/currentlyEating //what the worm is currently eating
+ var/eatingDuration = 0 //how long he's been eating it for
+
+ head
+ name = "space worm head"
+ icon_state = "spacewormhead"
+ icon_living = "spacewormhead"
+ icon_dead = "spacewormdead"
+
+ maxHealth = 20
+ health = 20
+
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ attacktext = "bitten"
+
+ animate_movement = SLIDE_STEPS
+
+ New(var/location, var/segments = 6)
+ ..()
+
+ var/mob/living/simple_animal/space_worm/current = src
+
+ for(var/i = 1 to segments)
+ var/mob/living/simple_animal/space_worm/newSegment = new /mob/living/simple_animal/space_worm(loc)
+ current.Attach(newSegment)
+ current = newSegment
+
+ update_icon()
+ if(stat == CONSCIOUS || stat == UNCONSCIOUS)
+ icon_state = "spacewormhead[previous?1:0]"
+ if(previous)
+ set_dir(get_dir(previous,src))
+ else
+ icon_state = "spacewormheaddead"
+
+ Life()
+ ..()
+
+ if(next && !(next in view(src,1)))
+ Detach()
+
+ if(stat == DEAD) //dead chunks fall off and die immediately
+ if(previous)
+ previous.Detach()
+ if(next)
+ Detach(1)
+
+ if(prob(stomachProcessProbability))
+ ProcessStomach()
+
+ update_icon()
+
+ return
+
+ Destroy() //if a chunk a destroyed, make a new worm out of the split halves
+ if(previous)
+ previous.Detach()
+ ..()
+
+ Move()
+ var/attachementNextPosition = loc
+ if(..())
+ if(previous)
+ previous.Move(attachementNextPosition)
+ update_icon()
+
+ Bump(atom/obstacle)
+ if(currentlyEating != obstacle)
+ currentlyEating = obstacle
+ eatingDuration = 0
+
+ if(!AttemptToEat(obstacle))
+ eatingDuration++
+ else
+ currentlyEating = null
+ eatingDuration = 0
+
+ return
+
+ update_icon() //only for the sake of consistency with the other update icon procs
+ if(stat == CONSCIOUS || stat == UNCONSCIOUS)
+ if(previous) //midsection
+ icon_state = "spaceworm[get_dir(src,previous) | get_dir(src,next)]" //see 3 lines below
+ else //tail
+ icon_state = "spacewormtail"
+ set_dir(get_dir(src,next)) //next will always be present since it's not a head and if it's dead, it goes in the other if branch
+ else
+ icon_state = "spacewormdead"
+
+ return
+
+ proc/AttemptToEat(var/atom/target)
+ if(istype(target,/turf/simulated/wall))
+ var/turf/simulated/wall/W = target
+ if((!W.reinf_material && eatingDuration >= 100) || eatingDuration >= 200) //need 20 ticks to eat an rwall, 10 for a regular one
+ W.dismantle_wall()
+ return 1
+ else if(istype(target,/atom/movable))
+ if(istype(target,/mob) || eatingDuration >= 50) //5 ticks to eat stuff like airlocks
+ var/atom/movable/objectOrMob = target
+ contents += objectOrMob
+ return 1
+
+ return 0
+
+ proc/Attach(var/mob/living/simple_animal/space_worm/attachement)
+ if(!attachement)
+ return
+
+ previous = attachement
+ attachement.next = src
+
+ return
+
+ proc/Detach(die = 0)
+ var/mob/living/simple_animal/space_worm/newHead = new /mob/living/simple_animal/space_worm/head(loc,0)
+ var/mob/living/simple_animal/space_worm/newHeadPrevious = previous
+
+ previous = null //so that no extra heads are spawned
+
+ newHead.Attach(newHeadPrevious)
+
+ if(die)
+ newHead.death()
+
+ qdel(src)
+
+ proc/ProcessStomach()
+ for(var/atom/movable/stomachContent in contents)
+ if(prob(digestionProbability))
+ if(istype(stomachContent,/obj/item/stack)) //converts to plasma, keeping the stack value
+ if(!istype(stomachContent,/obj/item/stack/material/phoron))
+ var/obj/item/stack/oldStack = stomachContent
+ new /obj/item/stack/material/phoron(src, oldStack.get_amount())
+ qdel(oldStack)
+ continue
+ else if(istype(stomachContent,/obj/item)) //converts to plasma, keeping the w_class
+ var/obj/item/oldItem = stomachContent
+ new /obj/item/stack/material/phoron(src, oldItem.w_class)
+ qdel(oldItem)
+ continue
+ else
+ new /obj/item/stack/material/phoron(src, flatPlasmaValue) //just flat amount
+ qdel(stomachContent)
+ continue
+
+ if(previous)
+ for(var/atom/movable/stomachContent in contents) //transfer it along the digestive tract
+ previous.contents += stomachContent
+ else
+ for(var/atom/movable/stomachContent in contents) //or poop it out
+ loc.contents += stomachContent
+
+ return
diff --git a/code/modules/mob/living/simple_animal/borer/borer.dm b/code/modules/mob/living/simple_animal/borer/borer.dm
index dfcefb28a18..f6a0fa3010d 100644
--- a/code/modules/mob/living/simple_animal/borer/borer.dm
+++ b/code/modules/mob/living/simple_animal/borer/borer.dm
@@ -158,7 +158,7 @@
if(host.mind)
borers.remove_antagonist(host.mind)
- src.loc = get_turf(host)
+ src.forceMove(get_turf(host))
reset_view(null)
machine = null
diff --git a/code/modules/mob/living/simple_animal/borer/borer_powers.dm b/code/modules/mob/living/simple_animal/borer/borer_powers.dm
index a95a22b948a..de4bfd4c5b6 100644
--- a/code/modules/mob/living/simple_animal/borer/borer_powers.dm
+++ b/code/modules/mob/living/simple_animal/borer/borer_powers.dm
@@ -105,7 +105,7 @@
src.host = M
src.host.status_flags |= PASSEMOTES
- src.loc = M
+ src.forceMove(M)
//Update their traitor status.
if(host.mind)
diff --git a/code/modules/mob/living/simple_animal/constructs/soulstone.dm b/code/modules/mob/living/simple_animal/constructs/soulstone.dm
index 08160f257f7..05087c40368 100644
--- a/code/modules/mob/living/simple_animal/constructs/soulstone.dm
+++ b/code/modules/mob/living/simple_animal/constructs/soulstone.dm
@@ -131,7 +131,7 @@
qdel(animation)
var/mob/living/simple_animal/shade/S = new /mob/living/simple_animal/shade( T.loc )
- S.loc = src //put shade in stone
+ S.forceMove(src) //put shade in stone
S.status_flags |= GODMODE //So they won't die inside the stone somehow
S.canmove = 0//Can't move out of the soul stone
S.name = "Shade of [T.real_name]"
@@ -167,7 +167,7 @@
U << "Capture failed!: The soul stone has already been imprinted with [src.imprinted]'s mind!"
return
- T.loc = src //put shade in stone
+ T.forceMove(src) //put shade in stone
T.status_flags |= GODMODE
T.canmove = 0
T.health = T.maxHealth
diff --git a/code/modules/mob/living/simple_animal/friendly/mushroom.dm b/code/modules/mob/living/simple_animal/friendly/mushroom.dm
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
deleted file mode 100644
index 52704d85cd9..00000000000
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ /dev/null
@@ -1,223 +0,0 @@
-
-#define SPINNING_WEB 1
-#define LAYING_EGGS 2
-#define MOVING_TO_TARGET 3
-#define SPINNING_COCOON 4
-
-//basic spider mob, these generally guard nests
-/mob/living/simple_animal/hostile/giant_spider
- name = "giant spider"
- desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes."
- icon_state = "guard"
- icon_living = "guard"
- icon_dead = "guard_dead"
- speak_emote = list("chitters")
- emote_hear = list("chitters")
- speak_chance = 5
- turns_per_move = 5
- see_in_dark = 10
- meat_type = /obj/item/weapon/reagent_containers/food/snacks/xenomeat
- response_help = "pets"
- response_disarm = "gently pushes aside"
- response_harm = "punches"
- stop_automated_movement_when_pulled = 0
- maxHealth = 200
- health = 200
- melee_damage_lower = 15
- melee_damage_upper = 20
- heat_damage_per_tick = 20
- cold_damage_per_tick = 20
- var/poison_per_bite = 5
- var/poison_chance = 10
- var/poison_type = "spidertoxin"
- faction = "spiders"
- var/busy = 0
- pass_flags = PASSTABLE
- move_to_delay = 6
- speed = 3
-
-//nursemaids - these create webs and eggs
-/mob/living/simple_animal/hostile/giant_spider/nurse
- desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes."
- icon_state = "nurse"
- icon_living = "nurse"
- icon_dead = "nurse_dead"
- maxHealth = 40
- health = 40
- melee_damage_lower = 5
- melee_damage_upper = 10
- poison_per_bite = 7
- var/atom/cocoon_target
- poison_type = "stoxin"
- var/fed = 0
-
-//hunters have the most poison and move the fastest, so they can find prey
-/mob/living/simple_animal/hostile/giant_spider/hunter
- desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes."
- icon_state = "hunter"
- icon_living = "hunter"
- icon_dead = "hunter_dead"
- maxHealth = 120
- health = 120
- melee_damage_lower = 10
- melee_damage_upper = 20
- poison_per_bite = 5
- move_to_delay = 4
-
-/mob/living/simple_animal/hostile/giant_spider/New(var/location, var/atom/parent)
- get_light_and_color(parent)
- ..()
-
-/mob/living/simple_animal/hostile/giant_spider/AttackingTarget()
- . = ..()
- if(isliving(.))
- var/mob/living/L = .
- if(L.reagents)
- L.reagents.add_reagent(poison_type, poison_per_bite)
- if(prob(poison_chance))
- L << "You feel a tiny prick."
- L.reagents.add_reagent(poison_type, poison_per_bite)
-
-/mob/living/simple_animal/hostile/giant_spider/nurse/AttackingTarget()
- . = ..()
- if(ishuman(.))
- var/mob/living/carbon/human/H = .
- if(prob(5))
- var/obj/item/organ/external/O = pick(H.organs)
- if(!(O.robotic >= ORGAN_ROBOT))
- var/eggcount
- for(var/obj/I in O.implants)
- if(istype(I, /obj/effect/spider/eggcluster))
- eggcount ++
- if(!eggcount)
- var/eggs = PoolOrNew(/obj/effect/spider/eggcluster/small, list(O, src))
- O.implants += eggs
- H << "The [src] injects something into your [O.name]!"
-
-/mob/living/simple_animal/hostile/giant_spider/Life()
- ..()
- if(!stat)
- if(stance == STANCE_IDLE)
- //1% chance to skitter madly away
- if(!busy && prob(1))
- /*var/list/move_targets = list()
- for(var/turf/T in orange(20, src))
- move_targets.Add(T)*/
- stop_automated_movement = 1
- walk_to(src, pick(orange(20, src)), 1, move_to_delay)
- spawn(50)
- stop_automated_movement = 0
- walk(src,0)
-
-/mob/living/simple_animal/hostile/giant_spider/nurse/proc/GiveUp(var/C)
- spawn(100)
- if(busy == MOVING_TO_TARGET)
- if(cocoon_target == C && get_dist(src,cocoon_target) > 1)
- cocoon_target = null
- busy = 0
- stop_automated_movement = 0
-
-/mob/living/simple_animal/hostile/giant_spider/nurse/Life()
- ..()
- if(!stat)
- if(stance == STANCE_IDLE)
- var/list/can_see = view(src, 10)
- //30% chance to stop wandering and do something
- if(!busy && prob(30))
- //first, check for potential food nearby to cocoon
- for(var/mob/living/C in can_see)
- if(C.stat)
- cocoon_target = C
- busy = MOVING_TO_TARGET
- walk_to(src, C, 1, move_to_delay)
- //give up if we can't reach them after 10 seconds
- GiveUp(C)
- return
-
- //second, spin a sticky spiderweb on this tile
- var/obj/effect/spider/stickyweb/W = locate() in get_turf(src)
- if(!W)
- busy = SPINNING_WEB
- src.visible_message("\The [src] begins to secrete a sticky substance.")
- stop_automated_movement = 1
- spawn(40)
- if(busy == SPINNING_WEB)
- new /obj/effect/spider/stickyweb(src.loc)
- busy = 0
- stop_automated_movement = 0
- else
- //third, lay an egg cluster there
- var/obj/effect/spider/eggcluster/E = locate() in get_turf(src)
- if(!E && fed > 0)
- busy = LAYING_EGGS
- src.visible_message("\The [src] begins to lay a cluster of eggs.")
- stop_automated_movement = 1
- spawn(50)
- if(busy == LAYING_EGGS)
- E = locate() in get_turf(src)
- if(!E)
- PoolOrNew(/obj/effect/spider/eggcluster, list(loc, src))
- fed--
- busy = 0
- stop_automated_movement = 0
- else
- //fourthly, cocoon any nearby items so those pesky pinkskins can't use them
- for(var/obj/O in can_see)
-
- if(O.anchored)
- continue
-
- if(istype(O, /obj/item) || istype(O, /obj/structure) || istype(O, /obj/machinery))
- cocoon_target = O
- busy = MOVING_TO_TARGET
- stop_automated_movement = 1
- walk_to(src, O, 1, move_to_delay)
- //give up if we can't reach them after 10 seconds
- GiveUp(O)
-
- else if(busy == MOVING_TO_TARGET && cocoon_target)
- if(get_dist(src, cocoon_target) <= 1)
- busy = SPINNING_COCOON
- src.visible_message("\The [src] begins to secrete a sticky substance around \the [cocoon_target].")
- stop_automated_movement = 1
- walk(src,0)
- spawn(50)
- if(busy == SPINNING_COCOON)
- if(cocoon_target && istype(cocoon_target.loc, /turf) && get_dist(src,cocoon_target) <= 1)
- var/obj/effect/spider/cocoon/C = new(cocoon_target.loc)
- var/large_cocoon = 0
- C.pixel_x = cocoon_target.pixel_x
- C.pixel_y = cocoon_target.pixel_y
- for(var/mob/living/M in C.loc)
- if(istype(M, /mob/living/simple_animal/hostile/giant_spider))
- continue
- large_cocoon = 1
- fed++
- src.visible_message("\The [src] sticks a proboscis into \the [cocoon_target] and sucks a viscous substance out.")
- M.loc = C
- C.pixel_x = M.pixel_x
- C.pixel_y = M.pixel_y
- break
- for(var/obj/item/I in C.loc)
- I.loc = C
- for(var/obj/structure/S in C.loc)
- if(!S.anchored)
- S.loc = C
- large_cocoon = 1
- for(var/obj/machinery/M in C.loc)
- if(!M.anchored)
- M.loc = C
- large_cocoon = 1
- if(large_cocoon)
- C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3")
- busy = 0
- stop_automated_movement = 0
-
- else
- busy = 0
- stop_automated_movement = 0
-
-#undef SPINNING_WEB
-#undef LAYING_EGGS
-#undef MOVING_TO_TARGET
-#undef SPINNING_COCOON
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
deleted file mode 100644
index 6269bd4634e..00000000000
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/mob/living/simple_animal/hostile
- faction = "hostile"
- break_stuff_probability = 10
- stop_automated_movement_when_pulled = 0
- destroy_surroundings = 1
- a_intent = I_HURT
- hostile = 1
-
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm
deleted file mode 100644
index ac352edd1a7..00000000000
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm
+++ /dev/null
@@ -1,49 +0,0 @@
-/mob/living/simple_animal/hostile/retaliate
- var/list/enemies = list()
-
-/mob/living/simple_animal/hostile/retaliate/Found(var/atom/A)
- if(isliving(A))
- var/mob/living/L = A
- if(!L.stat)
- stance = STANCE_ATTACK
- return L
- else
- enemies -= L
- else if(istype(A, /obj/mecha))
- var/obj/mecha/M = A
- if(M.occupant)
- stance = STANCE_ATTACK
- return A
-
-/mob/living/simple_animal/hostile/retaliate/ListTargets()
- if(!enemies.len)
- return list()
- var/list/see = ..()
- see &= enemies // Remove all entries that aren't in enemies
- return see
-
-/mob/living/simple_animal/hostile/retaliate/proc/Retaliate()
- ..()
- var/list/around = view(src, 7)
-
- for(var/atom/movable/A in around)
- if(A == src)
- continue
- if(isliving(A))
- var/mob/living/M = A
- if(!attack_same && M.faction != faction)
- enemies |= M
- else if(istype(A, /obj/mecha))
- var/obj/mecha/M = A
- if(M.occupant)
- enemies |= M
- enemies |= M.occupant
-
- for(var/mob/living/simple_animal/hostile/retaliate/H in around)
- if(!attack_same && !H.attack_same && H.faction == faction)
- H.enemies |= enemies
- return 0
-
-/mob/living/simple_animal/hostile/retaliate/adjustBruteLoss(var/damage)
- ..(damage)
- Retaliate()
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/simple_animal/humanoids/clown.dm
similarity index 84%
rename from code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
rename to code/modules/mob/living/simple_animal/humanoids/clown.dm
index 5a6a7e57688..5bf310b1655 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
+++ b/code/modules/mob/living/simple_animal/humanoids/clown.dm
@@ -1,39 +1,47 @@
-/mob/living/simple_animal/hostile/retaliate/clown
- name = "clown"
- desc = "A denizen of clown planet"
- icon_state = "clown"
- icon_living = "clown"
- icon_dead = "clown_dead"
- icon_gib = "clown_gib"
- speak_chance = 0
- turns_per_move = 5
- response_help = "pokes"
- response_disarm = "gently pushes aside"
- response_harm = "hits"
- speak = list("HONK", "Honk!", "Welcome to clown planet!")
- emote_see = list("honks")
- speak_chance = 1
- a_intent = I_HURT
- stop_automated_movement_when_pulled = 0
- maxHealth = 75
- health = 75
- speed = -1
- harm_intent_damage = 8
- melee_damage_lower = 10
- melee_damage_upper = 10
- attacktext = "attacked"
- attack_sound = 'sound/items/bikehorn.ogg'
-
- min_oxy = 5
- max_oxy = 0
- min_tox = 0
- max_tox = 1
- min_co2 = 0
- max_co2 = 5
- min_n2 = 0
- max_n2 = 0
- minbodytemp = 270
- maxbodytemp = 370
- heat_damage_per_tick = 15 //amount of damage applied if animal's body temperature is higher than maxbodytemp
- cold_damage_per_tick = 10 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
- unsuitable_atoms_damage = 10
+/mob/living/simple_animal/hostile/clown
+ name = "clown"
+ desc = "A denizen of clown planet"
+ icon_state = "clown"
+ icon_living = "clown"
+ icon_dead = "clown_dead"
+ icon_gib = "clown_gib"
+
+ faction = "clown"
+ maxHealth = 75
+ health = 75
+ speed = -1
+ move_to_delay = 2
+
+ run_at_them = 0
+ cooperative = 1
+
+ turns_per_move = 5
+ stop_when_pulled = 0
+
+ response_help = "pokes"
+ response_disarm = "gently pushes aside"
+ response_harm = "hits"
+
+ harm_intent_damage = 8
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ attacktext = "attacked"
+ attack_sound = 'sound/items/bikehorn.ogg'
+
+ min_oxy = 5
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 1
+ min_co2 = 0
+ max_co2 = 5
+ min_n2 = 0
+ max_n2 = 0
+ minbodytemp = 270
+ maxbodytemp = 370
+ heat_damage_per_tick = 15 //amount of damage applied if animal's body temperature is higher than maxbodytemp
+ cold_damage_per_tick = 10 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
+ unsuitable_atoms_damage = 10
+
+ speak_chance = 1
+ speak = list("HONK", "Honk!", "Welcome to clown planet!")
+ emote_see = list("honks")
diff --git a/code/modules/mob/living/simple_animal/head.dm b/code/modules/mob/living/simple_animal/humanoids/head.dm
similarity index 92%
rename from code/modules/mob/living/simple_animal/head.dm
rename to code/modules/mob/living/simple_animal/humanoids/head.dm
index 88a9f3d6cae..f0e8b8abde4 100644
--- a/code/modules/mob/living/simple_animal/head.dm
+++ b/code/modules/mob/living/simple_animal/humanoids/head.dm
@@ -5,16 +5,23 @@
icon_state = "crab"
icon_living = "crab"
icon_dead = "crab_dead"
- speak_emote = list("clicks")
- emote_hear = list("clicks")
- emote_see = list("clacks")
+
+ wander = 0
+ stop_automated_movement = 1
universal_speak = 1
- speak_chance = 1
turns_per_move = 5
- meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
+
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "punches"
+
+ speak_chance = 1
+ speak_emote = list("clicks")
+ emote_hear = list("clicks")
+ emote_see = list("clacks")
+
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
+
var/list/insults = list(
"Man you suck",
"You look like the most retarded douche around",
@@ -32,22 +39,15 @@
"Say \"what\" again. Say \"what\" again. I dare you. I double-dare you, motherfucker. Say \"what\" one more goddamn time.",
"Ezekiel 25:17 ,The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who in the name of charity and good will shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who attempt to poison and destroy my brothers. And you will know my name is the Lord... when I lay my vengeance upon thee.",
"Did you notice a sign out in front of my house that said \"Dead Nigger Storage\"?")
- stop_automated_movement = 1
/mob/living/simple_animal/head/Life()
- if(stat == DEAD)
- if(health > 0)
- icon_state = icon_living
- stat = CONSCIOUS
- density = 1
- return
- else if(health < 1)
- Die()
- else if(health > maxHealth)
- health = maxHealth
+ . = ..()
+ if(!. || ai_inactive) return
+
for(var/mob/A in viewers(world.view,src))
if(A.ckey)
say_something(A)
+
/mob/living/simple_animal/head/proc/say_something(mob/A)
if(prob(85))
return
diff --git a/code/modules/mob/living/simple_animal/kobold.dm b/code/modules/mob/living/simple_animal/humanoids/kobold.dm
similarity index 82%
rename from code/modules/mob/living/simple_animal/kobold.dm
rename to code/modules/mob/living/simple_animal/humanoids/kobold.dm
index 3e52a408747..8f3ce0d28c0 100644
--- a/code/modules/mob/living/simple_animal/kobold.dm
+++ b/code/modules/mob/living/simple_animal/humanoids/kobold.dm
@@ -6,25 +6,34 @@
icon_state = "kobold_idle"
icon_living = "kobold_idle"
icon_dead = "kobold_dead"
- //speak = list("You no take candle!","Ooh, pretty shiny.","Me take?","Where gold here...","Me likey.")
- speak_emote = list("mutters","hisses","grumbles")
- emote_hear = list("mutters under it's breath.","grumbles.", "yips!")
- emote_see = list("looks around suspiciously.", "scratches it's arm.","putters around a bit.")
- speak_chance = 15
+
+ run_at_them = 0
+ cooperative = 1
+
turns_per_move = 5
see_in_dark = 6
- meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/monkey
+
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
- minbodytemp = 250
+
min_oxy = 16 //Require atleast 16kPA oxygen
minbodytemp = 223 //Below -50 Degrees Celcius
maxbodytemp = 323 //Above 50 Degrees Celcius
+ speak_chance = 5
+ speak = list("You no take candle!","Ooh, pretty shiny.","Me take?","Where gold here...","Me likey.")
+ speak_emote = list("mutters","hisses","grumbles")
+ emote_hear = list("mutters under it's breath.","grumbles.", "yips!")
+ emote_see = list("looks around suspiciously.", "scratches it's arm.","putters around a bit.")
+
+ meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/monkey
+
/mob/living/simple_animal/kobold/Life()
- ..()
- if(prob(15) && turns_since_move && !stat)
+ . = ..()
+ if(!.) return
+
+ if(prob(5))
flick("kobold_act",src)
/mob/living/simple_animal/kobold/Move(var/dir)
diff --git a/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm b/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm
new file mode 100644
index 00000000000..79f389821e7
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/humanoids/mechamobs.dm
@@ -0,0 +1,94 @@
+/mob/living/simple_animal/hostile/mecha
+ name = "syndicate gygax"
+ desc = "Well that's forboding."
+ icon = 'icons/mecha/mecha.dmi'
+ icon_state = "darkgygax"
+ icon_living = "darkgygax"
+ icon_dead = "darkgygax-broken"
+
+ faction = "syndicate"
+ maxHealth = 300
+ health = 300
+ speed = 7
+ move_to_delay = 8
+
+ run_at_them = 0
+ cooperative = 1
+ investigates = 1
+ firing_lines = 1
+
+ turns_per_move = 5
+ stop_when_pulled = 0
+
+ response_help = "taps on"
+ response_disarm = "knocks on"
+ response_harm = "uselessly hits"
+
+ harm_intent_damage = 0
+ melee_damage_lower = 35
+ melee_damage_upper = 35
+ attacktext = "slashed"
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+
+ min_oxy = 0
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 0
+ min_co2 = 0
+ max_co2 = 0
+ min_n2 = 0
+ max_n2 = 0
+ unsuitable_atoms_damage = 0
+
+ ranged = 1
+ rapid = 1
+ projectiletype = /obj/item/projectile/beam
+ projectilesound = 'sound/weapons/laser.ogg'
+
+ speak_chance = 1
+ speak = list("Know what we need? More meatshields.",
+ "Glad I finally got a mech.",
+ "I'll stomp those NanoTrasen dogs into paste.",
+ "Glad I didn't become a line chef.",
+ "This anti-fog visor is nice...",
+ "Did I refill the air tank?")
+ emote_hear = list("humms ominously","whirrs softly","grinds a gear")
+ emote_see = list("looks around the area","turns from side to side")
+ say_understood = list()
+ say_cannot = list()
+ say_maybe_target = list("Just my sensors?","Detecting something?","Is that...?","What was that?")
+ say_got_target = list("ENGAGING!!!","CONTACT!!!","TARGET SPOTTED!","FOUND ONE!")
+ reactions = list()
+
+ var/datum/effect/effect/system/spark_spread/sparks
+ var/wreckage = /obj/effect/decal/mecha_wreckage/gygax/dark
+
+/mob/living/simple_animal/hostile/mecha/New()
+ ..()
+ sparks = new (src)
+ sparks.set_up(3, 1, src)
+
+/mob/living/simple_animal/hostile/mecha/Destroy()
+ qdel(sparks)
+ ..()
+
+/mob/living/simple_animal/hostile/mecha/Life()
+ . = ..()
+ if(!.) return
+ if((health < maxHealth*0.3) && prob(10))
+ sparks.start()
+
+/mob/living/simple_animal/hostile/mecha/bullet_act()
+ ..()
+ sparks.start()
+
+/mob/living/simple_animal/hostile/mecha/death()
+ ..(0,"is explodes!")
+ sparks.start()
+ explosion(get_turf(src), 0, 0, 1, 3)
+ qdel(src)
+ new /obj/effect/decal/mecha_wreckage/gygax/dark(get_turf(src))
+
+/mob/living/simple_animal/hostile/mecha/Move()
+ ..()
+ playsound(src,'sound/mecha/mechstep.ogg',40,1)
diff --git a/code/modules/mob/living/simple_animal/hostile/pirate.dm b/code/modules/mob/living/simple_animal/humanoids/pirate.dm
similarity index 80%
rename from code/modules/mob/living/simple_animal/hostile/pirate.dm
rename to code/modules/mob/living/simple_animal/humanoids/pirate.dm
index 3fbbe40aa63..02d1a8eb72b 100644
--- a/code/modules/mob/living/simple_animal/hostile/pirate.dm
+++ b/code/modules/mob/living/simple_animal/humanoids/pirate.dm
@@ -1,56 +1,66 @@
-/mob/living/simple_animal/hostile/pirate
- name = "Pirate"
- desc = "Does what he wants cause a pirate is free."
- icon_state = "piratemelee"
- icon_living = "piratemelee"
- icon_dead = "piratemelee_dead"
- speak_chance = 0
- turns_per_move = 5
- response_help = "pushes"
- response_disarm = "shoves"
- response_harm = "hits"
- speed = 4
- stop_automated_movement_when_pulled = 0
- maxHealth = 100
- health = 100
-
- harm_intent_damage = 5
- melee_damage_lower = 30
- melee_damage_upper = 30
- attacktext = "slashed"
- attack_sound = 'sound/weapons/bladeslice.ogg'
-
- min_oxy = 5
- max_oxy = 0
- min_tox = 0
- max_tox = 1
- min_co2 = 0
- max_co2 = 5
- min_n2 = 0
- max_n2 = 0
- unsuitable_atoms_damage = 15
- var/corpse = /obj/effect/landmark/mobcorpse/pirate
- var/weapon1 = /obj/item/weapon/melee/energy/sword/pirate
-
- faction = "pirate"
-
-/mob/living/simple_animal/hostile/pirate/ranged
- name = "Pirate Gunner"
- icon_state = "pirateranged"
- icon_living = "pirateranged"
- icon_dead = "piratemelee_dead"
- projectilesound = 'sound/weapons/laser.ogg'
- ranged = 1
- projectiletype = /obj/item/projectile/beam
- corpse = /obj/effect/landmark/mobcorpse/pirate/ranged
- weapon1 = /obj/item/weapon/gun/energy/laser
-
-
-/mob/living/simple_animal/hostile/pirate/death()
- ..()
- if(corpse)
- new corpse (src.loc)
- if(weapon1)
- new weapon1 (src.loc)
- qdel(src)
+/mob/living/simple_animal/hostile/pirate
+ name = "Pirate"
+ desc = "Does what he wants cause a pirate is free."
+ icon_state = "piratemelee"
+ icon_living = "piratemelee"
+ icon_dead = "piratemelee_dead"
+
+ faction = "pirate"
+ maxHealth = 100
+ health = 100
+ speed = 4
+
+ run_at_them = 0
+ cooperative = 1
+ investigates = 1
+ firing_lines = 1
+ returns_home = 1
+ reacts = 1
+
+ turns_per_move = 5
+ stop_when_pulled = 0
+
+ response_help = "pushes"
+ response_disarm = "shoves"
+ response_harm = "hits"
+
+ harm_intent_damage = 5
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ attacktext = "slashed"
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+
+ min_oxy = 5
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 1
+ min_co2 = 0
+ max_co2 = 5
+ min_n2 = 0
+ max_n2 = 0
+ unsuitable_atoms_damage = 15
+
+ loot_list = list(/obj/item/weapon/melee/energy/sword/pirate = 100)
+
+ var/corpse = /obj/effect/landmark/mobcorpse/pirate
+
+/mob/living/simple_animal/hostile/pirate/ranged
+ name = "Pirate Gunner"
+ icon_state = "pirateranged"
+ icon_living = "pirateranged"
+ icon_dead = "piratemelee_dead"
+
+ ranged = 1
+ projectiletype = /obj/item/projectile/beam
+ projectilesound = 'sound/weapons/laser.ogg'
+
+ loot_list = list(/obj/item/weapon/gun/energy/laser = 100)
+
+ corpse = /obj/effect/landmark/mobcorpse/pirate/ranged
+
+/mob/living/simple_animal/hostile/pirate/death()
+ ..()
+ if(corpse)
+ new corpse (src.loc)
+ qdel(src)
return
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/russian.dm b/code/modules/mob/living/simple_animal/humanoids/russian.dm
similarity index 79%
rename from code/modules/mob/living/simple_animal/hostile/russian.dm
rename to code/modules/mob/living/simple_animal/humanoids/russian.dm
index ee99318c153..96ff3a5177d 100644
--- a/code/modules/mob/living/simple_animal/hostile/russian.dm
+++ b/code/modules/mob/living/simple_animal/humanoids/russian.dm
@@ -1,55 +1,66 @@
-/mob/living/simple_animal/hostile/russian
- name = "russian"
- desc = "For the Motherland!"
- icon_state = "russianmelee"
- icon_living = "russianmelee"
- icon_dead = "russianmelee_dead"
- icon_gib = "syndicate_gib"
- speak_chance = 0
- turns_per_move = 5
- response_help = "pokes"
- response_disarm = "shoves"
- response_harm = "hits"
- speed = 4
- stop_automated_movement_when_pulled = 0
- maxHealth = 100
- health = 100
- harm_intent_damage = 5
- melee_damage_lower = 15
- melee_damage_upper = 15
- attacktext = "punched"
- a_intent = I_HURT
- var/corpse = /obj/effect/landmark/mobcorpse/russian
- var/weapon1 = /obj/item/weapon/material/knife
- min_oxy = 5
- max_oxy = 0
- min_tox = 0
- max_tox = 1
- min_co2 = 0
- max_co2 = 5
- min_n2 = 0
- max_n2 = 0
- unsuitable_atoms_damage = 15
- faction = "russian"
- status_flags = CANPUSH
-
-
-/mob/living/simple_animal/hostile/russian/ranged
- icon_state = "russianranged"
- icon_living = "russianranged"
- corpse = /obj/effect/landmark/mobcorpse/russian/ranged
- weapon1 = /obj/item/weapon/gun/projectile/revolver/mateba
- ranged = 1
- projectiletype = /obj/item/projectile/bullet
- projectilesound = 'sound/weapons/Gunshot.ogg'
- casingtype = /obj/item/ammo_casing/spent
-
-
-/mob/living/simple_animal/hostile/russian/death()
- ..()
- if(corpse)
- new corpse (src.loc)
- if(weapon1)
- new weapon1 (src.loc)
- qdel(src)
- return
\ No newline at end of file
+/mob/living/simple_animal/hostile/russian
+ name = "russian"
+ desc = "For the Motherland!"
+ icon_state = "russianmelee"
+ icon_living = "russianmelee"
+ icon_dead = "russianmelee_dead"
+ icon_gib = "syndicate_gib"
+
+ faction = "russian"
+ maxHealth = 100
+ health = 100
+ speed = 4
+
+ run_at_them = 0
+ cooperative = 1
+ investigates = 1
+ firing_lines = 1
+ returns_home = 1
+ reacts = 1
+
+ turns_per_move = 5
+ stop_when_pulled = 0
+ status_flags = CANPUSH
+
+ response_help = "pokes"
+ response_disarm = "shoves"
+ response_harm = "hits"
+
+ harm_intent_damage = 5
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ attacktext = "punched"
+
+ min_oxy = 5
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 1
+ min_co2 = 0
+ max_co2 = 5
+ min_n2 = 0
+ max_n2 = 0
+ unsuitable_atoms_damage = 15
+
+ loot_list = list(/obj/item/weapon/material/knife = 100)
+
+ var/corpse = /obj/effect/landmark/mobcorpse/russian
+
+/mob/living/simple_animal/hostile/russian/ranged
+ icon_state = "russianranged"
+ icon_living = "russianranged"
+
+ ranged = 1
+ projectiletype = /obj/item/projectile/bullet
+ casingtype = /obj/item/ammo_casing/spent
+ projectilesound = 'sound/weapons/Gunshot.ogg'
+
+ loot_list = list(/obj/item/weapon/gun/projectile/revolver/mateba = 100)
+
+ corpse = /obj/effect/landmark/mobcorpse/russian/ranged
+
+/mob/living/simple_animal/hostile/russian/death()
+ ..()
+ if(corpse)
+ new corpse (src.loc)
+ qdel(src)
+ return
diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/humanoids/syndicate.dm
similarity index 67%
rename from code/modules/mob/living/simple_animal/hostile/syndicate.dm
rename to code/modules/mob/living/simple_animal/humanoids/syndicate.dm
index 694e95b4340..eebc6c9571b 100644
--- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm
+++ b/code/modules/mob/living/simple_animal/humanoids/syndicate.dm
@@ -1,163 +1,192 @@
-/mob/living/simple_animal/hostile/syndicate
- name = "\improper Syndicate operative"
- desc = "Death to the Company."
- icon_state = "syndicate"
- icon_living = "syndicate"
- icon_dead = "syndicate_dead"
- icon_gib = "syndicate_gib"
- speak_chance = 0
- turns_per_move = 5
- response_help = "pokes"
- response_disarm = "shoves"
- response_harm = "hits"
- speed = 4
- stop_automated_movement_when_pulled = 0
- maxHealth = 100
- health = 100
- harm_intent_damage = 5
- melee_damage_lower = 10
- melee_damage_upper = 10
- attacktext = "punched"
- a_intent = I_HURT
- var/corpse = /obj/effect/landmark/mobcorpse/syndicatesoldier
- var/weapon1
- var/weapon2
- min_oxy = 5
- max_oxy = 0
- min_tox = 0
- max_tox = 1
- min_co2 = 0
- max_co2 = 5
- min_n2 = 0
- max_n2 = 0
- unsuitable_atoms_damage = 15
- environment_smash = 1
- faction = "syndicate"
- status_flags = CANPUSH
-
-/mob/living/simple_animal/hostile/syndicate/death()
- ..()
- if(corpse)
- new corpse (src.loc)
- if(weapon1)
- new weapon1 (src.loc)
- if(weapon2)
- new weapon2 (src.loc)
- qdel(src)
- return
-
-///////////////Sword and shield////////////
-
-/mob/living/simple_animal/hostile/syndicate/melee
- melee_damage_lower = 20
- melee_damage_upper = 25
- icon_state = "syndicatemelee"
- icon_living = "syndicatemelee"
- weapon1 = /obj/item/weapon/melee/energy/sword/red
- weapon2 = /obj/item/weapon/shield/energy
- attacktext = "slashed"
- status_flags = 0
-
-/mob/living/simple_animal/hostile/syndicate/melee/attackby(var/obj/item/O as obj, var/mob/user as mob)
- if(O.force)
- if(prob(80))
- var/damage = O.force
- if (O.damtype == HALLOSS)
- damage = 0
- health -= damage
- visible_message("\red \b [src] has been attacked with the [O] by [user]. ")
- else
- visible_message("\red \b [src] blocks the [O] with its shield! ")
- //user.do_attack_animation(src)
- else
- usr << "\red This weapon is ineffective, it does no damage."
- visible_message("\red [user] gently taps [src] with the [O]. ")
-
-
-/mob/living/simple_animal/hostile/syndicate/melee/bullet_act(var/obj/item/projectile/Proj)
- if(!Proj) return
- if(prob(65))
- src.health -= Proj.damage
- else
- visible_message("\red [src] blocks [Proj] with its shield!")
- return 0
-
-
-/mob/living/simple_animal/hostile/syndicate/melee/space
- min_oxy = 0
- max_oxy = 0
- min_tox = 0
- max_tox = 0
- min_co2 = 0
- max_co2 = 0
- min_n2 = 0
- max_n2 = 0
- minbodytemp = 0
- icon_state = "syndicatemeleespace"
- icon_living = "syndicatemeleespace"
- name = "Syndicate Commando"
- corpse = /obj/effect/landmark/mobcorpse/syndicatecommando
- speed = 0
-
-/mob/living/simple_animal/hostile/syndicate/melee/space/Process_Spacemove(var/check_drift = 0)
- return
-
-/mob/living/simple_animal/hostile/syndicate/ranged
- ranged = 1
- rapid = 1
- icon_state = "syndicateranged"
- icon_living = "syndicateranged"
- casingtype = /obj/item/ammo_casing/spent
- projectilesound = 'sound/weapons/Gunshot_light.ogg'
- projectiletype = /obj/item/projectile/bullet/pistol/medium
-
- weapon1 = /obj/item/weapon/gun/projectile/automatic/c20r
-
-/mob/living/simple_animal/hostile/syndicate/ranged/space
- icon_state = "syndicaterangedpsace"
- icon_living = "syndicaterangedpsace"
- name = "Syndicate Commando"
- min_oxy = 0
- max_oxy = 0
- min_tox = 0
- max_tox = 0
- min_co2 = 0
- max_co2 = 0
- min_n2 = 0
- max_n2 = 0
- minbodytemp = 0
- corpse = /obj/effect/landmark/mobcorpse/syndicatecommando
- speed = 0
-
-/mob/living/simple_animal/hostile/syndicate/ranged/space/Process_Spacemove(var/check_drift = 0)
- return
-
-
-
-/mob/living/simple_animal/hostile/viscerator
- name = "viscerator"
- desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations."
- icon = 'icons/mob/critter.dmi'
- icon_state = "viscerator_attack"
- icon_living = "viscerator_attack"
- pass_flags = PASSTABLE
- health = 15
- maxHealth = 15
- melee_damage_lower = 15
- melee_damage_upper = 15
- attacktext = "cut"
- attack_sound = 'sound/weapons/bladeslice.ogg'
- faction = "syndicate"
- min_oxy = 0
- max_oxy = 0
- min_tox = 0
- max_tox = 0
- min_co2 = 0
- max_co2 = 0
- min_n2 = 0
- max_n2 = 0
- minbodytemp = 0
-
-/mob/living/simple_animal/hostile/viscerator/death()
- ..(null,"is smashed into pieces!")
- qdel(src)
+/mob/living/simple_animal/hostile/syndicate
+ name = "syndicate operative"
+ desc = "Death to the Company."
+ icon_state = "syndicate"
+ icon_living = "syndicate"
+ icon_dead = "syndicate_dead"
+ icon_gib = "syndicate_gib"
+
+ faction = "syndicate"
+ maxHealth = 100
+ health = 100
+ speed = 4
+
+ run_at_them = 0
+ cooperative = 1
+ investigates = 1
+ firing_lines = 1
+ returns_home = 1
+ reacts = 1
+
+ turns_per_move = 5
+ stop_when_pulled = 0
+ status_flags = CANPUSH
+
+ response_help = "pokes"
+ response_disarm = "shoves"
+ response_harm = "hits"
+
+ harm_intent_damage = 5
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ environment_smash = 1
+ attacktext = "punched"
+
+ min_oxy = 5
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 1
+ min_co2 = 0
+ max_co2 = 5
+ min_n2 = 0
+ max_n2 = 0
+ unsuitable_atoms_damage = 15
+
+ speak_chance = 1
+ speak = list("Fuckin' NT, man.",
+ "When are we gonna get out of this chicken-shit outfit?",
+ "Wish I had better equipment...",
+ "I knew I should have been a line chef...",
+ "Fuckin' helmet keeps fogging up.",
+ "Anyone else smell that?")
+ emote_hear = list("sniffs","coughs","taps his foot")
+ emote_see = list("looks around","checks his equipment")
+ say_understood = list()
+ say_cannot = list()
+ say_maybe_target = list("What's that?","Is someone there?","Is that...?","Hmm?")
+ say_got_target = list("ENGAGING!!!","CONTACT!!!","TARGET SPOTTED!","FOUND ONE!")
+ reactions = list("Hey guys, you ready?" = "Fuck yeah!")
+
+ var/corpse = /obj/effect/landmark/mobcorpse/syndicatesoldier
+
+/mob/living/simple_animal/hostile/syndicate/death()
+ ..()
+ if(corpse)
+ new corpse (src.loc)
+ qdel(src)
+ return
+
+///////////////Sword and shield////////////
+
+/mob/living/simple_animal/hostile/syndicate/melee
+ icon_state = "syndicatemelee"
+ icon_living = "syndicatemelee"
+
+ melee_damage_lower = 20
+ melee_damage_upper = 25
+ attacktext = "slashed"
+
+ status_flags = 0
+
+ loot_list = list(/obj/item/weapon/melee/energy/sword/red = 100, /obj/item/weapon/shield/energy = 100)
+
+/mob/living/simple_animal/hostile/syndicate/melee/attackby(var/obj/item/O as obj, var/mob/user as mob)
+ if(O.force)
+ if(prob(20))
+ visible_message("\The [src] blocks \the [O] with its shield!")
+ if(user)
+ react_to_attack(user)
+ return
+ else
+ ..()
+ else
+ usr << "This weapon is ineffective, it does no damage."
+ visible_message("\The [user] gently taps [src] with \the [O].")
+
+/mob/living/simple_animal/hostile/syndicate/melee/bullet_act(var/obj/item/projectile/Proj)
+ if(!Proj) return
+ if(prob(35))
+ visible_message("\red [src] blocks [Proj] with its shield!")
+ if(Proj.firer)
+ react_to_attack(Proj.firer)
+ return
+ else
+ ..()
+
+/mob/living/simple_animal/hostile/syndicate/melee/space
+ name = "syndicate commando"
+ icon_state = "syndicatemeleespace"
+ icon_living = "syndicatemeleespace"
+
+ speed = 0
+
+ min_oxy = 0
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 0
+ min_co2 = 0
+ max_co2 = 0
+ min_n2 = 0
+ max_n2 = 0
+ minbodytemp = 0
+
+ corpse = /obj/effect/landmark/mobcorpse/syndicatecommando
+
+/mob/living/simple_animal/hostile/syndicate/melee/space/Process_Spacemove(var/check_drift = 0)
+ return
+
+/mob/living/simple_animal/hostile/syndicate/ranged
+ icon_state = "syndicateranged"
+ icon_living = "syndicateranged"
+
+ ranged = 1
+ rapid = 1
+ projectiletype = /obj/item/projectile/bullet/pistol/medium
+ casingtype = /obj/item/ammo_casing/spent
+ projectilesound = 'sound/weapons/Gunshot_light.ogg'
+
+ loot_list = list(/obj/item/weapon/gun/projectile/automatic/c20r = 100)
+
+/mob/living/simple_animal/hostile/syndicate/ranged/space
+ name = "syndicate sommando"
+ icon_state = "syndicaterangedpsace"
+ icon_living = "syndicaterangedpsace"
+
+ speed = 0
+
+ min_oxy = 0
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 0
+ min_co2 = 0
+ max_co2 = 0
+ min_n2 = 0
+ max_n2 = 0
+ minbodytemp = 0
+
+ corpse = /obj/effect/landmark/mobcorpse/syndicatecommando
+
+/mob/living/simple_animal/hostile/syndicate/ranged/space/Process_Spacemove(var/check_drift = 0)
+ return
+
+/mob/living/simple_animal/hostile/viscerator
+ name = "viscerator"
+ desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations."
+ icon = 'icons/mob/critter.dmi'
+ icon_state = "viscerator_attack"
+ icon_living = "viscerator_attack"
+
+ faction = "syndicate"
+ maxHealth = 15
+ health = 15
+
+ pass_flags = PASSTABLE
+
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+ attacktext = "cut"
+
+ min_oxy = 0
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 0
+ min_co2 = 0
+ max_co2 = 0
+ min_n2 = 0
+ max_n2 = 0
+ minbodytemp = 0
+
+/mob/living/simple_animal/hostile/viscerator/death()
+ ..(null,"is smashed into pieces!")
+ qdel(src)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 44d17e9212b..1179dc889f2 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -1,3 +1,8 @@
+#define ai_log(M,V) if(debug_ai) ai_log_output(M,V)
+
+//Talky things
+#define try_say_list(L) if(L.len) say(pick(L))
+
/mob/living/simple_animal
name = "animal"
icon = 'icons/mob/animal.dmi'
@@ -5,84 +10,114 @@
maxHealth = 20
mob_bump_flag = SIMPLE_ANIMAL
- mob_swap_flags = MONKEY|SLIME|SIMPLE_ANIMAL
- mob_push_flags = MONKEY|SLIME|SIMPLE_ANIMAL
+ mob_swap_flags = MONKEY|SLIME|HUMAN
+ mob_push_flags = MONKEY|SLIME|HUMAN
- var/show_stat_health = 1 //does the percentage health show in the stat panel for the mob
+ //Settings for played mobs
+ var/show_stat_health = 1 // Does the percentage health show in the stat panel for the mob
+ var/ai_inactive = 0 // Set to 1 to turn off most AI actions
- var/icon_living = ""
- var/icon_dead = ""
- var/icon_gib = null //We only try to show a gibbing animation if this exists.
+ //Mob icon settings
+ var/icon_living = "" // The iconstate if we're alive, required
+ var/icon_dead = "" // The iconstate if we're dead, required
+ var/icon_gib = null // The iconstate for being gibbed, optional
+ var/icon_rest = null // The iconstate for resting, optional
- var/list/speak = list()
- var/speak_chance = 0
- var/list/emote_hear = list() //Hearable emotes
- var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps
+ //Mob talking settings
+ universal_speak = 0 // Can all mobs in the entire universe understand this one?
+ var/has_langs = list(LANGUAGE_GALCOM)// Text name of their language if they speak something other than galcom. They speak the first one.
+ var/speak_chance = 0 // Probability that I talk (this is 'X in 200' chance since even 1/100 is pretty noisy)
+ var/reacts = 0 // Reacts to some things being said
+ var/list/speak = list() // Things I might say if I talk
+ var/list/emote_hear = list() // Hearable emotes I might perform
+ var/list/emote_see = list() // Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps
+ var/list/say_understood = list()// List of things to say when accepting an order
+ var/list/say_cannot = list() // List of things to say when they cannot comply
+ var/list/say_maybe_target = list()// List of things to say when they spot something barely
+ var/list/say_got_target = list()// List of things to say when they engage a target
+ var/list/reactions = list() // List of "string" = "reaction" and things they hear will be searched for string.
- var/turns_per_move = 1
- var/turns_since_move = 0
- universal_speak = 0 //No, just no.
- var/meat_amount = 0
- var/meat_type
- var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals.
- var/wander = 1 // Does the mob wander around when idle?
- var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it.
+ //Mob movement settings
+ var/wander = 1 // Does the mob wander around when idle?
+ var/wander_distance = 3 // How far the mob will wander before going home (assuming they are allowed to do that)
+ var/returns_home = 0 // Mob knows how to return to wherever it started
+ var/turns_per_move = 1 // How many life() cycles to wait between each wander mov?
+ var/stop_when_pulled = 1 // When set to 1 this stops the animal from moving when someone is pulling it.
+ var/follow_dist = 2 // Distance the mob tries to follow a friend
+ var/obstacles = list() // Things this mob refuses to move through
+ var/speed = 0 // Higher speed is slower, negative speed is faster.
+ var/obj/item/weapon/card/id/myid// An ID card if they have one to give them access to stuff.
+ var/turf/home_turf // Set when they spawned, they try to come back here sometimes.
- //Interaction
- var/response_help = "tries to help"
- var/response_disarm = "tries to disarm"
- var/response_harm = "tries to hurt"
- var/harm_intent_damage = 3
+ //Mob interaction
+ var/response_help = "tries to help" // If clicked on help intent
+ var/response_disarm = "tries to disarm" // If clicked on disarm intent
+ var/response_harm = "tries to hurt" // If clicked on harm intent
+ var/harm_intent_damage = 3 // How much an unarmed harm click does to this mob.
+ var/meat_amount = 0 // How much meat to drop from this mob when butchered
+ var/obj/meat_type // The meat object to drop
+ var/list/loot_list = list() // The list of lootable objects to drop, with "/path = prob%" structure
+ var/recruitable = 0 // Mob can be bossed around
+ var/recruit_cmd_str = "Hey," // The thing you prefix commands with when bossing them around
- //Temperature effect
- var/minbodytemp = 250
- var/maxbodytemp = 350
- var/heat_damage_per_tick = 3 //amount of damage applied if animal's body temperature is higher than maxbodytemp
- var/cold_damage_per_tick = 2 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
- var/fire_alert = 0
+ //Mob environment settings
+ var/minbodytemp = 250 // Minimum "okay" temperature in kelvin
+ var/maxbodytemp = 350 // Maximum of above
+ var/heat_damage_per_tick = 3 // Amount of damage applied if animal's body temperature is higher than maxbodytemp
+ var/cold_damage_per_tick = 2 // Same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
+ var/fire_alert = 0 // 0 = fine, 1 = hot, 2 = cold
- //Atmos effect - Yes, you can make creatures that require phoron or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage
- var/min_oxy = 5
- var/max_oxy = 0 //Leaving something at 0 means it's off - has no maximum
- var/min_tox = 0
- var/max_tox = 1
- var/min_co2 = 0
- var/max_co2 = 5
- var/min_n2 = 0
- var/max_n2 = 0
- var/unsuitable_atoms_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above
- var/speed = 0 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster
+ var/min_oxy = 5 // Oxygen in moles, minimum, 0 is 'no minimum
+ var/max_oxy = 0 // Oxygen in moles, maximum, 0 is 'no maximum'
+ var/min_tox = 0 // Phoron min
+ var/max_tox = 1 // Phoron max
+ var/min_co2 = 0 // CO2 min
+ var/max_co2 = 5 // CO2 max
+ var/min_n2 = 0 // N2 min
+ var/max_n2 = 0 // N2 max
+ var/unsuitable_atoms_damage = 2 // This damage is taken when atmos doesn't fit all the requirements above
- //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly
- var/melee_damage_lower = 0
- var/melee_damage_upper = 0
- var/attacktext = "attacked"
- var/attack_sound = null
- var/friendly = "nuzzles"
- var/environment_smash = 0
- var/resistance = 0 // Damage reduction
+ //Hostility settings
+ var/hostile = 0 // Do I even attack?
+ var/retaliate = 0 // I respond to damage against specific targets.
+ var/view_range = 7 // Scan for targets in this range.
+ var/specific_targets = 0 // Only use Found() targets, ignore others.
+ var/investigates = 0 // Do I investigate if I saw someone briefly?
+ var/attack_same = 0 // Do I attack members of my own faction?
+ var/cooperative = 0 // Do I ask allies to help me?
+ var/assist_distance = 25 // Radius in which I'll ask my comrades for help.
+ var/supernatural = 0 // If the mob is supernatural (used in null-rod stuff for banishing?)
+ var/grab_resist = 75 // Chance of me resisting a grab attempt.
- //Null rod stuff
- var/supernatural = 0
- var/purge = 0
+ //Attack ranged settings
+ var/ranged = 0 // Do I attack at range?
+ var/shoot_range = 7 // How far away do I start shooting from?
+ var/rapid = 0 // Three-round-burst fire mode
+ var/firing_lines = 0 // Avoids shooting allies
+ var/projectiletype // The projectiles I shoot
+ var/projectilesound // The sound I make when I do it
+ var/casingtype // What to make the hugely laggy casings pile out of
- //Pulling hostile mob vars down
- var/stance = STANCE_IDLE //Used to determine behavior
- var/mob/living/target_mob
- var/attack_same = 0
- var/ranged = 0
- var/rapid = 0
- var/projectiletype
- var/projectilesound
- var/casingtype
- var/move_to_delay = 4 //delay for the automated movement.
- var/list/friends = list()
- var/break_stuff_probability = 0
- var/destroy_surroundings = 0
- var/shuttletarget = null
- var/enroute = 0
- var/ai_inactive = 0 // Set to 1 to turn off most AI actions in Life()
+ //Mob melee settings
+ var/melee_damage_lower = 2 // Lower bound of randomized melee damage
+ var/melee_damage_upper = 6 // Upper bound of randomized melee damage
+ var/attacktext = "attacked" // "You are [attacktext] by the mob!"
+ var/friendly = "nuzzles" // What mobs do to people when they aren't really hostile
+ var/attack_sound = null // Sound to play when I attack
+ var/environment_smash = 0 // How much environment damage do I do when I hit stuff?
+ //Special attacks
+ var/spattack_prob = 0 // Chance of the mob doing a special attack (0 for never)
+ var/spattack_min_range = 0 // Min range to perform the special attacks from
+ var/spattack_max_range = 0 // Max range to perform special attacks from
+
+ //Attack movement settings
+ var/run_at_them = 1 // Don't use A* pathfinding, use walk_to
+ var/move_to_delay = 4 // Delay for the automated movement (deciseconds)
+ var/destroy_surroundings = 1 // Should I smash things to get to my target?
+
+ //Damage resistances
+ var/resistance = 0 // Damage reduction for all types
var/list/resistances = list(
HALLOSS = 0,
BRUTE = 1,
@@ -92,97 +127,217 @@
CLONE = 0
)
- var/hostile = 0
+ //Scary debug things
+ var/debug_ai = 0 // Logging level for this mob (1,2,3)
+ var/path_display = 0 // Will display the path in green when pathing
+ var/path_icon = 'icons/misc/debug_group.dmi' // What icon to use for the overlay
+ var/path_icon_state = "red" // What state to use for the overlay
+ var/icon/path_overlay // A reference to restart
+
+ ////// These are used for inter-proc communications so don't edit them manually //////
+ var/stance = STANCE_IDLE // Used to determine behavior
+ var/stop_automated_movement = 0 // Use this to temporarely stop random movement or to if you write special movement code for animals.
+ var/lifes_since_move = 0 // A counter for how many life() cycles since move
+ var/shuttletarget = null // Shuttle's here, time to get to it
+ var/enroute = 0 // If the shuttle is en-route
+ var/purge = 0 // A counter used for null-rod stuff
+ var/mob/living/target_mob // Who I'm trying to attack
+ var/mob/living/follow_mob // Who I'm recruited by
+ var/mob/living/list/friends = list() // People who are immune to my wrath, for now
+ var/mob/living/simple_animal/list/faction_friends = list() // Other simple mobs I am friends with
+ var/turf/list/walk_list = list()// List of turfs to walk through to get somewhere
+ var/astarpathing = 0 // Am I currently pathing to somewhere?
+ var/stance_changed = 0 // When our stance last changed (world.time)
+ var/last_target_time = 0 // When we last set our target, to prevent juggles
+ var/last_follow_time = 0 // When did we last get asked to follow someone?
+ var/last_helpask_time = 0 // When did we last call for help?
+ var/follow_until_time = 0 // Give up following when we reach this time (0 = never)
+ var/annoyed = 0 // Do people keep distract-kiting us?
+ ////// ////// //////
/mob/living/simple_animal/New()
..()
verbs -= /mob/verb/observe
+ home_turf = get_turf(src)
+ path_overlay = new(path_icon,path_icon_state)
+ move_to_delay = max(2,move_to_delay) //Protection against people coding things incorrectly and A* pathing 100% of the time
+ for(var/L in has_langs)
+ languages |= all_languages[L]
+ if(languages.len)
+ default_language = languages[1]
+
+ if(cooperative)
+ var/mob/living/simple_animal/first_friend
+ for(var/mob/living/simple_animal/M in living_mob_list)
+ if(M.faction == src.faction)
+ first_friend = M
+ break
+ if(first_friend)
+ faction_friends = first_friend.faction_friends
+ faction_friends |= src
+ else
+ faction_friends |= src
+
+/mob/living/simple_animal/Destroy()
+ home_turf = null
+ path_overlay = null
+ default_language = null
+ target_mob = null
+ follow_mob = null
+ if(myid)
+ qdel(myid)
+ myid = null
+
+ if(faction_friends.len) //This list is shared amongst the faction
+ faction_friends -= src
+
+ friends.Cut() //This one is not
+ walk_list.Cut()
+ languages.Cut()
+ ..()
+
+//Client attached
/mob/living/simple_animal/Login()
if(src && src.client)
src.client.screen = list()
src.client.screen += src.client.void
+ ai_inactive = 1
+ handle_stance(STANCE_IDLE)
+ src.client << "Mob AI disabled while you are controlling the mob."
..()
+//Client detatched
+/mob/living/simple_animal/Logout()
+ if(src && !src.client)
+ spawn(15 SECONDS) //15 seconds to get back into the mob before it goes wild
+ if(src && !src.client)
+ ai_inactive = initial(ai_inactive) //So if they never have an AI, they stay that way.
+ ..()
+
+//For debug purposes!
+/mob/living/simple_animal/proc/ai_log_output(var/msg = "missing message", var/ver = 1)
+ if(ver <= debug_ai)
+ log_debug("SA-AI: ([src]:[x],[y],[z])(@[world.time]): [msg] ")
+
+//Should we be dead?
/mob/living/simple_animal/updatehealth()
- return
+ //Alive, becoming dead
+ if((stat < DEAD) && (health <= 0))
+ death()
+
+ //Dead, becoming alive
+ else if((stat >= DEAD) && (health > 0))
+ dead_mob_list -= src
+ living_mob_list += src
+ stat = CONSCIOUS
+ density = 1
+
+ //Overhealth
+ else if(health > maxHealth)
+ health = maxHealth
+
+/mob/living/simple_animal/update_icon()
+ ..()
+ //Awake and normal
+ if((stat == CONSCIOUS) && (!icon_rest || !resting))
+ icon_state = icon_living
+
+ //Resting or KO'd
+ else if(((stat == UNCONSCIOUS) || resting) && icon_rest)
+ icon_state = icon_rest
+
+ //Dead
+ else if(stat >= DEAD)
+ icon_state = icon_dead
+
+ //Backup
+ else
+ icon_state = initial(icon_state)
/mob/living/simple_animal/Life()
..()
//Health
- if(stat == DEAD)
- if(health > 0)
- icon_state = icon_living
- dead_mob_list -= src
- living_mob_list += src
- stat = CONSCIOUS
- density = 1
- else
- walk(src, 0)
+ updatehealth()
+ if(stat >= DEAD)
return 0
-
- if(health <= 0)
- death()
- return
-
- if(health > maxHealth)
- health = maxHealth
-
handle_stunned()
handle_weakened()
handle_paralysed()
handle_supernatural()
+ handle_atmos() //Atmos
+ update_icon()
+ ai_log("Life() - stance=[stance] ai_inactive=[ai_inactive]", 4)
//Movement
- if(!client && !stop_automated_movement && wander && !anchored && !ai_inactive)
- if(isturf(src.loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc.
- turns_since_move++
- if(turns_since_move >= turns_per_move)
- if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled
- var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND
- moving_to = pick(cardinal)
- dir = moving_to //How about we turn them the direction they are moving, yay.
- Move(get_step(src,moving_to))
- turns_since_move = 0
+ if(!ai_inactive && !stop_automated_movement && wander && !anchored) //Allowed to move?
+ handle_wander_movement()
//Speaking
- if(!client && speak_chance)
- if(rand(0,200) < speak_chance)
- if(speak && speak.len)
- if((emote_hear && emote_hear.len) || (emote_see && emote_see.len))
- var/length = speak.len
- if(emote_hear && emote_hear.len)
- length += emote_hear.len
- if(emote_see && emote_see.len)
- length += emote_see.len
- var/randomValue = rand(1,length)
- if(randomValue <= speak.len)
- say(pick(speak))
- else
- randomValue -= speak.len
- if(emote_see && randomValue <= emote_see.len)
- visible_emote("[pick(emote_see)].")
- else
- audible_emote("[pick(emote_hear)].")
+ if(!ai_inactive && speak_chance && stance == STANCE_IDLE) // Allowed to chatter?
+ handle_idle_speaking()
+
+ //Stanceyness
+ if(!ai_inactive)
+ handle_stance()
+
+ //Resisting out of things
+ if(!ai_inactive && incapacitated(INCAPACITATION_DEFAULT) && stance != STANCE_IDLE)
+ resist()
+
+ return 1
+
+// Peforms the random walk wandering
+/mob/living/simple_animal/proc/handle_wander_movement()
+ if(isturf(src.loc) && !resting && !buckled && canmove) //Physically capable of moving?
+ lifes_since_move++ //Increment turns since move (turns are life() cycles)
+ if(lifes_since_move >= turns_per_move)
+ if(!(stop_when_pulled && pulledby)) //Some animals don't move when pulled
+ var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND
+ moving_to = pick(cardinal)
+ dir = moving_to //How about we turn them the direction they are moving, yay.
+ Move(get_step(src,moving_to))
+ lifes_since_move = 0
+
+// Handles random chatter, called from Life() when stance = STANCE_IDLE
+/mob/living/simple_animal/proc/handle_idle_speaking()
+ if(rand(0,200) < speak_chance)
+ if(speak && speak.len)
+ if((emote_hear && emote_hear.len) || (emote_see && emote_see.len))
+ var/length = speak.len
+ if(emote_hear && emote_hear.len)
+ length += emote_hear.len
+ if(emote_see && emote_see.len)
+ length += emote_see.len
+ var/randomValue = rand(1,length)
+ if(randomValue <= speak.len)
+ try_say_list(speak)
else
- say(pick(speak))
- else
- if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len))
- visible_emote("[pick(emote_see)].")
- if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len))
- audible_emote("[pick(emote_hear)].")
- if((emote_hear && emote_hear.len) && (emote_see && emote_see.len))
- var/length = emote_hear.len + emote_see.len
- var/pick = rand(1,length)
- if(pick <= emote_see.len)
+ randomValue -= speak.len
+ if(emote_see && randomValue <= emote_see.len)
visible_emote("[pick(emote_see)].")
else
audible_emote("[pick(emote_hear)].")
+ else
+ try_say_list(speak)
+ else
+ if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len))
+ visible_emote("[pick(emote_see)].")
+ if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len))
+ audible_emote("[pick(emote_hear)].")
+ if((emote_hear && emote_hear.len) && (emote_see && emote_see.len))
+ var/length = emote_hear.len + emote_see.len
+ var/pick = rand(1,length)
+ if(pick <= emote_see.len)
+ visible_emote("[pick(emote_see)].")
+ else
+ audible_emote("[pick(emote_hear)].")
-
- //Atmos
+// Handle interacting with and taking damage from atmos
+// TODO - Refactor this to use handle_environment() like a good /mob/living
+/mob/living/simple_animal/proc/handle_atmos()
var/atmos_suitable = 1
var/atom/A = src.loc
@@ -235,23 +390,55 @@
if(!atmos_suitable)
adjustBruteLoss(unsuitable_atoms_damage)
- //Hostility
- if(!stat && !client && hostile && !ai_inactive)
- switch(stance)
- if(STANCE_IDLE)
- target_mob = FindTarget()
+// For setting the stance WITHOUT processing it
+/mob/living/simple_animal/proc/set_stance(var/new_stance)
+ stance = new_stance
+ stance_changed = world.time
+ ai_log("set_stance() changing to [new_stance]",2)
- if(STANCE_ATTACK)
- if(destroy_surroundings)
- DestroySurroundings()
- MoveToTarget()
+// For proccessing the current stance, or setting and processing a new one
+/mob/living/simple_animal/proc/handle_stance(var/new_stance)
+ if(ai_inactive)
+ stance = STANCE_IDLE
+ return
- if(STANCE_ATTACKING)
- if(destroy_surroundings)
- DestroySurroundings()
- AttackTarget()
+ if(new_stance)
+ set_stance(new_stance)
- return 1
+ switch(stance)
+ if(STANCE_IDLE)
+ target_mob = null
+ a_intent = I_HELP
+ annoyed = max(0,annoyed--)
+
+ //Yes I'm breaking this into two if()'s for ease of reading
+ //If we ARE ALLOWED TO
+ if(returns_home && home_turf && !astarpathing && (world.time - stance_changed) > 10 SECONDS)
+ if(get_dist(src,home_turf) > wander_distance)
+ move_to_delay = initial(move_to_delay)*2 //Walk back.
+ GoHome()
+ else
+ stop_automated_movement = 0
+
+ //Search for targets while idle
+ if(hostile)
+ FindTarget()
+ if(STANCE_FOLLOW)
+ annoyed = 15
+ FollowTarget()
+ if(follow_until_time && world.time > follow_until_time)
+ LoseFollow()
+ return
+ if(hostile)
+ FindTarget()
+ if(STANCE_ATTACK)
+ annoyed = 50
+ a_intent = I_HURT
+ RequestHelp()
+ MoveToTarget()
+ if(STANCE_ATTACKING)
+ annoyed = 50
+ AttackTarget()
/mob/living/simple_animal/proc/handle_supernatural()
if(purge)
@@ -273,16 +460,20 @@
/mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
if(!Proj)
return
+ ai_log("bullet_act() I was shot by: [Proj.firer]",2)
if(Proj.taser_effect)
stun_effect_act(0, Proj.agony)
- if(Proj.nodamage)
- return
+ if(!Proj.nodamage)
+ adjustBruteLoss(Proj.damage)
+
+ if(Proj.firer)
+ react_to_attack(Proj.firer)
- adjustBruteLoss(Proj.damage)
return 0
+// When someone clicks us with an empty hand
/mob/living/simple_animal/attack_hand(mob/living/carbon/human/M as mob)
..()
@@ -290,10 +481,10 @@
if(I_HELP)
if (health > 0)
- M.visible_message("\blue [M] [response_help] \the [src]")
+ M.visible_message("[M] [response_help] \the [src].")
if(I_DISARM)
- M.visible_message("\blue [M] [response_disarm] \the [src]")
+ M.visible_message("[M] [response_disarm] \the [src].")
M.do_attack_animation(src)
//TODO: Push the mob away or something
@@ -302,6 +493,9 @@
return
if (!(status_flags & CANPUSH))
return
+ if(!incapacitated(INCAPACITATION_ALL) && (stance != STANCE_IDLE) && prob(grab_resist))
+ M.visible_message("[M] tries to grab [src] but fails!")
+ return
var/obj/item/weapon/grab/G = new /obj/item/weapon/grab(M, src)
@@ -311,16 +505,21 @@
G.affecting = src
LAssailant = M
- M.visible_message("\red [M] has grabbed [src] passively!")
+ M.visible_message("[M] has grabbed [src] passively!")
M.do_attack_animation(src)
+ ai_log("attack_hand() I was grabbed by: [M]",2)
+ react_to_attack(M)
if(I_HURT)
adjustBruteLoss(harm_intent_damage)
- M.visible_message("\red [M] [response_harm] \the [src]")
+ M.visible_message("[M] [response_harm] \the [src]!")
M.do_attack_animation(src)
+ ai_log("attack_hand() I was hit by: [M]",2)
+ react_to_attack(M)
return
+// When somoene clicks us with an item in hand
/mob/living/simple_animal/attackby(var/obj/item/O, var/mob/user)
if(istype(O, /obj/item/stack/medical))
if(stat != DEAD)
@@ -344,9 +543,10 @@
visible_message("[user] gently taps [src] with \the [O].")
else
O.attack(src, user, user.zone_sel.selecting)
+ ai_log("attackby() I was weapon'd by: [user]",2)
+ react_to_attack(user)
/mob/living/simple_animal/hit_with_weapon(obj/item/O, mob/living/user, var/effective_force, var/hit_zone)
-
visible_message("\The [src] has been attacked with \the [O] by [user].")
if(O.force <= resistance)
@@ -360,9 +560,23 @@
damage *= 2
purge = 3
adjustBruteLoss(damage)
+ ai_log("hit_with_weapon() I was h_w_weapon'd by: [user]",2)
+ react_to_attack(user)
return 0
+// When someone throws something at us
+/mob/living/simple_animal/hitby(atom/movable/AM)
+ ..()
+ if(AM.thrower)
+ react_to_attack(AM.thrower)
+
+//SA vs SA basically
+/mob/living/simple_animal/attack_generic(var/mob/attacker)
+ ..()
+ if(attacker)
+ react_to_attack(attacker)
+
/mob/living/simple_animal/movement_delay()
var/tally = 0 //Incase I need to add stuff other than "speed" later
@@ -380,9 +594,28 @@
if(statpanel("Status") && show_stat_health)
stat(null, "Health: [round((health / maxHealth) * 100)]%")
+/mob/living/simple_animal/lay_down()
+ ..()
+ if(resting && icon_rest)
+ icon_state = icon_rest
+ else
+ icon_state = icon_living
+
/mob/living/simple_animal/death(gibbed, deathmessage = "dies!")
- icon_state = icon_dead
- density = 0
+ density = 0 //We don't block even if we did before
+ walk(src, 0) //We stop any background-processing walks
+
+ if(faction_friends.len)
+ faction_friends -= src
+
+ if(loot_list.len) //Drop any loot
+ for(var/path in loot_list)
+ if(prob(loot_list[path]))
+ new path(get_turf(src))
+
+ spawn(3) //We'll update our icon in a sec
+ update_icon()
+
return ..(gibbed,deathmessage)
/mob/living/simple_animal/ex_act(severity)
@@ -407,18 +640,21 @@
/mob/living/simple_animal/adjustFireLoss(damage)
health = Clamp(health - damage, 0, maxHealth)
+// Check target_mob if worthy of attack (i.e. check if they are dead or empty mecha)
/mob/living/simple_animal/proc/SA_attackable(target_mob)
+ ai_log("SA_attackable([target_mob])",3)
if (isliving(target_mob))
var/mob/living/L = target_mob
- if(!L.stat && L.health >= 0)
- return (0)
+ if(!L.stat)
+ return 1
if (istype(target_mob,/obj/mecha))
var/obj/mecha/M = target_mob
if (M.occupant)
- return (0)
- return 1
+ return 1
+ ai_log("SA_attackable([target_mob]): no",3)
+ return 0
-/mob/living/simple_animal/say(var/message)
+/mob/living/simple_animal/say(var/message,var/datum/language/language)
var/verb = "says"
if(speak_emote.len)
verb = pick(speak_emote)
@@ -431,7 +667,7 @@
return verb
/mob/living/simple_animal/put_in_hands(var/obj/item/W) // No hands.
- W.loc = get_turf(src)
+ W.forceMove(get_turf(src))
return 1
// Harvest an animal's delicious byproducts
@@ -459,12 +695,58 @@
/mob/living/simple_animal/ExtinguishMob()
return
- //Hostile procs moved down
-/mob/living/simple_animal/proc/FindTarget()
+//We got hit! Consider hitting them back!
+/mob/living/simple_animal/proc/react_to_attack(var/mob/living/M)
+ if(stat || M == target_mob) return //Not if we're dead or already hitting them
+ if(M in friends || M.faction == faction) return //I'll overlook it THIS time...
+ ai_log("react_to_attack([M])",1)
+ if(retaliate && set_target(M))
+ handle_stance(STANCE_ATTACK)
+ return M
+ return 0
+
+/mob/living/simple_animal/proc/set_target(var/mob/M)
+ ai_log("SetTarget([M])",2)
+ if(!M || (world.time - last_target_time < 5 SECONDS) && target_mob)
+ ai_log("SetTarget() can't set it again so soon",3)
+ return 0
+
+ var/turf/seen = get_turf(M)
+
+ if(investigates && (annoyed < 10))
+ try_say_list(say_maybe_target)
+ face_atom(seen)
+ annoyed += 14
+ sleep(1 SECOND) //For realism
+
+ if(M in ListTargets(view_range))
+ try_say_list(say_got_target)
+ target_mob = M
+ last_target_time = world.time
+ return M
+ else if(investigates)
+ spawn(1)
+ WanderTowards(seen)
+
+ return 0
+
+// Set a follow target, with optional time for how long to follow them.
+/mob/living/simple_animal/proc/set_follow(var/mob/M, var/follow_for = 0)
+ ai_log("SetFollow([M]) for=[follow_for]",2)
+ if(!M || (world.time - last_target_time < 4 SECONDS) && follow_mob)
+ ai_log("SetFollow() can't set it again so soon",3)
+ return 0
+
+ follow_mob = M
+ last_follow_time = world.time
+ follow_until_time = !follow_for ? 0 : world.time + follow_for
+ return 1
+
+//Scan surroundings for a valid target
+/mob/living/simple_animal/proc/FindTarget()
var/atom/T = null
- stop_automated_movement = 0
- for(var/atom/A in ListTargets(10))
+ for(var/atom/A in ListTargets(view_range))
if(A == src)
continue
@@ -473,6 +755,8 @@
if(F)
T = F
break
+ else if(specific_targets)
+ return 0
if(isliving(A))
var/mob/living/L = A
@@ -480,52 +764,342 @@
continue
else if(L in friends)
continue
+ else if(!SA_attackable(L))
+ continue
else
- if(!L.stat)
- stance = STANCE_ATTACK
- T = L
- break
+ T = L
+ break
else if(istype(A, /obj/mecha)) // Our line of sight stuff was already done in ListTargets().
var/obj/mecha/M = A
- if (M.occupant)
- stance = STANCE_ATTACK
+ if(!SA_attackable(M))
+ continue
+ if((M.occupant.faction != src.faction) || attack_same)
T = M
break
+
+ //You found one!
+ if(T)
+ ai_log("FindTarget() found [T]!",1)
+ if(set_target(T))
+ handle_stance(STANCE_ATTACK)
+
return T
-
+//Used for special targeting or reactions
/mob/living/simple_animal/proc/Found(var/atom/A)
return
+//Requesting help from like-minded individuals
+/mob/living/simple_animal/proc/RequestHelp()
+ if(!cooperative || ((world.time - last_helpask_time) < 10 SECONDS))
+ return
+
+ ai_log("RequestHelp() to [faction_friends.len] friends",2)
+ last_helpask_time = world.time
+ for(var/mob/living/simple_animal/F in faction_friends)
+ if(F == src) continue
+ if(get_dist(src,F) <= F.assist_distance)
+ spawn(0)
+ if(F) //They could have died by now and some mobs delete themselves on death
+ ai_log("RequestHelp() to [F]",3)
+ F.HelpRequested(src)
+
+//Someone wants help?
+/mob/living/simple_animal/proc/HelpRequested(var/mob/living/simple_animal/F)
+ if(!(stance == STANCE_IDLE) || stat)
+ ai_log("HelpRequested() by [F] but we're busy/dead",2)
+ return
+ if(get_dist(src,F) <= follow_dist)
+ ai_log("HelpRequested() by [F] but we're already here",2)
+ return
+
+ if(set_follow(F, 10 SECONDS))
+ handle_stance(STANCE_FOLLOW)
+
+//Move to a target (or near if we're ranged)
/mob/living/simple_animal/proc/MoveToTarget()
- stop_automated_movement = 1
- if(!target_mob || SA_attackable(target_mob))
- stance = STANCE_IDLE
- if(target_mob in ListTargets(10))
- if(ranged)
- if(get_dist(src, target_mob) <= 6)
- OpenFire(target_mob)
- else
- walk_to(src, target_mob, 1, move_to_delay)
- else
- stance = STANCE_ATTACKING
- walk_to(src, target_mob, 1, move_to_delay)
+ if(incapacitated(INCAPACITATION_DISABLED))
+ ai_log("MoveToTarget() Bailing because we're disabled",2)
+ return
-/mob/living/simple_animal/proc/AttackTarget()
-
- stop_automated_movement = 1
- if(!target_mob || SA_attackable(target_mob))
+ //If we were chasing someone and we can't anymore, give up.
+ if(!target_mob || !SA_attackable(target_mob))
+ ai_log("MoveToTarget() Losing target at top",2)
LoseTarget()
+ return
+
+ //Don't wander
+ stop_automated_movement = 1
+ move_to_delay = initial(move_to_delay)
+
+ //We recompute our path every time we're called if we can still see them
+ if(target_mob in ListTargets(view_range))
+
+ //Recompute the path if we were using one since we can still see them.
+ if(astarpathing)
+ ForgetPath()
+
+ //Find out where we're getting to
+ var/get_to = ranged ? shoot_range-1 : 1 //Shoot range -1 just because we don't want to constantly get kited
+ var/distance = get_dist(src,target_mob)
+ ai_log("MoveToTarget() [src] [get_to] [distance]",2)
+
+ //We're here!
+ if(distance <= get_to)
+ ai_log("MoveToTarget() [src] attack range",2)
+ handle_stance(STANCE_ATTACKING)
+ return
+
+ //We're just setting out, making a new path, or we can't path with A*
+ if(!walk_list.len)
+ ai_log("SA: MoveToTarget() pathing to [target_mob]",2)
+
+ //GetPath failed for whatever reason, just smash into things towards them
+ if(run_at_them || !GetPath(get_turf(target_mob),get_to))
+
+ //We try the built-in way to stay close
+ walk_to(src, target_mob, get_to, move_to_delay)
+ ai_log("MoveToTarget() walk_to([src],[target_mob],[get_to],[move_to_delay])",3)
+
+ //Break shit in their direction! LEME SMAHSH
+ var/dir_to_mob = get_dir(src,target_mob)
+ face_atom(target_mob)
+ DestroySurroundings(dir_to_mob)
+ ai_log("MoveToTarget() DestroySurroundings([get_dir(src,target_mob)])",3)
+
+ //We have a path! We aren't already pathing it!
+ if(!astarpathing && walk_list.len)
+ ai_log("MoveToTarget() going to start a path",2)
+ spawn(1)
+
+ //Do the path!
+ var/result = WalkPath(target_thing = target_mob, target_dist = get_to)
+ ai_log("MoveToTarget() WalkPath r:[result]",2)
+
+ //WalkPath failed, either interrupted for recalc, or something else
+ if(!result)
+ return
+
+ //WalkPath either got close enough or we ran out of path
+ else
+ spawn(1)
+ ai_log("MoveToTarget() resetting",2)
+ MoveToTarget()
+
+ //We can't see them, and we don't have a path we're trying to follow to find them
+ else if(!astarpathing)
+ ai_log("MoveToTarget() Losing target at bottom",2)
+ LoseTarget() //Just forget it.
+
+//Follow a target (and don't attempt to murder it horribly)
+/mob/living/simple_animal/proc/FollowTarget()
+ ai_log("FollowTarget() [follow_mob]",1)
+ //If we were chasing someone and we can't anymore, give up.
+ if(!follow_mob || follow_mob.stat)
+ ai_log("FollowTarget() Losing follow at top",2)
+ LoseFollow()
+ return
+
+ if(incapacitated(INCAPACITATION_DISABLED))
+ ai_log("FollowTarget() Bailing because we're disabled",2)
+ return
+
+ if((get_dist(src,follow_mob) <= follow_dist))
+ ai_log("FollowTarget() Already at target",2)
+ return
+
+ move_to_delay = initial(move_to_delay)
+ var/start_distance = get_dist(src,follow_mob)
+
+ //Bad pathing
+ if(run_at_them)
+ walk_to(src, follow_mob, follow_dist, move_to_delay)
+ ai_log("FollowTarget() walk_to([src],[target_mob],2,[move_to_delay])",3)
+ spawn(3 SECONDS)
+ if(src && follow_mob && (stance == STANCE_FOLLOW) && (get_dist(src,follow_mob) >= start_distance))
+ ai_log("FollowTarget() walk_to not making headway, giving up",3)
+ LoseFollow()
+
+ //Good pathing
+ else
+ GetPath(get_turf(follow_mob),follow_dist)
+ if(!astarpathing && walk_list.len)
+ spawn(1)
+ ai_log("FollowTarget() A* path getting underway",2)
+ //Do the path!
+ var/result = WalkPath(target_thing = follow_mob, target_dist = follow_dist)
+ ai_log("FollowTarget() WalkPath r:[result]",3)
+
+ else
+ ai_log("FollowTarget() GetPath can't path, giving up",3)
+ LoseFollow()
+
+//Just try one time to go look at something. Don't really focus much on it.
+/mob/living/simple_animal/proc/WanderTowards(var/turf/T)
+ if(!T) return
+ ai_log("WanderTowards() [T.x],[T.y]",1)
+
+ stop_automated_movement = 1
+ GetPath(T,1)
+
+ if(run_at_them || !walk_list.len)
+ ai_log("WanderTowards() walk_to getting underway",2)
+ walk_to(src, T, 1, move_to_delay)
+ else
+ if(!astarpathing)
+ spawn(1)
+ ai_log("WanderTowards() A* path getting underway",2)
+ WalkPath(target_thing = T, target_dist = 1)
+
+//A* now, try to a path to a target
+/mob/living/simple_animal/proc/GetPath(var/turf/target,var/get_to = 1,var/max_distance = world.view*6)
+ ai_log("GetPath([target],[get_to],[max_distance])",2)
+ ForgetPath()
+ var/list/new_path = AStar(get_turf(loc), target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, min_target_dist = get_to, max_node_depth = max_distance, id = myid, exclude = obstacles)
+
+ if(new_path && new_path.len)
+ walk_list = new_path
+ if(path_display)
+ for(var/turf/T in walk_list)
+ T.overlays |= path_overlay
+ else
return 0
- if(!(target_mob in ListTargets(10)))
- LostTarget()
+
+ return walk_list.len
+
+//Walk along our A* path, target_thing allows us to stop early if we're nearby
+/mob/living/simple_animal/proc/WalkPath(var/atom/target_thing, var/target_dist = 1, var/proc/steps_callback = null, var/every_steps = 4)
+ ai_log("WalkPath() (steps:[walk_list.len])",2)
+ if(!walk_list || !walk_list.len)
+ return
+
+ astarpathing = 1
+ var/step_count = 0
+ var/failed_steps = 0
+ while(1)
+ //We're supposed to stop
+ if(!astarpathing || incapacitated(INCAPACITATION_DISABLED))
+ astarpathing = 0
+ ai_log("WalkPath() was interrupted",2)
+ return 0
+ //Finished the path
+ if(!walk_list.len)
+ astarpathing = 0
+ ai_log("WalkPath() exited naturally",2)
+ return 1
+ if(failed_steps >= 3)
+ astarpathing = 0
+ ai_log("WalkPath() failed too many steps",2)
+ return 0
+
+ //Take a step
+ if(!MoveOnce())
+ ai_log("WalkPath() failed a step",3)
+ failed_steps++
+ else
+ ai_log("WalkPath() took a step",3)
+ failed_steps = 0
+ step_count++
+
+ //If we have a particular target we care about, look for them
+ if(target_thing && (get_dist(src,target_thing) <= target_dist))
+ ai_log("WalkPath() returning due to proximity",2)
+ return target_thing
+
+ //If we have a callback
+ if(steps_callback && (step_count >= every_steps))
+ ai_log("WalkPath() doing callback",3)
+ call(steps_callback)()
+
+ //And wait for the time to our next step
+ sleep(move_to_delay)
+
+//Take one step along a path
+/mob/living/simple_animal/proc/MoveOnce()
+ if(!walk_list.len)
+ return
+
+ if(path_display)
+ var/turf/T = src.walk_list[1]
+ T.overlays -= path_overlay
+
+ step_towards(src, src.walk_list[1])
+ if(src.loc != src.walk_list[1])
+ ai_log("MoveOnce() step_towards returning 0",3)
return 0
- if(get_dist(src, target_mob) <= 1) //Attacking
- AttackingTarget()
+ else
+ walk_list -= src.walk_list[1]
+ ai_log("MoveOnce() step_towards returning 1",3)
return 1
-/mob/living/simple_animal/proc/AttackingTarget()
+//Forget the path entirely
+/mob/living/simple_animal/proc/ForgetPath()
+ ai_log("ForgetPath()",2)
+ if(path_display)
+ for(var/turf/T in walk_list)
+ T.overlays -= path_overlay
+ astarpathing = 0
+ walk_list.Cut()
+
+//Giving up on moving
+/mob/living/simple_animal/proc/GiveUpMoving()
+ ai_log("GiveUpMoving()",1)
+ ForgetPath()
+ walk(src, 0)
+ stop_automated_movement = 0
+
+//Return home, all-in-one proc (though does target scan and drop out if they see one)
+/mob/living/simple_animal/proc/GoHome()
+ if(!home_turf) return
+ if(astarpathing) ForgetPath()
+ ai_log("GoHome()",1)
+ var/close_enough = 2
+ var/look_in = 250
+ if(GetPath(home_turf,close_enough,look_in))
+ stop_automated_movement = 1
+ spawn(1)
+ if(WalkPath()) //If we made it without interruption
+ ai_log("GoHome() got home",2)
+ GiveUpMoving() //Go back to wandering
+
+//Get into attack mode on a target
+/mob/living/simple_animal/proc/AttackTarget()
+ stop_automated_movement = 1
+ if(!target_mob || !SA_attackable(target_mob))
+ LoseTarget()
+ return 0
+ if(!(target_mob in ListTargets(view_range)))
+ LostTarget()
+ return 0
+
+ ai_log("AttackTarget() vs. [target_mob]",1)
+ var/distance = get_dist(src, target_mob)
+ face_atom(target_mob)
+
+ //Hadoooooken!
+ if(prob(spattack_prob) && (distance >= spattack_min_range) && (distance <= spattack_max_range))
+ ai_log("AttackTarget() special",3)
+ if(SpecialAtkTarget()) //Might not succeed/be allowed, do something else.
+ return 1
+ //AAAAH!
+ if(distance <= 1)
+ ai_log("AttackTarget() melee",3)
+ PunchTarget()
+ return 1
+ //Open fire!
+ else if(ranged && (distance <= shoot_range))
+ ai_log("AttackTarget() ranged",3)
+ ShootTarget(target_mob)
+ return 1
+ //They ran away!
+ else
+ ai_log("AttackTarget() out of range!",3)
+ handle_stance(STANCE_ATTACK)
+ return 0
+
+//Attack the target in melee
+/mob/living/simple_animal/proc/PunchTarget()
if(!Adjacent(target_mob))
return
if(isliving(target_mob))
@@ -537,30 +1111,17 @@
M.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext)
return M
-/mob/living/simple_animal/proc/LoseTarget()
- stance = STANCE_IDLE
- target_mob = null
- walk(src, 0)
-
-/mob/living/simple_animal/proc/LostTarget()
- stance = STANCE_IDLE
- walk(src, 0)
-
-
-/mob/living/simple_animal/proc/ListTargets(var/dist = 7)
- var/list/L = hearers(src, dist)
-
- for (var/obj/mecha/M in mechas_list)
- if (M.z == src.z && get_dist(src, M) <= dist)
- L += M
-
- return L
-
-/mob/living/simple_animal/proc/OpenFire(target_mob)
+//The actual top-level ranged attack proc
+/mob/living/simple_animal/proc/ShootTarget()
var/target = target_mob
- visible_message("\red [src] fires at [target]!", 1)
-
var/tturf = get_turf(target)
+
+ if(firing_lines && !CheckFiringLine(tturf))
+ step_rand(src)
+ face_atom(tturf)
+ return 0
+
+ visible_message("[src] fires at [target]!", 1)
if(rapid)
spawn(1)
Shoot(tturf, src.loc, src)
@@ -579,11 +1140,34 @@
if(casingtype)
new casingtype
- stance = STANCE_IDLE
- target_mob = null
- return
+ return 1
+//Check firing lines for faction_friends (if we're not cooperative, we don't care)
+/mob/living/simple_animal/proc/CheckFiringLine(var/turf/tturf)
+ if(!tturf) return
+ var/turf/list/crosses = list()
+ var/this_turf = get_turf(src)
+
+ while(this_turf != tturf)
+ this_turf = get_step(this_turf,get_dir(this_turf,tturf))
+ crosses += this_turf
+
+ for(var/mob/living/FF in faction_friends)
+ if(FF.loc in crosses)
+ return 0
+
+ for(var/mob/living/F in friends)
+ if(F.loc in crosses)
+ return 0
+
+ return 1
+
+//Special attacks, like grenades or blinding spit or whatever
+/mob/living/simple_animal/proc/SpecialAtkTarget()
+ return 0
+
+//Shoot a bullet at someone
/mob/living/simple_animal/proc/Shoot(var/target, var/start, var/user, var/bullet = 0)
if(target == start)
return
@@ -598,18 +1182,62 @@
A.launch(target)
return
-/mob/living/simple_animal/proc/DestroySurroundings()
- if(prob(break_stuff_probability))
- for(var/dir in cardinal) // North, South, East, West
- for(var/obj/structure/window/obstacle in get_step(src, dir))
- if(obstacle.dir == reverse_dir[dir]) // So that windows get smashed in the right order
- obstacle.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext)
- return
- var/obj/structure/obstacle = locate(/obj/structure, get_step(src, dir))
- if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille))
- obstacle.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext)
+//We can't see the target
+/mob/living/simple_animal/proc/LoseTarget()
+ ai_log("LoseTarget() [target_mob]",2)
+ target_mob = null
+ handle_stance(STANCE_IDLE)
+ GiveUpMoving()
+//Target is no longer valid (?)
+/mob/living/simple_animal/proc/LostTarget()
+ handle_stance(STANCE_IDLE)
+ GiveUpMoving()
+//Forget a follow mode
+/mob/living/simple_animal/proc/LoseFollow()
+ ai_log("LoseFollow() [target_mob]",2)
+ follow_mob = null
+ handle_stance(STANCE_IDLE)
+ GiveUpMoving()
+
+//Find me some targets
+/mob/living/simple_animal/proc/ListTargets(var/dist = view_range)
+ var/list/L = hearers(src, dist)
+
+ for(var/obj/mecha/M in mechas_list)
+ if ((M.z == src.z) && (get_dist(src, M) <= dist) && (isInSight(src,M)))
+ L += M
+
+ return L
+
+//Break through windows/other things
+/mob/living/simple_animal/proc/DestroySurroundings(var/direction)
+ if(!direction)
+ direction = pick(cardinal) //FLAIL WILDLY
+
+ var/turf/problem_turf = get_step(src, direction)
+
+ ai_log("DestroySurroundings([direction])",3)
+ for(var/obj/structure/window/obstacle in problem_turf)
+ if(obstacle.dir == reverse_dir[dir]) // So that windows get smashed in the right order
+ ai_log("DestroySurroundings() directional window hit",3)
+ obstacle.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext)
+ return
+
+ var/obj/structure/obstacle = locate(/obj/structure, problem_turf)
+ if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille))
+ ai_log("DestroySurroundings() generic structure hit [obstacle]",3)
+ obstacle.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext)
+ return
+
+ for(var/obj/machinery/door/baddoor in problem_turf) //Required since firelocks take up the same turf
+ if(baddoor.density)
+ ai_log("DestroySurroundings() door hit [baddoor]",3)
+ baddoor.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext)
+ return
+
+//Check for shuttle bumrush
/mob/living/simple_animal/proc/check_horde()
return 0
if(emergency_shuttle.shuttle.location)
@@ -626,6 +1254,7 @@
enroute = 0
stop_automated_movement = 0
+//Shuttle bumrush
/mob/living/simple_animal/proc/horde()
var/turf/T = get_step_to(src, shuttletarget)
for(var/atom/A in T)
@@ -647,6 +1276,7 @@
if(!src.stat)
horde()
+//Touches a wire, etc
/mob/living/simple_animal/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null)
shock_damage *= siemens_coeff
if (shock_damage < 1)
@@ -659,6 +1289,7 @@
s.set_up(5, 1, loc)
s.start()
+//Shot with taser/stunvolver
/mob/living/simple_animal/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null)
var/stunDam = 0
var/agonyDam = 0
@@ -669,4 +1300,26 @@
if(agony_amount)
agonyDam += agony_amount * 0.5
- adjustFireLoss(agonyDam)
\ No newline at end of file
+ adjustFireLoss(agonyDam)
+
+//Commands, reactions, etc
+/mob/living/simple_animal/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol)
+ ..()
+ if(reacts && speaker && (message in reactions) && (!hostile || isliving(speaker)) && say_understands(speaker,language))
+ var/mob/living/L = speaker
+ if(L.faction == faction)
+ spawn(10)
+ face_atom(speaker)
+ say(reactions[message])
+
+//Just some subpaths for easy searching
+/mob/living/simple_animal/hostile
+ faction = "not yours"
+ hostile = 1
+ retaliate = 1
+ stop_when_pulled = 0
+ destroy_surroundings = 1
+
+/mob/living/simple_animal/retaliate
+ retaliate = 1
+ destroy_surroundings = 1
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 95a96f03c88..a161062f1ef 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -706,12 +706,13 @@
/mob/proc/facedir(var/ndir)
- if(!canface() || client.moving || world.time < client.move_delay)
+ if(!canface() || (client && (client.moving || (world.time < client.move_delay))))
return 0
set_dir(ndir)
if(buckled && buckled.buckle_movable)
buckled.set_dir(ndir)
- client.move_delay += movement_delay()
+ if(client)
+ client.move_delay += movement_delay()
return 1
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 2f788862f82..4327840a478 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -535,7 +535,7 @@ proc/is_blind(A)
if(. == SAFE_PERP)
return SAFE_PERP
- if(!istype(src, /mob/living/simple_animal/hostile/retaliate/goat))
+ if(!istype(src, /mob/living/simple_animal/retaliate/goat))
threatcount += 4
return threatcount
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index 269dbd4f30b..ccf46782163 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -304,7 +304,7 @@
return 1
if(ispath(MP, /mob/living/simple_animal/shade))
return 1
- if(ispath(MP, /mob/living/simple_animal/tomato))
+ if(ispath(MP, /mob/living/simple_animal/hostile/tomato))
return 1
if(ispath(MP, /mob/living/simple_animal/mouse))
return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak)
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index 483defc3985..ed27e1a259b 100644
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -36,7 +36,7 @@
/obj/machinery/disease2/incubator,
/obj/machinery/disposal,
/mob/living/simple_animal/cow,
- /mob/living/simple_animal/hostile/retaliate/goat,
+ /mob/living/simple_animal/retaliate/goat,
/obj/machinery/computer/centrifuge,
/obj/machinery/sleeper,
/obj/machinery/smartfridge/,
diff --git a/code/modules/xenoarcheaology/artifacts/autocloner.dm b/code/modules/xenoarcheaology/artifacts/autocloner.dm
index 89c653516f9..5f56ece8ee3 100644
--- a/code/modules/xenoarcheaology/artifacts/autocloner.dm
+++ b/code/modules/xenoarcheaology/artifacts/autocloner.dm
@@ -37,7 +37,7 @@
/mob/living/simple_animal/slime,
/mob/living/simple_animal/crab,
/mob/living/simple_animal/mouse,
- /mob/living/simple_animal/hostile/retaliate/goat)
+ /mob/living/simple_animal/retaliate/goat)
//todo: how the hell is the asteroid permanently powered?
/obj/machinery/auto_cloner/process()
diff --git a/polaris.dme b/polaris.dme
index 353899627c9..508a608512d 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -1678,45 +1678,45 @@
#include "code\modules\mob\living\silicon\robot\drone\drone_manufacturer.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_say.dm"
#include "code\modules\mob\living\simple_animal\corpse.dm"
-#include "code\modules\mob\living\simple_animal\parrot.dm"
-#include "code\modules\mob\living\simple_animal\shade.dm"
#include "code\modules\mob\living\simple_animal\simple_animal.dm"
-#include "code\modules\mob\living\simple_animal\worm.dm"
+#include "code\modules\mob\living\simple_animal\aliens\alien.dm"
+#include "code\modules\mob\living\simple_animal\aliens\creature.dm"
+#include "code\modules\mob\living\simple_animal\aliens\drone.dm"
+#include "code\modules\mob\living\simple_animal\aliens\faithless.dm"
+#include "code\modules\mob\living\simple_animal\aliens\hivebot.dm"
+#include "code\modules\mob\living\simple_animal\aliens\mimic.dm"
+#include "code\modules\mob\living\simple_animal\aliens\shade.dm"
+#include "code\modules\mob\living\simple_animal\animals\bat.dm"
+#include "code\modules\mob\living\simple_animal\animals\bear.dm"
+#include "code\modules\mob\living\simple_animal\animals\carp.dm"
+#include "code\modules\mob\living\simple_animal\animals\cat.dm"
+#include "code\modules\mob\living\simple_animal\animals\corgi.dm"
+#include "code\modules\mob\living\simple_animal\animals\crab.dm"
+#include "code\modules\mob\living\simple_animal\animals\farm_animals.dm"
+#include "code\modules\mob\living\simple_animal\animals\giant_spider.dm"
+#include "code\modules\mob\living\simple_animal\animals\goose.dm"
+#include "code\modules\mob\living\simple_animal\animals\lizard.dm"
+#include "code\modules\mob\living\simple_animal\animals\mouse.dm"
+#include "code\modules\mob\living\simple_animal\animals\parrot.dm"
+#include "code\modules\mob\living\simple_animal\animals\penguin.dm"
+#include "code\modules\mob\living\simple_animal\animals\slime.dm"
+#include "code\modules\mob\living\simple_animal\animals\spiderbot.dm"
+#include "code\modules\mob\living\simple_animal\animals\tomato.dm"
+#include "code\modules\mob\living\simple_animal\animals\tree.dm"
+#include "code\modules\mob\living\simple_animal\animals\worm.dm"
#include "code\modules\mob\living\simple_animal\borer\borer.dm"
#include "code\modules\mob\living\simple_animal\borer\borer_captive.dm"
#include "code\modules\mob\living\simple_animal\borer\borer_powers.dm"
#include "code\modules\mob\living\simple_animal\borer\say.dm"
#include "code\modules\mob\living\simple_animal\constructs\constructs.dm"
#include "code\modules\mob\living\simple_animal\constructs\soulstone.dm"
-#include "code\modules\mob\living\simple_animal\friendly\cat.dm"
-#include "code\modules\mob\living\simple_animal\friendly\corgi.dm"
-#include "code\modules\mob\living\simple_animal\friendly\crab.dm"
-#include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm"
-#include "code\modules\mob\living\simple_animal\friendly\lizard.dm"
-#include "code\modules\mob\living\simple_animal\friendly\mouse.dm"
-#include "code\modules\mob\living\simple_animal\friendly\mushroom.dm"
-#include "code\modules\mob\living\simple_animal\friendly\penguin.dm"
-#include "code\modules\mob\living\simple_animal\friendly\slime.dm"
-#include "code\modules\mob\living\simple_animal\friendly\spiderbot.dm"
-#include "code\modules\mob\living\simple_animal\friendly\tomato.dm"
-#include "code\modules\mob\living\simple_animal\hostile\alien.dm"
-#include "code\modules\mob\living\simple_animal\hostile\bat.dm"
-#include "code\modules\mob\living\simple_animal\hostile\bear.dm"
-#include "code\modules\mob\living\simple_animal\hostile\carp.dm"
-#include "code\modules\mob\living\simple_animal\hostile\creature.dm"
-#include "code\modules\mob\living\simple_animal\hostile\faithless.dm"
-#include "code\modules\mob\living\simple_animal\hostile\giant_spider.dm"
-#include "code\modules\mob\living\simple_animal\hostile\goose.dm"
-#include "code\modules\mob\living\simple_animal\hostile\hivebot.dm"
-#include "code\modules\mob\living\simple_animal\hostile\hostile.dm"
-#include "code\modules\mob\living\simple_animal\hostile\mimic.dm"
-#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
-#include "code\modules\mob\living\simple_animal\hostile\russian.dm"
-#include "code\modules\mob\living\simple_animal\hostile\syndicate.dm"
-#include "code\modules\mob\living\simple_animal\hostile\tree.dm"
-#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm"
-#include "code\modules\mob\living\simple_animal\hostile\retaliate\drone.dm"
-#include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\clown.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\head.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\kobold.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\mechamobs.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\pirate.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\russian.dm"
+#include "code\modules\mob\living\simple_animal\humanoids\syndicate.dm"
#include "code\modules\mob\living\voice\voice.dm"
#include "code\modules\mob\new_player\login.dm"
#include "code\modules\mob\new_player\logout.dm"