diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index 54f91eab634..df29fedc425 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -63,7 +63,7 @@ // Should this all be in Touch()? if(istype(H) && has_hands) if(H.get_accuracy_penalty() && H != src) //Should only trigger if they're not aiming well - var/hit_zone = get_zone_with_miss_chance(H.zone_sel.selecting, src, H.get_accuracy_penalty()) + var/hit_zone = get_zone_with_miss_chance(H.zone_sel.selecting, src, H.get_accuracy_penalty(), attacker = H) if(!hit_zone) H.do_attack_animation(src) playsound(src, 'sound/weapons/punchmiss.ogg', 25, 1, -1) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 333a5abaccc..c438cf75560 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -253,7 +253,7 @@ emp_act if(user == src) // Attacking yourself can't miss return target_zone - var/hit_zone = get_zone_with_miss_chance(target_zone, src, user.get_accuracy_penalty()) + var/hit_zone = get_zone_with_miss_chance(target_zone, src, user.get_accuracy_penalty(), attacker = user) if(!hit_zone) user.do_attack_animation(src) @@ -460,7 +460,7 @@ emp_act if (O.throw_source) var/distance = get_dist(O.throw_source, loc) miss_chance = max(15*(distance-2), 0) - zone = get_zone_with_miss_chance(zone, src, miss_chance, ranged_attack=1) + zone = get_zone_with_miss_chance(zone, src, miss_chance, ranged_attack=1, attacker = O.thrower) if(zone && O.thrower != src) var/shield_check = check_shields(throw_damage, O, thrower, zone, "[O]") diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 2655d7afc12..a28e0c45cf3 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -131,12 +131,23 @@ return ran_zone -// Emulates targetting a specific body part, and miss chances -// May return null if missed -// miss_chance_mod may be negative. -/proc/get_zone_with_miss_chance(zone, var/mob/target, var/miss_chance_mod = 0, var/ranged_attack=0, var/force_hit = FALSE) +/// Hit and miss chance is calculated here. +/// Returns: null(miss) or zone(hit) +/// Has a variety of toggles. In short: +/// always_hit: Miss chance is not calculateed. Always hit the zone it's targeting. (Default: FALSE) +/// user_misses: If Player Characters are subjected to RNG hitchance on other Player Characters (Default: FALSE) +/// mob_misses: If mobs are subjected to RNG hitchance on Player Characters (Default: TRUE) +/proc/get_zone_with_miss_chance(zone, mob/target, miss_chance_mod = 0, ranged_attack=0, force_hit = FALSE, mob/living/attacker) zone = check_zone(zone) + + /// Toggle for servers that desire to have attacks ALWAYS hit, since force_hit isn't always its default. + /// NOTE: This means that mobs will ALWAYS hit players and leads to much more punishing combat. + /// The system as is gives players an edge in PvE, while enabling always_hit gives mobs an edge in PvE. + var/always_hit = FALSE + if(always_hit) + return zone + if(!ranged_attack) // you cannot miss if your target is prone or restrained if(target.buckled || target.lying) @@ -149,16 +160,66 @@ if(force_hit) return zone - var/miss_chance = 10 - if (zone in base_miss_chance) - miss_chance = base_miss_chance[zone] - if (zone == "eyes" || zone == "mouth") - miss_chance = base_miss_chance["head"] - miss_chance = max(miss_chance + miss_chance_mod, 0) - if(prob(miss_chance)) - if(prob(70)) - return null - return pick(base_miss_chance) + //This is done here now, since previously it was just done in a dumb spot that made no sense. + //Even if you were Neo, anyone could land a blow on you. + var/has_evasion_chance = FALSE + if(isliving(target)) + var/mob/living/our_target = target + var/evasion_chance = our_target.get_evasion() + + if(!evasion_chance && !target.client) //If our target HAS no evasion chance and they're an NPC, we hit. + return zone + if(evasion_chance) + has_evasion_chance = TRUE + miss_chance_mod += evasion_chance + //However, get_accuracy_penalty() is also used in eyestab, open-hand clicking someone, and resolve_item_attack() + //The big one is resolve_item_attack(). It's the 'we are hit in melee combat' + //We are unable to include it here as it is dependent on the attacker, so we'll let it just continue being calculated where it is. + + if(has_evasion_chance && miss_chance_mod > 0 && prob(miss_chance_mod)) + return null + + if(!target.client) //If the target is an NPC, we will always hit (barring extreme circumstances like mobs having modified evasion, handled above). Removes baymiss against mobs. + return zone + + /// Toggle for servers that desire to have players able to miss each other. + /// This is if users are subjected to the same RNG hitchance against other players as mobs are. + /// By default, this is set to off, as evasion being calculated is (in my eyes) enough for PvP combat. + /// However, if you wish to enable it so there's miss chance, flip this FALSE to TRUE + var/user_misses = FALSE + if(!user_misses && attacker.client) + return zone + + /// Toggle for servers that desire to have mobs able to miss players. + /// If toggled on, mobs will have chances to miss players. + /// If toggled off, mobs will always hit each players, evasion-not-withstanding + /// This can make PvE combat feel better for players or introduce some randomization with PvP. + var/mob_misses = TRUE //Toggle to enable mob missing or not. + if(!mob_misses && !attacker.client) //If mob_misses is disabled, they land their blow on the zone they're targeting. + return zone + + //However, if a mob IS attacking a player, let's throw in some RNG into the mix to make it feel better for players. + //If a mob eats hits and dies, people are happy. + //If you shoot a mob point blank 10 times and every hit misses, people are upset (and rightfully so) + + + + var/randomization_chance = 10 //This can also be set to 0 to ensure mobs ALWAYS target the limb they're originally targeting. + /// First, we roll to see if we're going to target a random limb. + if(randomization_chance) //We got a 10% chance! Randomize where we're targeting! + zone = pick(base_miss_chance) + + // Second, we make sure to see if the place we are attacking is a valid area. + if(zone in base_miss_chance) + randomization_chance = base_miss_chance[zone] + + // Eyes and mouth can be targeted (although typically not by mobs) so we set it to the head. + else if (zone == "eyes" || zone == "mouth") + randomization_chance = base_miss_chance["head"] + + // Finally, now that we have our newfound zone, we see if we miss it or not! + if(prob(randomization_chance)) //If the mob rolled a miss chance? + return null //No hit! Player escapes unscathed! return zone diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 765568ff268..d01d71fcb26 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -417,7 +417,8 @@ if(one_handed_penalty >= 20) to_chat(user, span_warning("You struggle to keep \the [src] pointed at the correct position with just one hand!")) - accuracy = initial(accuracy) //Reset our accuracy + if(!zoom) //If we're not zoomed, reset our accuracy to our initial accuracy. + accuracy = initial(accuracy) //Reset our accuracy last_shot = world.time user.hud_used.update_ammo_hud(user, src) user.setClickCooldown(DEFAULT_QUICK_COOLDOWN) @@ -742,7 +743,7 @@ mouthshoot = 0 return -/obj/item/gun/proc/toggle_scope(var/zoom_amount=2.0) +/obj/item/gun/proc/toggle_scope(zoom_amount=2.0) //looking through a scope limits your periphereal vision //still, increase the view size by a tiny amount so that sniping isn't too restricted to NSEW var/zoom_offset = round(world.view * zoom_amount) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 4d368956e27..c900f9ac305 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -689,9 +689,13 @@ if(target_mob in impacted_mobs) return - //roll to-hit - miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), -100) - var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob), force_hit = !can_miss) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss + // Accuracy here is inverted as accuracy is being applied as a negative miss_chance_mod. + // This means that, accuracy negates evasion 1:1 when it comes to PvP combat (or for PvE combat if you give a mob natural evasion) + // Things that affect accuracy: gun_accuracy_mod species var (Bad Shot/Eagle Eye), Fear, Gun Accuracy. + // +accuracy = higher chance to hit through evasion. -accuracy = lower chance to hit through evasion. + // These ONLY matter if the mob you are attacking has evasion. + // The get_zone_with_miss_chance() proc is HIGHLY variable and can be changed server to server with multiple simple var switches built in without having to do specialty code or multiple edits. + var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, -accuracy, ranged_attack=(distance > 1 || original != target_mob), force_hit = !can_miss, attacker = firer) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss var/result = PROJECTILE_FORCE_MISS if(hit_zone)