/* run_armor_check(a,b) args a:def_zone - What part is getting hit, if null will check entire body b:attack_flag - What type of attack, bullet, laser, energy, melee c:armour_pen - How much armor to ignore. d:absorb_text - Custom text to send to the player when the armor fully absorbs an attack. e:soften_text - Similar to absorb_text, custom text to send to the player when some damage is reduced. Returns A number between 0 and 100, with higher numbers resulting in less damage taken. */ /mob/living/proc/run_armor_check(var/def_zone = null, var/attack_flag = "melee", var/armour_pen = 0, var/absorb_text = null, var/soften_text = null) if(Debug2) world.log << "## DEBUG: getarmor() was called." if(armour_pen >= 100) return 0 //might as well just skip the processing var/armor = getarmor(def_zone, attack_flag) if(armor) var/armor_variance_range = round(armor * 0.25) //Armor's effectiveness has a +25%/-25% variance. var/armor_variance = rand(-armor_variance_range, armor_variance_range) //Get a random number between -25% and +25% of the armor's base value if(Debug2) world.log << "## DEBUG: The range of armor variance is [armor_variance_range]. The variance picked by RNG is [armor_variance]." armor = min(armor + armor_variance, 100) //Now we calcuate damage using the new armor percentage. armor = max(armor - armour_pen, 0) //Armor pen makes armor less effective. if(armor >= 100) if(absorb_text) src << "[absorb_text]" else src << "Your armor absorbs the blow!" else if(armor > 0) if(soften_text) src << "[soften_text]" else src << "Your armor softens the blow!" if(Debug2) world.log << "## DEBUG: Armor when [src] was attacked was [armor]." return armor /* //Old armor code here. if(armour_pen >= 100) return 0 //might as well just skip the processing var/armor = getarmor(def_zone, attack_flag) var/absorb = 0 //Roll armour if(prob(armor)) absorb += 1 if(prob(armor)) absorb += 1 //Roll penetration if(prob(armour_pen)) absorb -= 1 if(prob(armour_pen)) absorb -= 1 if(absorb >= 2) if(absorb_text) show_message("[absorb_text]") else show_message("Your armor absorbs the blow!") return 2 if(absorb == 1) if(absorb_text) show_message("[soften_text]",4) else show_message("Your armor softens the blow!") return 1 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) //Being hit while using a deadman switch if(istype(get_active_hand(),/obj/item/device/assembly/signaler)) var/obj/item/device/assembly/signaler/signaler = get_active_hand() if(signaler.deadman && prob(80)) log_and_message_admins("has triggered a signaler deadman's switch") src.visible_message("[src] triggers their deadman's switch!") 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) if ((proj_sharp || proj_edge) && (soaked >= round(P.damage*0.8))) proj_sharp = 0 proj_edge = 0 if ((proj_sharp || proj_edge) && prob(getarmor(def_zone, P.check_armour))) proj_sharp = 0 proj_edge = 0 //Stun Beams if(P.taser_effect) stun_effect_act(0, P.agony, def_zone, P) src <<"You have been hit by [P]!" if(!P.nodamage) 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, soaked, 0, P, sharp=proj_sharp, edge=proj_edge) P.on_hit(src, absorb, soaked, def_zone) if(absorb == 100) return 2 else if (absorb >= 0) return 1 else return 0 // return absorb //Handles the effects of "stun" weapons /mob/living/proc/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) flash_pain() if (stun_amount) Stun(stun_amount) Weaken(stun_amount) apply_effect(STUTTER, stun_amount) apply_effect(EYE_BLUR, stun_amount) if (agony_amount) apply_damage(agony_amount, HALLOSS, def_zone, 0, used_weapon) apply_effect(STUTTER, agony_amount/10) apply_effect(EYE_BLUR, agony_amount/10) /mob/living/proc/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0) return 0 //only carbon liveforms have this proc /mob/living/emp_act(severity) var/list/L = src.get_contents() for(var/obj/O in L) O.emp_act(severity) ..() /mob/living/blob_act(var/obj/structure/blob/B) if(stat == DEAD) return var/damage = rand(30, 40) var/armor_pen = 0 var/armor_check = "melee" var/damage_type = BRUTE var/attack_message = "The blob attacks you!" var/attack_verb = "attacks" var/def_zone = pick(BP_HEAD, BP_TORSO, BP_GROIN, BP_L_ARM, BP_R_ARM, BP_L_LEG, BP_R_LEG) if(B && B.overmind) var/datum/blob_type/blob = B.overmind.blob_type damage = rand(blob.damage_lower, blob.damage_upper) armor_check = blob.armor_check armor_pen = blob.armor_pen damage_type = blob.damage_type attack_message = "[blob.attack_message][isSynthetic() ? "[blob.attack_message_synth]":"[blob.attack_message_living]"]" attack_verb = blob.attack_verb B.overmind.blob_type.on_attack(B, src, def_zone) if( (damage_type == TOX || damage_type == OXY) && isSynthetic()) // Borgs and FBPs don't really handle tox/oxy damage the same way other mobs do. damage_type = BRUTE damage *= 0.66 // Take 2/3s as much damage. visible_message("\The [B] [attack_verb] \the [src]!", "[attack_message]!") playsound(loc, 'sound/effects/attackblob.ogg', 50, 1) //Armor var/soaked = get_armor_soak(def_zone, armor_check, armor_pen) var/absorb = run_armor_check(def_zone, armor_check, armor_pen) apply_damage(damage, damage_type, def_zone, absorb, soaked) /mob/living/proc/resolve_item_attack(obj/item/I, mob/living/user, var/target_zone) return target_zone //Called when the mob is hit with an item in combat. Returns the blocked result /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, 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/soaked, var/hit_zone) if(!effective_force || blocked >= 100) return 0 //Hulk modifier if(HULK in user.mutations) effective_force *= 2 //Apply weapon damage var/weapon_sharp = is_sharp(I) var/weapon_edge = has_edge(I) if(getsoak(hit_zone, "melee",) - (I.armor_penetration/5) > round(effective_force*0.8)) //soaking a hit turns sharp attacks into blunt ones weapon_sharp = 0 weapon_edge = 0 if(prob(max(getarmor(hit_zone, "melee") - I.armor_penetration, 0))) //melee armour provides a chance to turn sharp/edge weapon attacks into blunt ones weapon_sharp = 0 weapon_edge = 0 apply_damage(effective_force, I.damtype, hit_zone, blocked, soaked, sharp=weapon_sharp, edge=weapon_edge, used_weapon=I) return 1 //this proc handles being hit by a thrown atom /mob/living/hitby(atom/movable/AM as mob|obj,var/speed = THROWFORCE_SPEED_DIVISOR)//Standardization and logging -Sieve if(istype(AM,/obj/)) var/obj/O = AM var/dtype = O.damtype var/throw_damage = O.throwforce*(speed/THROWFORCE_SPEED_DIVISOR) var/miss_chance = 15 if (O.throw_source) var/distance = get_dist(O.throw_source, loc) miss_chance = max(15*(distance-2), 0) if (prob(miss_chance)) visible_message("\The [O] misses [src] narrowly!") return src.visible_message("[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, soaked, is_sharp(O), has_edge(O), O) O.throwing = 0 //it hit, so stop moving if(ismob(O.thrower)) var/mob/M = O.thrower var/client/assailant = M.client if(assailant) add_attack_logs(M,src,"Hit by thrown [O.name]") // Begin BS12 momentum-transfer code. var/mass = 1.5 if(istype(O, /obj/item)) var/obj/item/I = O mass = I.w_class/THROWNOBJ_KNOCKBACK_DIVISOR var/momentum = speed*mass if(O.throw_source && momentum >= THROWNOBJ_KNOCKBACK_SPEED) var/dir = get_dir(O.throw_source, src) visible_message("[src] staggers under the impact!","You stagger under the impact!") src.throw_at(get_edge_target_turf(src,dir),1,momentum) if(!O || !src) return if(O.sharp) //Projectile is suitable for pinning. if(soaked >= round(throw_damage*0.8)) return //Handles embedding for non-humans and simple_animals. embed(O) var/turf/T = near_wall(dir,2) if(T) src.loc = T visible_message("[src] is pinned to the wall by [O]!","You are pinned to the wall by [O]!") src.anchored = 1 src.pinned += O /mob/living/proc/embed(var/obj/O, var/def_zone=null) O.loc = src src.embedded += O src.verbs += /mob/proc/yank_out_object //This is called when the mob is thrown into a dense turf /mob/living/proc/turf_collision(var/turf/T, var/speed) src.take_organ_damage(speed*5) /mob/living/proc/near_wall(var/direction,var/distance=1) var/turf/T = get_step(get_turf(src),direction) var/turf/last_turf = src.loc var/i = 1 while(i>0 && i<=distance) if(T.density) //Turf is a wall! return last_turf i++ last_turf = T T = get_step(T,direction) return 0 // End BS12 momentum-transfer code. /mob/living/attack_generic(var/mob/user, var/damage, var/attack_message) if(!damage) return adjustBruteLoss(damage) add_attack_logs(user,src,"Generic attack (probably animal)", admin_notify = FALSE) //Usually due to simple_animal attacks src.visible_message("[user] has [attack_message] [src]!") user.do_attack_animation(src) spawn(1) updatehealth() return 1 /mob/living/proc/IgniteMob() if(fire_stacks > 0 && !on_fire) on_fire = 1 handle_light() update_fire() /mob/living/proc/ExtinguishMob() if(on_fire) on_fire = 0 fire_stacks = 0 handle_light() update_fire() /mob/living/proc/update_fire() return /mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person fire_stacks = Clamp(fire_stacks + add_fire_stacks, FIRE_MIN_STACKS, FIRE_MAX_STACKS) /mob/living/proc/handle_fire() if(fire_stacks < 0) fire_stacks = min(0, ++fire_stacks) //If we've doused ourselves in water to avoid fire, dry off slowly if(fire_stacks > 0) fire_stacks = max(0, (fire_stacks-0.1)) //Should slowly burn out if(!on_fire) return 1 else if(fire_stacks <= 0) ExtinguishMob() //Fire's been put out. return 1 var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment if(G.gas["oxygen"] < 1) ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire return 1 var/turf/location = get_turf(src) location.hotspot_expose(fire_burn_temperature(), 50, 1) /mob/living/fire_act() adjust_fire_stacks(2) IgniteMob() //Share fire evenly between the two mobs //Called in MobCollide() and Crossed() /mob/living/proc/spread_fire(mob/living/L) return // This is commented out pending discussion on Polaris. If you're a downsteam and you want people to spread fire by touching each other, feel free to uncomment this. /* if(!istype(L)) return var/L_old_on_fire = L.on_fire if(on_fire) //Only spread fire stacks if we're on fire fire_stacks /= 2 L.fire_stacks += fire_stacks if(L.IgniteMob()) message_admins("[key_name(src)] bumped into [key_name(L)] and set them on fire.") if(L_old_on_fire) //Only ignite us and gain their stacks if they were onfire before we bumped them L.fire_stacks /= 2 fire_stacks += L.fire_stacks IgniteMob() */ /mob/living/proc/get_cold_protection() return 0 /mob/living/proc/get_heat_protection() return 0 //Finds the effective temperature that the mob is burning at. /mob/living/proc/fire_burn_temperature() if (fire_stacks <= 0) return 0 //Scale quadratically so that single digit numbers of fire stacks don't burn ridiculously hot. //lower limit of 700 K, same as matches and roughly the temperature of a cool flame. return max(2.25*round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2), 700) /mob/living/proc/reagent_permeability() return 1 return round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2) /mob/living/proc/handle_actions() //Pretty bad, i'd use picked/dropped instead but the parent calls in these are nonexistent for(var/datum/action/A in actions) if(A.CheckRemoval(src)) A.Remove(src) for(var/obj/item/I in src) if(I.action_button_name) if(!I.action) if(I.action_button_is_hands_free) I.action = new/datum/action/item_action/hands_free else I.action = new/datum/action/item_action I.action.name = I.action_button_name I.action.target = I I.action.Grant(src) return /mob/living/update_action_buttons() if(!hud_used) return if(!client) return if(hud_used.hud_shown != 1) //Hud toggled to minimal return client.screen -= hud_used.hide_actions_toggle for(var/datum/action/A in actions) if(A.button) client.screen -= A.button if(hud_used.action_buttons_hidden) if(!hud_used.hide_actions_toggle) hud_used.hide_actions_toggle = new(hud_used) hud_used.hide_actions_toggle.UpdateIcon() if(!hud_used.hide_actions_toggle.moved) hud_used.hide_actions_toggle.screen_loc = hud_used.ButtonNumberToScreenCoords(1) //hud_used.SetButtonCoords(hud_used.hide_actions_toggle,1) client.screen += hud_used.hide_actions_toggle return var/button_number = 0 for(var/datum/action/A in actions) button_number++ if(A.button == null) var/obj/screen/movable/action_button/N = new(hud_used) N.owner = A A.button = N var/obj/screen/movable/action_button/B = A.button B.UpdateIcon() B.name = A.UpdateName() client.screen += B if(!B.moved) B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number) //hud_used.SetButtonCoords(B,button_number) if(button_number > 0) if(!hud_used.hide_actions_toggle) hud_used.hide_actions_toggle = new(hud_used) hud_used.hide_actions_toggle.InitialiseIcon(src) if(!hud_used.hide_actions_toggle.moved) hud_used.hide_actions_toggle.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number+1) //hud_used.SetButtonCoords(hud_used.hide_actions_toggle,button_number+1) client.screen += hud_used.hide_actions_toggle // Returns a number to determine if something is harder or easier to hit than normal. /mob/living/proc/get_evasion() var/result = evasion // First we get the 'base' evasion. Generally this is zero. for(var/datum/modifier/M in modifiers) if(!isnull(M.evasion)) result += M.evasion return result