diff --git a/code/__DEFINES/flags/do_after.dm b/code/__DEFINES/_flags/do_after.dm
similarity index 100%
rename from code/__DEFINES/flags/do_after.dm
rename to code/__DEFINES/_flags/do_after.dm
diff --git a/code/__DEFINES/flags/shields.dm b/code/__DEFINES/_flags/shields.dm
similarity index 100%
rename from code/__DEFINES/flags/shields.dm
rename to code/__DEFINES/_flags/shields.dm
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 8451dc4060..2200f3bb1a 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -245,7 +245,7 @@
var/slot = text2num(href_list["item"])
if(slot in check_obscured_slots())
to_chat(usr, "You can't reach that! Something is covering it.")
- return
+ return
if(href_list["pockets"])
var/strip_mod = 1
var/strip_silence = FALSE
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index e7be540eb9..2b334a8f71 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -7,12 +7,14 @@
buckle_lying = FALSE
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
/// Enable stamina combat
- combat_flags = COMBAT_FLAGS_DEFAULT
+ combat_flags = COMBAT_FLAGS_DEFAULT | COMBAT_FLAG_UNARMED_PARRY
status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH|CANSTAGGER
has_field_of_vision = FALSE //Handled by species.
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
+ block_parry_data = /datum/block_parry_data/unarmed/human
+
//Hair colour and style
var/hair_color = "000"
var/hair_style = "Bald"
@@ -71,3 +73,50 @@
var/lastpuke = 0
var/account_id
var/last_fire_update
+
+/// Unarmed parry data for human
+/datum/block_parry_data/unarmed/human
+ parry_respect_clickdelay = TRUE
+ parry_stamina_cost = 4
+ parry_attack_types = ATTACK_TYPE_UNARMED
+ parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK | PARRY_LOCK_ATTACKING
+
+ parry_time_windup = 0
+ parry_time_spindown = 1
+ parry_time_active = 5
+
+ parry_time_perfect = 1
+ parry_time_perfect_leeway = 1
+ parry_imperfect_falloff_percent = 20
+ parry_efficiency_perfect = 100
+
+ parry_efficiency_considered_successful = 0.01
+ parry_efficiency_to_counterattack = 0.01
+ parry_max_attacks = 3
+ parry_cooldown = 30
+ parry_failed_stagger_duration = 0
+ parry_failed_clickcd_duration = 0.4
+
+ parry_data = list( // yeah it's snowflake
+ "HUMAN_PARRY_STAGGER" = 3 SECONDS,
+ "HUMAN_PARRY_PUNCH" = TRUE,
+ "HUMAN_PARRY_MININUM_EFFICIENCY" = 0.9
+ )
+
+/mob/living/carbon/human/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
+ var/datum/block_parry_data/D = return_block_parry_datum(block_parry_data)
+ if(parry_efficiency >= D.parry_data["HUMAN_PARRY_MINIMUM_EFFICIENCY"])
+ visible_message("[src] strikes back perfectly at [attacker], staggering them!")
+ if(D.parry_data["HUMAN_PARRY_PUNCH"])
+ // snowflake, yay
+ if(ishuman(attacker) && dna?.species)
+ dna.species.harm(src, attacker, mind?.martial_art, TRUE)
+ else
+ var/old = a_intent
+ a_intent = INTENT_HARM
+ UnarmedAttack(attacker)
+ a_intent = old
+ var/mob/living/L = attacker
+ if(istype(L))
+ L.Stagger(D.parry_data["HUMAN_PARRY_STAGGER"])
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index efff888c41..715a22bc2b 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1444,7 +1444,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
target.grabbedby(user)
return 1
-/datum/species/proc/harm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
+/datum/species/proc/harm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style, parry = FALSE)
if(!attacker_style && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "You don't want to harm [target]!")
return FALSE
@@ -1456,10 +1456,11 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
target_message = "[target] blocks your attack!")
return FALSE
- if(HAS_TRAIT(user, TRAIT_PUGILIST))//CITADEL CHANGE - makes punching cause staminaloss but funny martial artist types get a discount
- user.adjustStaminaLossBuffered(1.5)
- else
- user.adjustStaminaLossBuffered(3.5)
+ if(!parry)
+ if(HAS_TRAIT(user, TRAIT_PUGILIST))//CITADEL CHANGE - makes punching cause staminaloss but funny martial artist types get a discount
+ user.adjustStaminaLossBuffered(1.5)
+ else
+ user.adjustStaminaLossBuffered(3.5)
if(attacker_style && attacker_style.harm_act(user,target))
return TRUE
@@ -1497,13 +1498,16 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
var/miss_chance = 100//calculate the odds that a punch misses entirely. considers stamina and brute damage of the puncher. punches miss by default to prevent weird cases
- if(user.dna.species.punchdamagelow)
- if(atk_verb == ATTACK_EFFECT_KICK) //kicks never miss (provided your species deals more than 0 damage)
- miss_chance = 0
- else if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists have a flat 10% miss chance
- miss_chance = 10
- else
- miss_chance = min(10 + max(puncherstam * 0.5, puncherbrute * 0.5), 100) //probability of miss has a base of 10, and modified based on half brute total. Capped at max 100 to prevent weirdness in prob()
+ if(parry)
+ miss_chance = 0
+ else
+ if(user.dna.species.punchdamagelow)
+ if(atk_verb == ATTACK_EFFECT_KICK) //kicks never miss (provided your species deals more than 0 damage)
+ miss_chance = 0
+ else if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists have a flat 10% miss chance
+ miss_chance = 10
+ else
+ miss_chance = min(10 + max(puncherstam * 0.5, puncherbrute * 0.5), 100) //probability of miss has a base of 10, and modified based on half brute total. Capped at max 100 to prevent weirdness in prob()
if(!damage || !affecting || prob(miss_chance))//future-proofing for species that have 0 damage/weird cases where no zone is targeted
playsound(target.loc, user.dna.species.miss_sound, 25, TRUE, -1)
diff --git a/tgstation.dme b/tgstation.dme
index c43a6de0e4..09f1c04b7c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -119,8 +119,10 @@
#include "code\__DEFINES\wall_dents.dm"
#include "code\__DEFINES\wires.dm"
#include "code\__DEFINES\_flags\_flags.dm"
+#include "code\__DEFINES\_flags\do_after.dm"
#include "code\__DEFINES\_flags\item_flags.dm"
#include "code\__DEFINES\_flags\obj_flags.dm"
+#include "code\__DEFINES\_flags\shields.dm"
#include "code\__DEFINES\admin\keybindings.dm"
#include "code\__DEFINES\combat\attack_types.dm"
#include "code\__DEFINES\combat\block.dm"
@@ -128,8 +130,6 @@
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\signals.dm"
-#include "code\__DEFINES\flags\do_after.dm"
-#include "code\__DEFINES\flags\shields.dm"
#include "code\__DEFINES\mapping\maploader.dm"
#include "code\__DEFINES\material\worth.dm"
#include "code\__DEFINES\misc\return_values.dm"