diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 1556c834e49..1c90711d70c 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -70,6 +70,11 @@ #define PROJECTILE_ATTACK 3 #define THROWN_PROJECTILE_ATTACK 4 #define LEAP_ATTACK 5 +#define ALL_ATTACK_TYPES list(MELEE_ATTACK, UNARMED_ATTACK, PROJECTILE_ATTACK, THROWN_PROJECTILE_ATTACK, LEAP_ATTACK) +#define NON_PROJECTILE_ATTACKS list(MELEE_ATTACK, UNARMED_ATTACK, LEAP_ATTACK) + +// the standard parry time out time +#define PARRY_DEFAULT_TIMEOUT 1 SECONDS //attack visual effects #define ATTACK_EFFECT_PUNCH "punch" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 55f3d746e15..e968543612f 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -529,6 +529,7 @@ #define COMPONENT_BLOCK_MARK_RETRIEVAL (1<<0) ///from base of obj/item/hit_reaction(): (list/args) #define COMSIG_ITEM_HIT_REACT "item_hit_react" + #define COMPONENT_BLOCK_SUCCESSFUL (1 << 0) ///called on item when crossed by something (): (/atom/movable, mob/living/crossed) #define COMSIG_ITEM_WEARERCROSSED "wearer_crossed" ///called on item when microwaved (): (obj/machinery/microwave/M) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 500d64b746b..032e23d74e5 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -25,7 +25,7 @@ #define CLICK_CD_HANDCUFFED 10 #define CLICK_CD_TKSTRANGLE 10 #define CLICK_CD_POINT 10 -#define CLICK_CD_RESIST 20 +#define CLICK_CD_RESIST 8 #define CLICK_CD_CLICK_ABILITY 6 #define CLICK_CD_RAPID 2 diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm new file mode 100644 index 00000000000..ea3e41e65bb --- /dev/null +++ b/code/datums/components/parry.dm @@ -0,0 +1,96 @@ +/datum/component/parry + /// the world.time we last parried at + var/time_parried + /// the max time since `time_parried` that the shield is still considered "active" + var/parry_time_out_time + + /// the flat amount of damage the shield user takes per non-perfect parry + var/stamina_constant + /// stamina_coefficient * damage * time_since_time_parried = stamina damage taken per non perfect parry + var/stamina_coefficient + /// the attack types that are considered for parrying + var/parryable_attack_types + /// the time between parry attempts + var/parry_cooldown + +/datum/component/parry/RegisterWithParent() + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(equipped)) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(dropped)) + RegisterSignal(parent, COMSIG_ITEM_HIT_REACT, PROC_REF(attempt_parry)) + +/datum/component/parry/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED) + UnregisterSignal(parent, COMSIG_ITEM_DROPPED) + UnregisterSignal(parent, COMSIG_ITEM_HIT_REACT) + var/obj/item/I = parent + if(ismob(I.loc)) + UnregisterSignal(I.loc, COMSIG_LIVING_RESIST) + +/datum/component/parry/Initialize(_stamina_constant = 0, _stamina_coefficient = 0, _parry_time_out_time = PARRY_DEFAULT_TIMEOUT, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = 2 SECONDS) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + parry_time_out_time = _parry_time_out_time + stamina_constant = _stamina_constant + stamina_coefficient = _stamina_coefficient + parry_cooldown = _parry_cooldown + if(islist(_parryable_attack_types)) + parryable_attack_types = _parryable_attack_types + else + parryable_attack_types = list(_parryable_attack_types) + +/datum/component/parry/proc/equipped(datum/source, mob/user, slot) + SIGNAL_HANDLER + if(slot in list(slot_l_hand, slot_r_hand)) + RegisterSignal(user, COMSIG_LIVING_RESIST, PROC_REF(start_parry)) + else + UnregisterSignal(user, COMSIG_LIVING_RESIST) + +/datum/component/parry/proc/dropped(datum/source, mob/user) + SIGNAL_HANDLER + UnregisterSignal(user, COMSIG_LIVING_RESIST) + +/datum/component/parry/proc/start_parry(mob/living/L) + SIGNAL_HANDLER + var/time_since_parry = world.time - time_parried + if(time_since_parry < parry_cooldown) // stops spam + return + + time_parried = world.time + L.do_attack_animation(L, used_item = parent) + +/datum/component/parry/proc/attempt_parry(datum/source, mob/living/carbon/human/owner, atom/movable/hitby, damage = 0, attack_type = MELEE_ATTACK) + SIGNAL_HANDLER + if(!(attack_type in parryable_attack_types)) + return + var/time_since_parry = world.time - time_parried + if(time_since_parry > parry_time_out_time) + return + + var/armour_penetration_percentage = 0 + var/armour_penetration_flat = 0 + + if(isitem(hitby)) + var/obj/item/I = hitby + armour_penetration_percentage = I.armour_penetration_percentage + armour_penetration_flat = I.armour_penetration_flat + + if(armour_penetration_flat + armour_penetration_percentage >= 100) + return + + var/stamina_damage = stamina_coefficient * (((time_since_parry / parry_time_out_time) + armour_penetration_percentage / 100) * (damage + armour_penetration_flat)) + stamina_constant + + var/sound_to_play + if(attack_type == PROJECTILE_ATTACK) + sound_to_play = pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg') + else + sound_to_play = 'sound/weapons/parry.ogg' + + playsound(owner, sound_to_play, clamp(stamina_damage, 40, 120)) + + owner.adjustStaminaLoss(stamina_damage) + if(owner.getStaminaLoss() < 100) + return COMPONENT_BLOCK_SUCCESSFUL + + + diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm index d3d012f0e30..89c8aea2c73 100644 --- a/code/game/gamemodes/cult/cult_items.dm +++ b/code/game/gamemodes/cult/cult_items.dm @@ -505,7 +505,7 @@ // Hit by a melee weapon or blocked a projectile . = ..() - if(.) // 50|50 chance + if(.) // they did parry the attack playsound(src, 'sound/weapons/parry.ogg', 100, TRUE) if(illusions > 0) illusions-- @@ -568,7 +568,6 @@ throwforce = 30 throw_speed = 2 armour_penetration_percentage = 50 - block_chance = 30 attack_verb = list("attacked", "impaled", "stabbed", "torn", "gored") sharp = TRUE no_spin_thrown = TRUE @@ -576,6 +575,10 @@ needs_permit = TRUE var/datum/action/innate/cult/spear/spear_act +/obj/item/twohanded/cult_spear/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.4, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (2 / 3) SECONDS ) // 0.666667 seconds for 60% uptime. + /obj/item/twohanded/cult_spear/Destroy() if(spear_act) qdel(spear_act) @@ -626,20 +629,6 @@ playsound(T, 'sound/effects/glassbr3.ogg', 100) qdel(src) -/obj/item/twohanded/cult_spear/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(wielded) - final_block_chance *= 2 - if(prob(final_block_chance)) - if(attack_type == PROJECTILE_ATTACK) - owner.visible_message("[owner] deflects [attack_text] with [src]!") - playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, TRUE) - return TRUE - else - playsound(src, 'sound/weapons/parry.ogg', 100, TRUE) - owner.visible_message("[owner] parries [attack_text] with [src]!") - return TRUE - return FALSE - /obj/item/twohanded/cult_spear/attack(mob/living/M, mob/living/user, def_zone) . = ..() var/datum/status_effect/cult_stun_mark/S = M.has_status_effect(STATUS_EFFECT_CULT_STUN) diff --git a/code/game/machinery/computer/HolodeckControl.dm b/code/game/machinery/computer/HolodeckControl.dm index 4c9142bc298..34f8d68f912 100644 --- a/code/game/machinery/computer/HolodeckControl.dm +++ b/code/game/machinery/computer/HolodeckControl.dm @@ -440,7 +440,10 @@ throwforce = 10 w_class = WEIGHT_CLASS_BULKY attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 + +/obj/item/holo/claymore/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = NON_PROJECTILE_ATTACKS) /obj/item/holo/claymore/blue @@ -462,9 +465,12 @@ throwforce = 0 w_class = WEIGHT_CLASS_SMALL armour_penetration_percentage = 50 - block_chance = 50 var/active = FALSE +/obj/item/holo/esword/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = NON_PROJECTILE_ATTACKS) + /obj/item/holo/esword/green/New() ..() item_color = "green" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index fa3a80a8a0b..1c16ce13611 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -81,7 +81,6 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons var/breakouttime = 0 var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES - var/block_chance = 0 var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom // Needs to be in /obj/item because corgis can wear a lot of @@ -399,11 +398,10 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons return ..() /obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) - if(prob(final_block_chance)) + if((SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, owner, hitby, damage, attack_type) & COMPONENT_BLOCK_SUCCESSFUL) || prob(final_block_chance)) owner.visible_message("[owner] blocks [attack_text] with [src]!") - return 1 - return 0 + return TRUE + return FALSE // Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. // Returns TRUE on success, FALSE on failure. diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm index 630bfc41531..a0edc49db52 100644 --- a/code/game/objects/items/weapons/holy_weapons.dm +++ b/code/game/objects/items/weapons/holy_weapons.dm @@ -130,7 +130,10 @@ w_class = WEIGHT_CLASS_HUGE force = 5 slot_flags = SLOT_BACK - block_chance = 50 + +/obj/item/nullrod/staff/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) /obj/item/nullrod/staff/blue name = "blue holy staff" @@ -144,11 +147,14 @@ desc = "A weapon fit for a crusade!" w_class = WEIGHT_CLASS_BULKY slot_flags = SLOT_BACK|SLOT_BELT - block_chance = 30 sharp = TRUE hitsound = 'sound/weapons/bladeslice.ogg' attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") +/obj/item/nullrod/claymore/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.7, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (7 / 3) SECONDS) // 2.3333 seconds of cooldown for 30% uptime + /obj/item/nullrod/claymore/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) if(attack_type == PROJECTILE_ATTACK) final_block_chance = 0 //Don't bring a sword to a gunfight @@ -381,7 +387,6 @@ desc = "A long, tall staff made of polished wood. Traditionally used in ancient old-Earth martial arts, now used to harass the clown." w_class = WEIGHT_CLASS_BULKY force = 13 - block_chance = 40 slot_flags = SLOT_BACK sharp = FALSE hitsound = "swing_hit" @@ -389,6 +394,10 @@ icon_state = "bostaff0" item_state = "bostaff0" +/obj/item/nullrod/claymore/bostaff/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.4, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (2 / 3) SECONDS ) // will remove the other component, 0.666667 seconds for 60% uptime. + /obj/item/nullrod/tribal_knife name = "arrhythmic knife" icon_state = "crysknife" @@ -531,7 +540,6 @@ w_class = WEIGHT_CLASS_HUGE force = 5 slot_flags = SLOT_BACK - block_chance = 50 var/team_color = "red" var/obj/item/clothing/suit/hooded/chaplain_hoodie/missionary_robe/robes = null //the robes linked with this staff @@ -547,6 +555,7 @@ icon_state = "godstaff-[team_color]" item_state = "godstaff-[team_color]" name = "[team_color] holy staff" + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) /obj/item/nullrod/missionary_staff/Destroy() if(robes) //delink on destruction diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index 513a930968c..f7d22bfcfd1 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -124,7 +124,6 @@ armour_penetration_percentage = 50 armour_penetration_flat = 10 origin_tech = "combat=3;magnets=4;syndicate=4" - block_chance = 50 sharp = TRUE var/hacked = FALSE @@ -132,6 +131,7 @@ ..() if(item_color == null) item_color = pick("red", "blue", "green", "purple") + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) /obj/item/melee/energy/sword/detailed_examine() return "The energy sword is a very strong melee weapon, capable of severing limbs easily, if they are targeted. It can also has a chance \ diff --git a/code/game/objects/items/weapons/melee/misc.dm b/code/game/objects/items/weapons/melee/misc.dm index 93911c9443c..f248ebf81d8 100644 --- a/code/game/objects/items/weapons/melee/misc.dm +++ b/code/game/objects/items/weapons/melee/misc.dm @@ -29,7 +29,6 @@ force = 15 throwforce = 10 w_class = WEIGHT_CLASS_BULKY - block_chance = 50 armour_penetration_percentage = 75 sharp = TRUE origin_tech = "combat=5" @@ -38,10 +37,9 @@ materials = list(MAT_METAL = 1000) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF // Theft targets should be hard to destroy -/obj/item/melee/rapier/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 //Don't bring a sword to a gunfight - return ..() +/obj/item/melee/rapier/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = NON_PROJECTILE_ATTACKS) /obj/item/melee/icepick name = "ice pick" @@ -108,13 +106,17 @@ w_class = WEIGHT_CLASS_BULKY force = 25 armour_penetration_flat = 50 - block_chance = 50 sharp = TRUE ///enchantment holder, gives it unique on hit effects. var/datum/enchantment/enchant = null ///the cooldown and power of enchantments are multiplied by this var when its applied var/power = 1 +/obj/item/melee/spellblade/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) + + /obj/item/melee/spellblade/Destroy() QDEL_NULL(enchant) return ..() diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm index 171fa84a3ef..88ca18d01d8 100644 --- a/code/game/objects/items/weapons/shields.dm +++ b/code/game/objects/items/weapons/shields.dm @@ -1,11 +1,15 @@ /obj/item/shield name = "shield" - block_chance = 50 armor = list(MELEE = 50, BULLET = 50, LASER = 50, ENERGY = 0, BOMB = 30, BIO = 0, RAD = 0, FIRE = 80, ACID = 70) +/obj/item/shield/proc/add_parry_component() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) + +/obj/item/shield/Initialize(mapload) + . = ..() + add_parry_component() + /obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == THROWN_PROJECTILE_ATTACK) - final_block_chance += 30 if(attack_type == LEAP_ATTACK) final_block_chance = 100 return ..() @@ -25,9 +29,9 @@ attack_verb = list("shoved", "bashed") var/cooldown = 0 //shield bash cooldown. based on world.time -/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob, params) +/obj/item/shield/riot/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/melee/baton)) - if(cooldown < world.time - 25) + if(cooldown < world.time - 2.5 SECONDS) user.visible_message("[user] bashes [src] with [W]!") playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) cooldown = world.time @@ -43,9 +47,11 @@ /obj/item/shield/riot/roman/fake desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." - block_chance = 0 armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) +/obj/item/shield/riot/roman/fake/add_parry_component() + return + /obj/item/shield/riot/buckler name = "wooden buckler" desc = "A medieval wooden buckler." @@ -54,7 +60,10 @@ materials = list() origin_tech = "materials=1;combat=3;biotech=2" resistance_flags = FLAMMABLE - block_chance = 30 + +/obj/item/shield/riot/buckler/add_parry_component() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.7, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (7 / 3) SECONDS) // 2.3333 seconds of cooldown for 30% uptime + /obj/item/shield/energy name = "energy combat shield" @@ -69,6 +78,9 @@ attack_verb = list("shoved", "bashed") var/active = FALSE +/obj/item/shield/energy/add_parry_component() + return + /obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) if(istype(hitby, /obj/item/projectile)) var/obj/item/projectile/P = hitby @@ -111,6 +123,7 @@ if(!forced) add_fingerprint(user) return + /obj/item/shield/riot/tele name = "telescopic shield" desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." @@ -127,7 +140,7 @@ /obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) if(active) return ..() - return 0 + return FALSE // by not calling the parent the hit_reaction signal is never sent /obj/item/shield/riot/tele/attack_self(mob/living/user) active = !active diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm index 0a5fc33f6bf..e645be44cd9 100644 --- a/code/game/objects/items/weapons/twohanded.dm +++ b/code/game/objects/items/weapons/twohanded.dm @@ -285,7 +285,6 @@ armour_penetration_flat = 10 origin_tech = "magnets=4;syndicate=5" attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 75 sharp_when_wielded = TRUE // only sharp when wielded max_integrity = 200 armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 70) @@ -299,6 +298,7 @@ ..() if(!blade_color) blade_color = pick("red", "blue", "green", "purple") + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.25, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (1 / 3) SECONDS) // 0.3333 seconds of cooldown for 75% uptime /obj/item/twohanded/dualsaber/update_icon_state() if(wielded) @@ -838,7 +838,6 @@ force_wielded = 22 damtype = BURN armour_penetration_percentage = 50 - block_chance = 50 sharp = TRUE attack_effect_override = ATTACK_EFFECT_CLAW hitsound = 'sound/weapons/bladeslice.ogg' @@ -850,6 +849,7 @@ /obj/item/twohanded/required/pyro_claws/Initialize(mapload) . = ..() START_PROCESSING(SSobj, src) + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) /obj/item/twohanded/required/pyro_claws/Destroy() STOP_PROCESSING(SSobj, src) diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm index 2f91305ed3f..e2efee73f4f 100644 --- a/code/game/objects/items/weapons/weaponry.dm +++ b/code/game/objects/items/weapons/weaponry.dm @@ -55,11 +55,14 @@ sharp = TRUE w_class = WEIGHT_CLASS_NORMAL attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 max_integrity = 200 armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 50) resistance_flags = FIRE_PROOF +/obj/item/claymore/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) + /obj/item/claymore/suicide_act(mob/user) user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide.") return BRUTELOSS @@ -82,12 +85,15 @@ w_class = WEIGHT_CLASS_BULKY hitsound = 'sound/weapons/bladeslice.ogg' attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 max_integrity = 200 armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 50) resistance_flags = FIRE_PROOF needs_permit = TRUE +/obj/item/katana/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) + /obj/item/katana/suicide_act(mob/user) user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku.") diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 4eb0d468584..145c995e493 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -360,12 +360,12 @@ desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield." flags = NODROP | DROPDEL icon_state = "ling_shield" - block_chance = 50 var/remaining_uses //Set by the changeling ability. /obj/item/shield/changeling/Initialize(mapload) . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) if(ismob(loc)) loc.visible_message("The end of [loc.name]\'s hand inflates rapidly, forming a huge shield-like mass!", "We inflate our hand into a strong shield.", "You hear organic matter ripping and tearing!") diff --git a/code/modules/martial_arts/martial.dm b/code/modules/martial_arts/martial.dm index 2000e9af89e..6fc6ec4abc8 100644 --- a/code/modules/martial_arts/martial.dm +++ b/code/modules/martial_arts/martial.dm @@ -323,7 +323,10 @@ throw_speed = 2 attack_verb = list("smashed", "slammed", "whacked", "thwacked") icon_state = "bostaff0" - block_chance = 50 + +/obj/item/twohanded/bostaff/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) /obj/item/twohanded/bostaff/update_icon_state() icon_state = "bostaff[wielded]" diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 6715d969eae..136c3315409 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -503,7 +503,6 @@ force = 15 armour_penetration_percentage = 40 armour_penetration_flat = 10 - block_chance = 50 sharp = TRUE w_class = WEIGHT_CLASS_HUGE attack_verb = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") @@ -524,6 +523,7 @@ /obj/item/cursed_katana/Initialize(mapload) . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = NON_PROJECTILE_ATTACKS) for(var/combo in combo_list) var/list/combo_specifics = combo_list[combo] var/step_string = english_list(combo_specifics[COMBO_STEPS]) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 79c13005905..3b11d36c259 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -215,21 +215,11 @@ emp_act //End Here -#define BLOCK_CHANCE_CALCULATION(hand_block_chance, armour_penetration_flat, armour_penetration_percentage, block_chance_modifier) clamp((hand_block_chance * ((100-armour_penetration_percentage) / 100)) - max(armour_penetration_flat, 0) + block_chance_modifier, 0, 100) /mob/living/carbon/human/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration_flat = 0, armour_penetration_percentage = 0) - var/block_chance_modifier = round(damage / -3) - var/l_block_chance = 0 - var/r_block_chance = 0 + var/obj/item/shield = get_best_shield() - if(l_hand) - l_block_chance = BLOCK_CHANCE_CALCULATION(l_hand.block_chance, armour_penetration_flat, armour_penetration_percentage, block_chance_modifier) - if(r_hand) - r_block_chance = BLOCK_CHANCE_CALCULATION(r_hand.block_chance, armour_penetration_flat, armour_penetration_percentage, block_chance_modifier) - - if(l_block_chance > r_block_chance && l_hand.hit_reaction(src, AM, attack_text, l_block_chance, damage, attack_type)) - return TRUE - else if(r_block_chance > 0 && r_hand.hit_reaction(src, AM, attack_text, r_block_chance, damage, attack_type)) + if(shield?.hit_reaction(src, AM, attack_text, 0, damage, attack_type)) return TRUE if(wear_suit && wear_suit.hit_reaction(src, AM, attack_text, 0, damage, attack_type)) @@ -243,7 +233,18 @@ emp_act return FALSE -#undef BLOCK_CHANCE_CALCULATION +/mob/living/carbon/human/proc/get_best_shield() + var/datum/component/parry/left_hand_parry = l_hand?.GetComponent(/datum/component/parry) + var/datum/component/parry/right_hand_parry = r_hand?.GetComponent(/datum/component/parry) + if(!right_hand_parry && !left_hand_parry) + return null // no parry component + + if(right_hand_parry && left_hand_parry) + if(right_hand_parry.stamina_coefficient > left_hand_parry.stamina_coefficient) // try and parry with your best item + return left_hand_parry.parent + else + return right_hand_parry.parent + return right_hand_parry?.parent || left_hand_parry?.parent // parry with whichever hand has an item that can parry /mob/living/carbon/human/proc/check_block() if(mind && mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 30feec4f219..db00c6a031a 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -148,9 +148,7 @@ if(!(M.status_flags & CANPUSH)) return TRUE //anti-riot equipment is also anti-push - if(M.r_hand && (prob(M.r_hand.block_chance * 2)) && !isclothing(M.r_hand)) - return TRUE - if(M.l_hand && (prob(M.l_hand.block_chance * 2)) && !isclothing(M.l_hand)) + if(M.r_hand?.GetComponent(/datum/component/parry) || M.l_hand?.GetComponent(/datum/component/parry)) return TRUE //Called when we bump into an obj diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 0366951f59a..bea0bc72d55 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -141,6 +141,7 @@ name = "instagib laser" icon_state = "purple_laser" damage = 200 + armour_penetration_percentage = 100 damage_type = BURN impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser light_color = LIGHT_COLOR_PURPLE diff --git a/paradise.dme b/paradise.dme index 474344ab65e..81f7f134504 100644 --- a/paradise.dme +++ b/paradise.dme @@ -360,6 +360,7 @@ #include "code\datums\components\material_container.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\paintable.dm" +#include "code\datums\components\parry.dm" #include "code\datums\components\persistent_overlay.dm" #include "code\datums\components\proximity_monitor.dm" #include "code\datums\components\radioactive.dm"