diff --git a/code/ZAS/Airflow.dm b/code/ZAS/Airflow.dm index 01a84fdf5d..8dd637e58f 100644 --- a/code/ZAS/Airflow.dm +++ b/code/ZAS/Airflow.dm @@ -228,13 +228,16 @@ mob/living/carbon/human/airflow_hit(atom/A) var/b_loss = airflow_speed * vsc.airflow_damage var/blocked = run_armor_check(BP_HEAD,"melee") - apply_damage(b_loss/3, BRUTE, BP_HEAD, blocked, 0, "Airflow") + var/soaked = get_armor_soak(BP_HEAD,"melee") + apply_damage(b_loss/3, BRUTE, BP_HEAD, blocked, soaked, 0, "Airflow") blocked = run_armor_check(BP_TORSO,"melee") - apply_damage(b_loss/3, BRUTE, BP_TORSO, blocked, 0, "Airflow") + soaked = get_armor_soak(BP_TORSO,"melee") + apply_damage(b_loss/3, BRUTE, BP_TORSO, blocked, soaked, 0, "Airflow") blocked = run_armor_check(BP_GROIN,"melee") - apply_damage(b_loss/3, BRUTE, BP_GROIN, blocked, 0, "Airflow") + soaked = get_armor_soak(BP_GROIN,"melee") + apply_damage(b_loss/3, BRUTE, BP_GROIN, blocked, soaked, 0, "Airflow") if(airflow_speed > 10) Paralyse(round(airflow_speed * vsc.airflow_stun)) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 0559b626e1..4316da3c49 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -44,6 +44,7 @@ var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up var/canremove = 1 //Mostly for Ninja code at this point but basically will not allow the item to be removed if set to 0. /N var/list/armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0) + var/list/armorsoak = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0) var/list/allowed = null //suit storage stuff. var/obj/item/device/uplink/hidden/hidden_uplink = null // All items can have an uplink hidden inside, just remember to add the triggers. var/zoomdevicename = null //name used for message when binoculars/scope is used diff --git a/code/game/objects/items/weapons/traps.dm b/code/game/objects/items/weapons/traps.dm index e5a655d7f9..cd11af3400 100644 --- a/code/game/objects/items/weapons/traps.dm +++ b/code/game/objects/items/weapons/traps.dm @@ -77,11 +77,15 @@ //armour var/blocked = L.run_armor_check(target_zone, "melee") + var/soaked = L.get_armor_soak(target_zone, "melee") if(blocked >= 100) return - if(!L.apply_damage(30, BRUTE, target_zone, blocked, used_weapon=src)) + if(soaked >= 30) + return + + if(!L.apply_damage(30, BRUTE, target_zone, blocked, soaked, used_weapon=src)) return 0 //trap the victim in place diff --git a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm index ae4fe4d740..0e6064785d 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm @@ -150,20 +150,22 @@ var/def_zone = ran_zone() var/blocked = occupant.run_armor_check(def_zone, "melee") + var/soaked = occupant.get_armor_soak(def_zone, "melee") occupant.throw_at(A, 3, propelled) occupant.apply_effect(6, STUN, blocked) occupant.apply_effect(6, WEAKEN, blocked) occupant.apply_effect(6, STUTTER, blocked) - occupant.apply_damage(10, BRUTE, def_zone, blocked) + occupant.apply_damage(10, BRUTE, def_zone, blocked, soaked) playsound(src.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) if(istype(A, /mob/living)) var/mob/living/victim = A def_zone = ran_zone() blocked = victim.run_armor_check(def_zone, "melee") + soaked = victim.get_armor_soak(def_zone, "melee") victim.apply_effect(6, STUN, blocked) victim.apply_effect(6, WEAKEN, blocked) victim.apply_effect(6, STUTTER, blocked) - victim.apply_damage(10, BRUTE, def_zone, blocked) + victim.apply_damage(10, BRUTE, def_zone, blocked, soaked) occupant.visible_message("[occupant] crashed into \the [A]!") /obj/structure/bed/chair/office/light diff --git a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm index 2bcbf8aa87..9ae19769eb 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm @@ -149,20 +149,22 @@ var/def_zone = ran_zone() var/blocked = occupant.run_armor_check(def_zone, "melee") + var/soaked = occupant.get_armor_soak(def_zone, "melee") occupant.throw_at(A, 3, propelled) occupant.apply_effect(6, STUN, blocked) occupant.apply_effect(6, WEAKEN, blocked) occupant.apply_effect(6, STUTTER, blocked) - occupant.apply_damage(10, BRUTE, def_zone) + occupant.apply_damage(10, BRUTE, def_zone, soaked) playsound(src.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) if(istype(A, /mob/living)) var/mob/living/victim = A def_zone = ran_zone() blocked = victim.run_armor_check(def_zone, "melee") + soaked = victim.get_armor_soak(def_zone, "melee") victim.apply_effect(6, STUN, blocked) victim.apply_effect(6, WEAKEN, blocked) victim.apply_effect(6, STUTTER, blocked) - victim.apply_damage(10, BRUTE, def_zone) + victim.apply_damage(10, BRUTE, def_zone, soaked) if(pulling) occupant.visible_message("[pulling] has thrusted \the [name] into \the [A], throwing \the [occupant] out of it!") diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index 0bd14ef9d1..855aa49c7e 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -161,6 +161,7 @@ piece.permeability_coefficient = permeability_coefficient piece.unacidable = unacidable if(islist(armor)) piece.armor = armor.Copy() + if(islist(armorsoak)) piece.armorsoak = armorsoak.Copy() update_icon(1) diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm index f6388e0190..1d92705dec 100644 --- a/code/modules/hydroponics/seed.dm +++ b/code/modules/hydroponics/seed.dm @@ -114,8 +114,11 @@ if(!target_limb) target_limb = pick(BP_ALL) var/blocked = target.run_armor_check(target_limb, "melee") + var/soaked = target.get_armor_soak(target_limb, "melee") + if(blocked >= 100) return + var/obj/item/organ/external/affecting = target.get_organ(target_limb) var/damage = 0 var/has_edge = 0 @@ -125,7 +128,7 @@ if(affecting) to_chat(target, "\The [fruit]'s thorns pierce your [affecting.name] greedily!") - target.apply_damage(damage, BRUTE, target_limb, blocked, "Thorns", sharp=1, edge=has_edge) + target.apply_damage(damage, BRUTE, target_limb, blocked, soaked, "Thorns", sharp=1, edge=has_edge) else to_chat(target, "\The [fruit]'s thorns pierce your flesh greedily!") target.adjustBruteLoss(damage) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index db59f6bc83..66ba7ecd51 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -4,7 +4,7 @@ return null ..() -/mob/living/carbon/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/hit_zone) +/mob/living/carbon/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/soaked, var/hit_zone) if(!effective_force || blocked >= 100) return 0 @@ -12,6 +12,10 @@ if(HULK in user.mutations) effective_force *= 2 + //If the armor soaks all of the damage, it just skips the rest of the checks + if(effective_force <= soaked) + return 0 + //Apply weapon damage var/weapon_sharp = is_sharp(I) var/weapon_edge = has_edge(I) diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index fccf6afbf6..01005b031b 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -43,6 +43,7 @@ return 0 var/obj/item/organ/external/affecting = get_organ(ran_zone(H.zone_sel.selecting)) var/armor_block = run_armor_check(affecting, "melee") + var/armor_soak = get_armor_soak(affecting, "melee") if(HULK in H.mutations) damage += 5 @@ -51,7 +52,10 @@ visible_message("\red [H] has punched [src]!") - apply_damage(damage, HALLOSS, affecting, armor_block) + if(armor_soak >= damage) + return + + apply_damage(damage, HALLOSS, affecting, armor_block, armor_soak) if(damage >= 9) visible_message("\red [H] has weakened [src]!") apply_effect(4, WEAKEN, armor_block) @@ -245,11 +249,12 @@ real_damage = max(1, real_damage) var/armour = run_armor_check(affecting, "melee") + var/soaked = get_armor_soak(affecting, "melee") // Apply additional unarmed effects. attack.apply_effects(H, src, armour, rand_damage, hit_zone) // Finally, apply damage to target - apply_damage(real_damage, (attack.deal_halloss ? HALLOSS : BRUTE), affecting, armour, sharp=attack.sharp, edge=attack.edge) + apply_damage(real_damage, (attack.deal_halloss ? HALLOSS : BRUTE), affecting, armour, soaked, sharp=attack.sharp, edge=attack.edge) if(I_DISARM) M.attack_log += text("\[[time_stamp()]\] Disarmed [src.name] ([src.ckey])") @@ -325,7 +330,8 @@ var/dam_zone = pick(organs_by_name) var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) + var/armor_soak = get_armor_soak(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block, armor_soak) updatehealth() return 1 diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index a6471c4aeb..4d8e428cc6 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -93,6 +93,29 @@ emp_act total += weight return (armorval/max(total, 1)) +//Like getarmor, but the value it returns will be numerical damage reduction +/mob/living/carbon/human/getsoak(var/def_zone, var/type) + var/soakval = 0 + var/total = 0 + + if(def_zone) + if(isorgan(def_zone)) + return getsoak_organ(def_zone, type) + var/obj/item/organ/external/affecting = get_organ(def_zone) + if(affecting) + return getsoak_organ(affecting, type) + //If a specific bodypart is targetted, check how that bodypart is protected and return the value. + + //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values + for(var/organ_name in organs_by_name) + if (organ_name in organ_rel_size) + var/obj/item/organ/external/organ = organs_by_name[organ_name] + if(organ) + var/weight = organ_rel_size[organ_name] + soakval += getsoak_organ(organ, type) * weight + total += weight + return (soakval/max(total, 1)) + //this proc returns the Siemens coefficient of electrical resistivity for a particular external organ. /mob/living/carbon/human/proc/get_siemens_coefficient_organ(var/obj/item/organ/external/def_zone) if (!def_zone) @@ -119,6 +142,17 @@ emp_act protection += C.armor[type] return protection +/mob/living/carbon/human/proc/getsoak_organ(var/obj/item/organ/external/def_zone, var/type) + if(!type || !def_zone) return 0 + var/soaked = 0 + var/list/protective_gear = list(head, wear_mask, wear_suit, w_uniform, gloves, shoes) + for(var/gear in protective_gear) + if(gear && istype(gear ,/obj/item/clothing)) + var/obj/item/clothing/C = gear + if(istype(C) && C.body_parts_covered & def_zone.body_part) + soaked += C.armorsoak[type] + return soaked + /mob/living/carbon/human/proc/check_head_coverage() var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform) @@ -195,25 +229,35 @@ emp_act visible_message("[src] has been [I.attack_verb.len? pick(I.attack_verb) : "attacked"] in the [affecting.name] with [I.name] by [user]!") + var/soaked = get_armor_soak(hit_zone, "melee", I.armor_penetration) + + if(soaked >= effective_force) + src << "Your armor absorbs the force of [I.name]!" + return + var/blocked = run_armor_check(hit_zone, "melee", I.armor_penetration, "Your armor has protected your [affecting.name].", "Your armor has softened the blow to your [affecting.name].") - standard_weapon_hit_effects(I, user, effective_force, blocked, hit_zone) + + standard_weapon_hit_effects(I, user, effective_force, blocked, soaked, hit_zone) return blocked -/mob/living/carbon/human/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/hit_zone) +/mob/living/carbon/human/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/soaked, var/hit_zone) var/obj/item/organ/external/affecting = get_organ(hit_zone) if(!affecting) return 0 + if(soaked >= effective_force) + return 0 + // Handle striking to cripple. if(user.a_intent == I_DISARM) effective_force *= 0.5 //reduced effective force... - if(!..(I, user, effective_force, blocked, hit_zone)) + if(!..(I, user, effective_force, blocked, soaked, hit_zone)) return 0 //set the dislocate mult less than the effective force mult so that //dislocating limbs on disarm is a bit easier than breaking limbs on harm - attack_joint(affecting, I, effective_force, 0.75, blocked) //...but can dislocate joints + attack_joint(affecting, I, effective_force, 0.75, blocked, soaked) //...but can dislocate joints else if(!..()) return 0 @@ -243,7 +287,7 @@ emp_act switch(hit_zone) if("head")//Harder to score a stun but if you do it lasts a bit longer if(prob(effective_force)) - apply_effect(20, PARALYZE, blocked) + apply_effect(20, PARALYZE, blocked, soaked) visible_message("\The [src] has been knocked unconscious!") if(bloody)//Apply blood if(wear_mask) @@ -257,15 +301,15 @@ emp_act update_inv_glasses(0) if("chest")//Easier to score a stun but lasts less time if(prob(effective_force + 10)) - apply_effect(6, WEAKEN, blocked) + apply_effect(6, WEAKEN, blocked, soaked) visible_message("\The [src] has been knocked down!") if(bloody) bloody_body(src) return 1 -/mob/living/carbon/human/proc/attack_joint(var/obj/item/organ/external/organ, var/obj/item/W, var/effective_force, var/dislocate_mult, var/blocked) - if(!organ || (organ.dislocated == 2) || (organ.dislocated == -1) || blocked >= 100) +/mob/living/carbon/human/proc/attack_joint(var/obj/item/organ/external/organ, var/obj/item/W, var/effective_force, var/dislocate_mult, var/blocked, var/soaked) + if(!organ || (organ.dislocated == 2) || (organ.dislocated == -1) || blocked >= 100 || soaked > effective_force) return 0 if(W.damtype != BRUTE) @@ -338,10 +382,6 @@ emp_act var/hit_area = affecting.name src.visible_message("\red [src] has been hit in the [hit_area] by [O].") - var/armor = run_armor_check(affecting, "melee", O.armor_penetration, "Your armor has protected your [hit_area].", "Your armor has softened hit to your [hit_area].") //I guess "melee" is the best fit here - - if(armor < 100) - apply_damage(throw_damage, dtype, zone, armor, is_sharp(O), has_edge(O), O) if(ismob(O.thrower)) var/mob/M = O.thrower @@ -352,12 +392,25 @@ emp_act if(!istype(src,/mob/living/simple_animal/mouse)) msg_admin_attack("[src.name] ([src.ckey]) was hit by a [O], thrown by [M.name] ([assailant.ckey]) (JMP)") + //If the armor absorbs all of the damage, skip the rest of the calculations + var/soaked = get_armor_soak(affecting, "melee", O.armor_penetration) + if(soaked >= throw_damage) + src << "Your armor absorbs the force of [O.name]!" + return + + var/armor = run_armor_check(affecting, "melee", O.armor_penetration, "Your armor has protected your [hit_area].", "Your armor has softened hit to your [hit_area].") //I guess "melee" is the best fit here + if(armor < 100) + apply_damage(throw_damage, dtype, zone, armor, soaked, is_sharp(O), has_edge(O), O) + + //thrown weapon embedded object code. if(dtype == BRUTE && istype(O,/obj/item)) var/obj/item/I = O if (!is_robot_module(I)) var/sharp = is_sharp(I) var/damage = throw_damage + if (soaked) + damage -= soaked if (armor) damage /= armor+1 diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index f09eb3741f..552319e6fe 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -8,11 +8,13 @@ Returns standard 0 if fail */ -/mob/living/proc/apply_damage(var/damage = 0,var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/used_weapon = null, var/sharp = 0, var/edge = 0) +/mob/living/proc/apply_damage(var/damage = 0,var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/soaked = 0, var/used_weapon = null, var/sharp = 0, var/edge = 0) if(Debug2) world.log << "## DEBUG: apply_damage() was called on [src], with [damage] damage, and an armor value of [blocked]." - if(!damage || (blocked >= 100)) + if(!damage || (blocked >= 100) || soaked >= damage) return 0 + if(soaked) + damage -= soaked blocked = (100-blocked)/100 switch(damagetype) if(BRUTE) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 17eff101bb..c52311820e 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -77,10 +77,20 @@ return 0 */ +//Certain pieces of armor actually absorb flat amounts of damage from income attacks +/mob/living/proc/get_armor_soak(var/def_zone = null, var/attack_flag = "melee", var/armour_pen = 0) + var/soaked = getsoak(def_zone, attack_flag) + //5 points of armor pen negate one point of soak + if(armour_pen) + soaked = max(soaked - (armour_pen/5), 0) + return soaked + //if null is passed for def_zone, then this should return something appropriate for all zones (e.g. area effect damage) /mob/living/proc/getarmor(var/def_zone, var/type) return 0 +/mob/living/proc/getsoak(var/def_zone, var/type) + return 0 /mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone) @@ -93,6 +103,7 @@ signaler.signal() //Armor + var/soaked = get_armor_soak(def_zone, P.check_armour, P.armor_penetration) var/absorb = run_armor_check(def_zone, P.check_armour, P.armor_penetration) var/proj_sharp = is_sharp(P) var/proj_edge = has_edge(P) @@ -105,13 +116,13 @@ stun_effect_act(0, P.agony, def_zone, P) src <<"\red You have been hit by [P]!" if(!P.nodamage) - apply_damage(P.damage, P.damage_type, def_zone, absorb, 0, P, sharp=proj_sharp, edge=proj_edge) + apply_damage(P.damage, P.damage_type, def_zone, absorb, soaked, 0, P, sharp=proj_sharp, edge=proj_edge) qdel(P) return if(!P.nodamage) - apply_damage(P.damage, P.damage_type, def_zone, absorb, 0, P, sharp=proj_sharp, edge=proj_edge) - P.on_hit(src, absorb, def_zone) + apply_damage(P.damage, P.damage_type, def_zone, absorb, soaked, 0, P, sharp=proj_sharp, edge=proj_edge) + P.on_hit(src, absorb, soaked, def_zone) if(absorb == 100) return 2 @@ -153,17 +164,21 @@ /mob/living/proc/hit_with_weapon(obj/item/I, mob/living/user, var/effective_force, var/hit_zone) visible_message("[src] has been [I.attack_verb.len? pick(I.attack_verb) : "attacked"] with [I.name] by [user]!") + var/soaked = get_armor_soak(hit_zone, "melee") var/blocked = run_armor_check(hit_zone, "melee") - standard_weapon_hit_effects(I, user, effective_force, blocked, hit_zone) - if(I.damtype == BRUTE && prob(33)) // Added blood for whacking non-humans too - var/turf/simulated/location = get_turf(src) - if(istype(location)) location.add_blood_floor(src) + //If the armor absorbs all of the damage, skip the damage calculation and the blood + if(!soaked > effective_force) + standard_weapon_hit_effects(I, user, effective_force, blocked, soaked, hit_zone) + + if(I.damtype == BRUTE && prob(33)) // Added blood for whacking non-humans too + var/turf/simulated/location = get_turf(src) + if(istype(location)) location.add_blood_floor(src) return blocked //returns 0 if the effects failed to apply for some reason, 1 otherwise. -/mob/living/proc/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/hit_zone) +/mob/living/proc/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/soaked, var/hit_zone) if(!effective_force || blocked >= 100) return 0 @@ -171,6 +186,10 @@ if(HULK in user.mutations) effective_force *= 2 + //Armor soak + if(soaked >= effective_force) + return 0 + //Apply weapon damage var/weapon_sharp = is_sharp(I) var/weapon_edge = has_edge(I) @@ -178,7 +197,7 @@ weapon_sharp = 0 weapon_edge = 0 - apply_damage(effective_force, I.damtype, hit_zone, blocked, sharp=weapon_sharp, edge=weapon_edge, used_weapon=I) + apply_damage(effective_force, I.damtype, hit_zone, blocked, soaked, sharp=weapon_sharp, edge=weapon_edge, used_weapon=I) return 1 @@ -200,8 +219,10 @@ src.visible_message("\red [src] has been hit by [O].") var/armor = run_armor_check(null, "melee") + var/soaked = get_armor_soak(null, "melee") - apply_damage(throw_damage, dtype, null, armor, is_sharp(O), has_edge(O), O) + + apply_damage(throw_damage, dtype, null, armor, soaked, is_sharp(O), has_edge(O), O) O.throwing = 0 //it hit, so stop moving @@ -230,6 +251,9 @@ if(!O || !src) return if(O.sharp) //Projectile is suitable for pinning. + if(soaked >= throw_damage) //Don't embed if it didn't actually damage + return + //Handles embedding for non-humans and simple_animals. embed(O) diff --git a/code/modules/mob/living/simple_animal/animals/bear.dm b/code/modules/mob/living/simple_animal/animals/bear.dm index 9d1da5e08b..228e92e5b7 100644 --- a/code/modules/mob/living/simple_animal/animals/bear.dm +++ b/code/modules/mob/living/simple_animal/animals/bear.dm @@ -1,125 +1,125 @@ -//Space bears! -/mob/living/simple_animal/hostile/bear - name = "space bear" - desc = "RawrRawr!!" - icon_state = "bear" - icon_living = "bear" - icon_dead = "bear_dead" - icon_gib = "bear_gib" - - faction = "russian" - cooperative = 1 - - maxHealth = 60 - health = 60 - turns_per_move = 5 - see_in_dark = 6 - stop_when_pulled = 0 - - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "pokes" - - melee_damage_lower = 20 - melee_damage_upper = 30 - - //Space bears 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 - - speak_chance = 1 - speak = list("RAWR!","Rawr!","GRR!","Growl!") - speak_emote = list("growls", "roars") - emote_hear = list("rawrs","grumbles","grawls") - emote_see = list("stares ferociously", "stomps") - - meat_type = /obj/item/weapon/reagent_containers/food/snacks/bearmeat - - var/stance_step = 0 - -/mob/living/simple_animal/hostile/bear/handle_stance() - switch(stance) - if(STANCE_TIRED) - stop_automated_movement = 1 - stance_step++ - if(stance_step >= 10) //rests for 10 ticks - if(target_mob && target_mob in ListTargets(10)) - handle_stance(STANCE_ATTACK) //If the mob he was chasing is still nearby, resume the attack, otherwise go idle. - else - handle_stance(STANCE_IDLE) - - if(STANCE_ALERT) - stop_automated_movement = 1 - var/found_mob = 0 - if(target_mob && target_mob in ListTargets(10)) - if(!(SA_attackable(target_mob))) - stance_step = max(0, stance_step) //If we have not seen a mob in a while, the stance_step will be negative, we need to reset it to 0 as soon as we see a mob again. - stance_step++ - found_mob = 1 - src.set_dir(get_dir(src,target_mob)) //Keep staring at the mob - - if(stance_step in list(1,4,7)) //every 3 ticks - var/action = pick( list( "growls at [target_mob]", "stares angrily at [target_mob]", "prepares to attack [target_mob]", "closely watches [target_mob]" ) ) - if(action) - custom_emote(1,action) - if(!found_mob) - stance_step-- - - if(stance_step <= -20) //If we have not found a mob for 20-ish ticks, revert to idle mode - handle_stance(STANCE_IDLE) - if(stance_step >= 7) //If we have been staring at a mob for 7 ticks, - handle_stance(STANCE_ATTACK) - - if(STANCE_ATTACKING) - if(stance_step >= 20) //attacks for 20 ticks, then it gets tired and needs to rest - custom_emote(1, "is worn out and needs to rest." ) - handle_stance(STANCE_TIRED) - stance_step = 0 - walk(src, 0) //This stops the bear's walking - return - else - ..() - -/mob/living/simple_animal/hostile/bear/update_icons() - ..() - if(!stat) - if(loc && istype(loc,/turf/space)) - icon_state = "bear" - else - icon_state = "bearfloor" - -/mob/living/simple_animal/hostile/bear/Process_Spacemove(var/check_drift = 0) - return - -/mob/living/simple_animal/hostile/bear/FindTarget() - . = ..() - if(.) - custom_emote(1,"stares alertly at [.]") - handle_stance(STANCE_ALERT) - -/mob/living/simple_animal/hostile/bear/PunchTarget() - if(!Adjacent(target_mob)) - return - custom_emote(1, pick( list("slashes at [target_mob]", "bites [target_mob]") ) ) - - var/damage = rand(melee_damage_lower, melee_damage_upper) - - if(ishuman(target_mob)) - var/mob/living/carbon/human/H = target_mob - var/dam_zone = pick(BP_TORSO, BP_L_HAND, BP_R_HAND, BP_L_LEG, BP_R_LEG) - var/obj/item/organ/external/affecting = H.get_organ(ran_zone(dam_zone)) - H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp=1, edge=1) - return H - else if(isliving(target_mob)) - var/mob/living/L = target_mob - L.adjustBruteLoss(damage) - return L - else - ..() +//Space bears! +/mob/living/simple_animal/hostile/bear + name = "space bear" + desc = "RawrRawr!!" + icon_state = "bear" + icon_living = "bear" + icon_dead = "bear_dead" + icon_gib = "bear_gib" + + faction = "russian" + cooperative = 1 + + maxHealth = 60 + health = 60 + turns_per_move = 5 + see_in_dark = 6 + stop_when_pulled = 0 + + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "pokes" + + melee_damage_lower = 20 + melee_damage_upper = 30 + + //Space bears 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 + + speak_chance = 1 + speak = list("RAWR!","Rawr!","GRR!","Growl!") + speak_emote = list("growls", "roars") + emote_hear = list("rawrs","grumbles","grawls") + emote_see = list("stares ferociously", "stomps") + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/bearmeat + + var/stance_step = 0 + +/mob/living/simple_animal/hostile/bear/handle_stance() + switch(stance) + if(STANCE_TIRED) + stop_automated_movement = 1 + stance_step++ + if(stance_step >= 10) //rests for 10 ticks + if(target_mob && target_mob in ListTargets(10)) + handle_stance(STANCE_ATTACK) //If the mob he was chasing is still nearby, resume the attack, otherwise go idle. + else + handle_stance(STANCE_IDLE) + + if(STANCE_ALERT) + stop_automated_movement = 1 + var/found_mob = 0 + if(target_mob && target_mob in ListTargets(10)) + if(!(SA_attackable(target_mob))) + stance_step = max(0, stance_step) //If we have not seen a mob in a while, the stance_step will be negative, we need to reset it to 0 as soon as we see a mob again. + stance_step++ + found_mob = 1 + src.set_dir(get_dir(src,target_mob)) //Keep staring at the mob + + if(stance_step in list(1,4,7)) //every 3 ticks + var/action = pick( list( "growls at [target_mob]", "stares angrily at [target_mob]", "prepares to attack [target_mob]", "closely watches [target_mob]" ) ) + if(action) + custom_emote(1,action) + if(!found_mob) + stance_step-- + + if(stance_step <= -20) //If we have not found a mob for 20-ish ticks, revert to idle mode + handle_stance(STANCE_IDLE) + if(stance_step >= 7) //If we have been staring at a mob for 7 ticks, + handle_stance(STANCE_ATTACK) + + if(STANCE_ATTACKING) + if(stance_step >= 20) //attacks for 20 ticks, then it gets tired and needs to rest + custom_emote(1, "is worn out and needs to rest." ) + handle_stance(STANCE_TIRED) + stance_step = 0 + walk(src, 0) //This stops the bear's walking + return + else + ..() + +/mob/living/simple_animal/hostile/bear/update_icons() + ..() + if(!stat) + if(loc && istype(loc,/turf/space)) + icon_state = "bear" + else + icon_state = "bearfloor" + +/mob/living/simple_animal/hostile/bear/Process_Spacemove(var/check_drift = 0) + return + +/mob/living/simple_animal/hostile/bear/FindTarget() + . = ..() + if(.) + custom_emote(1,"stares alertly at [.]") + handle_stance(STANCE_ALERT) + +/mob/living/simple_animal/hostile/bear/PunchTarget() + if(!Adjacent(target_mob)) + return + custom_emote(1, pick( list("slashes at [target_mob]", "bites [target_mob]") ) ) + + var/damage = rand(melee_damage_lower, melee_damage_upper) + + if(ishuman(target_mob)) + var/mob/living/carbon/human/H = target_mob + var/dam_zone = pick(BP_TORSO, BP_L_HAND, BP_R_HAND, BP_L_LEG, BP_R_LEG) + var/obj/item/organ/external/affecting = H.get_organ(ran_zone(dam_zone)) + H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), H.get_armor_soak(affecting, "melee"), sharp=1, edge=1) + return H + else if(isliving(target_mob)) + var/mob/living/L = target_mob + L.adjustBruteLoss(damage) + return L + else + ..() diff --git a/code/modules/mob/living/simple_animal/animals/parrot.dm b/code/modules/mob/living/simple_animal/animals/parrot.dm index d388939fcd..03f0261688 100644 --- a/code/modules/mob/living/simple_animal/animals/parrot.dm +++ b/code/modules/mob/living/simple_animal/animals/parrot.dm @@ -1,761 +1,761 @@ -/* 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"
+/* 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"), H.get_armor_soak(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/mob_grab_specials.dm b/code/modules/mob/mob_grab_specials.dm
index e7b75a2e5d..e06da4eb5e 100644
--- a/code/modules/mob/mob_grab_specials.dm
+++ b/code/modules/mob/mob_grab_specials.dm
@@ -56,7 +56,8 @@
return
var/armor = target.run_armor_check(target, "melee")
- if(armor < 60)
+ var/soaked = target.get_armor_soak(target, "melee")
+ if(armor + soaked < 60)
target << "You feel extreme pain!"
var/max_halloss = round(target.species.total_health * 0.8) //up to 80% of passing out
@@ -100,8 +101,9 @@
damage += hat.force * 3
var/armor = target.run_armor_check(BP_HEAD, "melee")
- target.apply_damage(damage, BRUTE, BP_HEAD, armor)
- attacker.apply_damage(10, BRUTE, BP_HEAD, attacker.run_armor_check(BP_HEAD, "melee"))
+ var/soaked = target.get_armor_soak(BP_HEAD, "melee")
+ target.apply_damage(damage, BRUTE, BP_HEAD, armor, soaked)
+ attacker.apply_damage(10, BRUTE, BP_HEAD, attacker.run_armor_check(BP_HEAD), attacker.get_armor_soak(BP_HEAD), "melee")
if(!armor && target.headcheck(BP_HEAD) && prob(damage))
target.apply_effect(20, PARALYZE)
diff --git a/code/modules/planet/weather.dm b/code/modules/planet/weather.dm
index 14318a1d7f..c98383a242 100644
--- a/code/modules/planet/weather.dm
+++ b/code/modules/planet/weather.dm
@@ -265,11 +265,15 @@
var/target_zone = pick(BP_ALL)
var/amount_blocked = L.run_armor_check(target_zone, "melee")
+ var/amount_soaked = L.get_armor_soak(target_zone, "melee")
if(amount_blocked >= 100)
return // No need to apply damage.
- L.apply_damage(rand(5, 10), BRUTE, target_zone, amount_blocked, used_weapon = "hail")
+ if(amount_soaked >= 10)
+ return // No need to apply damage.
+
+ L.apply_damage(rand(5, 10), BRUTE, target_zone, amount_blocked, amount_soaked, used_weapon = "hail")
to_chat(L, "The hail raining down on you [L.can_feel_pain() ? "hurts" : "damages you"]!")
/datum/weather/sif/blood_moon