diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 0568963ccf3..3cb1af15965 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -530,6 +530,7 @@ ///from base of obj/item/hit_reaction(): (list/args) #define COMSIG_ITEM_HIT_REACT "item_hit_react" #define COMPONENT_BLOCK_SUCCESSFUL (1 << 0) + #define COMPONENT_BLOCK_PERFECT (1 << 2) ///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/datums/components/parry.dm b/code/datums/components/parry.dm index ea3e41e65bb..6488de14a01 100644 --- a/code/datums/components/parry.dm +++ b/code/datums/components/parry.dm @@ -12,6 +12,8 @@ var/parryable_attack_types /// the time between parry attempts var/parry_cooldown + ///Do we wish to mute the parry sound? + var/no_parry_sound /datum/component/parry/RegisterWithParent() RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(equipped)) @@ -26,7 +28,7 @@ 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) +/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, _no_parry_sound = FALSE) if(!isitem(parent)) return COMPONENT_INCOMPATIBLE @@ -34,6 +36,7 @@ stamina_constant = _stamina_constant stamina_coefficient = _stamina_coefficient parry_cooldown = _parry_cooldown + no_parry_sound = _no_parry_sound if(islist(_parryable_attack_types)) parryable_attack_types = _parryable_attack_types else @@ -61,6 +64,7 @@ /datum/component/parry/proc/attempt_parry(datum/source, mob/living/carbon/human/owner, atom/movable/hitby, damage = 0, attack_type = MELEE_ATTACK) SIGNAL_HANDLER + var/was_perfect = FALSE if(!(attack_type in parryable_attack_types)) return var/time_since_parry = world.time - time_parried @@ -80,17 +84,22 @@ 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' + if(!no_parry_sound) + 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)) + playsound(owner, sound_to_play, clamp(stamina_damage, 40, 120)) + if(time_since_parry <= parry_time_out_time * 0.5) // a perfect parry + was_perfect = TRUE owner.adjustStaminaLoss(stamina_damage) if(owner.getStaminaLoss() < 100) - return COMPONENT_BLOCK_SUCCESSFUL + if(!was_perfect) + return COMPONENT_BLOCK_SUCCESSFUL + return (COMPONENT_BLOCK_SUCCESSFUL | COMPONENT_BLOCK_PERFECT) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 1c16ce13611..21308002d7f 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -81,7 +81,8 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons var/breakouttime = 0 var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES - 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 + /// Used to give a reaction chance on hit that is not a block. If less than 0, will remove the block message, allowing overides. + var/hit_reaction_chance = 0 // Needs to be in /obj/item because corgis can wear a lot of // non-clothing items @@ -398,9 +399,11 @@ 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) - 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 TRUE + var/signal_result = SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, owner, hitby, damage, attack_type) + prob(final_block_chance) + if(signal_result != 0) + if(hit_reaction_chance >= 0) //Normally used for non blocking hit reactions, but also used for displaying block message on actual blocks + owner.visible_message("[owner] blocks [attack_text] with [src]!") + return signal_result return FALSE // Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 2cf35534ed2..bec8484be9e 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -32,8 +32,12 @@ emp_act return -1 //Shields - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration_flat, P.armour_penetration_percentage)) + var/shield_check_result = check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration_flat, P.armour_penetration_percentage) + if(shield_check_result == 1) return 2 + else if(shield_check_result == -1) + P.reflect_back(src) + return -1 if(mind?.martial_art?.deflection_chance) //Some martial arts users can deflect projectiles! if(!IS_HORIZONTAL(src) && !HAS_TRAIT(src, TRAIT_HULK) && mind.martial_art.try_deflect(src)) //But only if they're not lying down, and hulks can't do it @@ -219,9 +223,11 @@ emp_act /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/obj/item/shield = get_best_shield() - - if(shield?.hit_reaction(src, AM, attack_text, 0, damage, attack_type)) + var/shield_result = shield?.hit_reaction(src, AM, attack_text, 0, damage, attack_type) + if(shield_result >= 1) return TRUE + if(shield_result == -1) + return -1 if(wear_suit && wear_suit.hit_reaction(src, AM, attack_text, 0, damage, attack_type)) return TRUE diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index a729dae4d2c..50065876b82 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -280,3 +280,14 @@ materials = list(MAT_GOLD = 5000, MAT_URANIUM = 4000, MAT_METAL = 5000, MAT_TITANIUM = 2000, MAT_BLUESPACE = 2000) build_path = /obj/item/weaponcrafting/gunkit/u_ionsilencer category = list("Weapons") + +/datum/design/v1_arm + name = "Vortex arm implant shell" + desc = "A shell to make an arm able to parry, reflect, and boost the power of incoming projectiles." + id = "v1_arm" + req_tech = list("combat" = 7, "magnets" = 6, "engineering" = 6, "biotech" = 7) + build_type = PROTOLATHE + materials = list(MAT_GOLD = 5000, MAT_URANIUM = 4000, MAT_METAL = 10000, MAT_TITANIUM = 2000, MAT_BLUESPACE = 2000) + reagents_list = list("blood" = 50) + build_path = /obj/item/v1_arm_shell + category = list("Weapons") diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm index 9ac62b05403..49c46c6cbf8 100644 --- a/code/modules/surgery/organs/augments_arms.dm +++ b/code/modules/surgery/organs/augments_arms.dm @@ -411,3 +411,127 @@ contents = newlist(/obj/item/mop/advanced) action_icon = list(/datum/action/item_action/organ_action/toggle = 'icons/obj/janitor.dmi') action_icon_state = list(/datum/action/item_action/organ_action/toggle = "advmop") + +/obj/item/organ/internal/cyberimp/arm/v1_arm + name = "vortex feedback arm implant" + desc = "An implant, that when deployed surrounds the users arm in armor and circuitry, allowing them to redirect nearby projectiles with feedback from the vortex anomaly core." + origin_tech = "combat=6;magnets=6;biotech=6;engineering=6" + icon = 'icons/obj/items.dmi' + icon_state = "v1_arm" + parent_organ = "l_arm" //Left arm by default + slot = "l_arm_device" + + contents = newlist(/obj/item/shield/v1_arm) + action_icon = list(/datum/action/item_action/organ_action/toggle = 'icons/obj/items.dmi') + action_icon_state = list(/datum/action/item_action/organ_action/toggle = "v1_arm") + var/disabled = FALSE + +/obj/item/organ/internal/cyberimp/arm/v1_arm/emp_act(severity) + if(emp_proof && !disabled) + return + disabled = TRUE + addtimer(VARSET_CALLBACK(src, disabled, FALSE), 10 SECONDS) + +/obj/item/organ/internal/cyberimp/arm/v1_arm/Extend(obj/item/item) + if(disabled) + to_chat(owner, "Your arm fails to extend!") + return FALSE + ..() + +/obj/item/organ/internal/cyberimp/arm/v1_arm/Retract() + if(disabled) + to_chat(owner, "Your arm fails to retract!") + return FALSE + ..() + +/obj/item/shield/v1_arm + name = "vortex feedback arm" //format is they are holding x + desc = "A modification to a users arm, allowing them to use a vortex core energy feedback, to parry, reflect, and even empower projectile attack. Rumors that it runs on the users blood are unconfirmed" + icon_state = "v1_arm" + item_state = "v1_arm" + sprite_sheets_inhand = list("Drask" = 'icons/mob/clothing/species/drask/held.dmi', "Vox" = 'icons/mob/clothing/species/vox/held.dmi') + force = 20 //bonk, not sharp + attack_verb = list("slamed", "punched", "parried", "judged", "styled on", "disrespected", "interupted", "gored") + hitsound = 'sound/effects/bang.ogg' + light_power = 3 + light_range = 0 + light_color = "#9933ff" + hit_reaction_chance = -1 + /// The damage the reflected projectile will be increased by + var/reflect_damage_boost = 10 + /// The cap of the reflected damage. Damage will not be increased above 50, however it will not be reduced to 50 either. + var/reflect_damage_cap = 50 + var/disabled = FALSE + var/force_when_disabled = 5 //still basically a metal pipe, just hard to move + + +/obj/item/shield/v1_arm/emp_act(severity) + if(disabled) + return + to_chat(loc, "Your arm seises up!") + disabled = TRUE + force = force_when_disabled + addtimer(CALLBACK(src, PROC_REF(reboot)), 10 SECONDS) + +/obj/item/shield/v1_arm/proc/reboot() + disabled = FALSE + force = initial(force) + +/obj/item/shield/v1_arm/add_parry_component() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.35, _parryable_attack_types = ALL_ATTACK_TYPES, _parry_cooldown = (1 / 3) SECONDS, _no_parry_sound = TRUE) // 0.3333 seconds of cooldown for 75% uptime, countered by ions and plasma pistols + +/obj/item/shield/v1_arm/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(disabled) + return FALSE + // Hit by a melee weapon or blocked a projectile + . = ..() + if(!.) // they did not block the attack + return + if(. == 1) // a normal block + owner.visible_message("[owner] blocks [attack_text] with [src]!") + playsound(src, 'sound/weapons/effects/ric3.ogg', 100, TRUE) + return TRUE + + set_light(3) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, set_light), 0), 0.25 SECONDS) + + if(istype(hitby, /obj/item/projectile)) + var/obj/item/projectile/P = hitby + if(P.shield_buster || istype(P, /obj/item/projectile/ion)) //EMP's and unpariable attacks, after all. + return FALSE + if(P.reflectability == REFLECTABILITY_NEVER) //only 1 magic spell does this, but hey, needed + owner.visible_message("[owner] blocks [attack_text] with [src]!") + playsound(src, 'sound/weapons/effects/ric3.ogg', 100, TRUE) + return TRUE + + P.damage = clamp((P.damage + 10), P.damage, reflect_damage_cap) + var/sound = pick('sound/effects/explosion1.ogg', 'sound/effects/explosion2.ogg', 'sound/effects/meteorimpact.ogg') + P.hitsound = sound + P.hitsound_wall = sound + P.add_overlay("parry") + playsound(src, 'sound/weapons/v1_parry.ogg', 100, TRUE) + owner.visible_message("[owner] parries [attack_text] with [src]!") + add_attack_logs(P.firer, src, "hit by [P.type] but got parried by [src]") + return -1 + + owner.visible_message("[owner] parries [attack_text] with [src]!") + playsound(src, 'sound/weapons/v1_parry.ogg', 100, TRUE) + if(isitem(hitby)) //Thrown items + var/obj/item/TT = hitby + addtimer(CALLBACK(TT, TYPE_PROC_REF(/atom/movable, throw_at), locateUID(TT.thrownby), 15, 15, owner), 0.1 SECONDS) //yeet that shit right back + return TRUE + melee_attack_chain(owner, hitby) + return TRUE + +/obj/item/v1_arm_shell + name = "vortex feedback arm implant frame" + desc = "An implant awaiting installation of a vortex anomaly core" + icon_state = "v1_arm" + +/obj/item/v1_arm_shell/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/assembly/signaler/anomaly/vortex)) + to_chat(user, "You insert [I] into the back of the hand, and the implant begins to boot up.") + new /obj/item/organ/internal/cyberimp/arm/v1_arm(get_turf(src)) + qdel(src) + qdel(I) + return ..() diff --git a/icons/mob/clothing/species/drask/held.dmi b/icons/mob/clothing/species/drask/held.dmi index 51e93e98469..fea2228adec 100644 Binary files a/icons/mob/clothing/species/drask/held.dmi and b/icons/mob/clothing/species/drask/held.dmi differ diff --git a/icons/mob/clothing/species/vox/held.dmi b/icons/mob/clothing/species/vox/held.dmi index 853076c23ae..973f8aa94cc 100644 Binary files a/icons/mob/clothing/species/vox/held.dmi and b/icons/mob/clothing/species/vox/held.dmi differ diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi index a0dfef8af58..c8db062af90 100644 Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi index 5d7537c3a20..12f380147fa 100644 Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi index af0a38e7590..5371bccda14 100644 Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index bc4eac79664..a042e446cf7 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/sound/weapons/v1_parry.ogg b/sound/weapons/v1_parry.ogg new file mode 100644 index 00000000000..5179015ffaf Binary files /dev/null and b/sound/weapons/v1_parry.ogg differ