diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 8f36b1d007b..ff7cecf169c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -409,7 +409,12 @@ var/list/global/slot_flags_enumeration = list( /obj/item/proc/ui_action_click() attack_self(usr) -/obj/item/proc/IsShield() +//RETURN VALUES +//handle_shield should return a positive value to indicate that the attack is blocked and should be prevented. +//If a negative value is returned, it should be treated as a special return value for bullet_act() and handled appropriately. +//For non-projectile attacks this usually means the attack is blocked. +//Otherwise should return 0 to indicate that the attack is not affected in any way. +/obj/item/proc/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") return 0 /obj/item/proc/get_loc_turf() diff --git a/code/game/objects/items/weapons/material/swords.dm b/code/game/objects/items/weapons/material/swords.dm index 8a93d59fe58..a8b0ea19a3b 100644 --- a/code/game/objects/items/weapons/material/swords.dm +++ b/code/game/objects/items/weapons/material/swords.dm @@ -11,8 +11,13 @@ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") hitsound = 'sound/weapons/bladeslice.ogg' -/obj/item/weapon/material/sword/IsShield() - return 1 +/obj/item/weapon/material/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + + if(default_parry_check(user, attacker, damage_source) && prob(50)) + user.visible_message("\The [user] parries [attack_text] with \the [src]!") + playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) + return 1 + return 0 /obj/item/weapon/material/sword/suicide_act(mob/user) viewers(user) << "[user] is falling on the [src.name]! It looks like \he's trying to commit suicide." diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm index b41a4175a79..6102c760260 100644 --- a/code/game/objects/items/weapons/material/twohanded.dm +++ b/code/game/objects/items/weapons/material/twohanded.dm @@ -71,6 +71,14 @@ O.unwield() return unwield() +//Allow a small chance of parrying melee attacks when wielded - maybe generalize this to other weapons someday +/obj/item/weapon/material/twohanded/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(wielded && default_parry_check(user, attacker, damage_source) && prob(15)) + user.visible_message("\The [user] parries [attack_text] with \the [src]!") + playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) + return 1 + return 0 + /obj/item/weapon/material/twohanded/update_icon() icon_state = "[base_icon][wielded]" item_state = icon_state @@ -167,51 +175,7 @@ else if(istype(A,/obj/effect/plant)) var/obj/effect/plant/P = A P.die_off() - -/* -/* - * Double-Bladed Energy Swords - Cheridan - */ - // Not sure what to do with this one, it won't work nicely with the material system, - // but I don't want to copypaste all the twohanded procs.. -/obj/item/weapon/material/twohanded/dualsaber - icon_state = "dualsaber0" - base_icon = "dualsaber" - name = "double-bladed energy sword" - desc = "Handle with care." - force = 3 - throwforce = 5.0 - throw_speed = 1 - throw_range = 5 - w_class = 2.0 - force_wielded = 30 - wieldsound = 'sound/weapons/saberon.ogg' - unwieldsound = 'sound/weapons/saberoff.ogg' - origin_tech = list(TECH_MAGNET = 3, TECH_ILLEGAL = 4) - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - sharp = 1 - edge = 1 - applies_material_colour = 0 - -/obj/item/weapon/material/twohanded/dualsaber/attack(target as mob, mob/living/user as mob) - ..() - if((CLUMSY in user.mutations) && (wielded) &&prob(40)) - user << "You twirl around a bit before losing your balance and impaling yourself on \the [src]." - user.take_organ_damage(20,25) - return - if((wielded) && prob(50)) - spawn(0) - for(var/i in list(1,2,4,8,4,2,1,2,4,8,4,2)) - user.set_dir(i) - sleep(1) - -/obj/item/weapon/material/twohanded/dualsaber/IsShield() - if(wielded) - return 1 - else - return 0 -*/ - + //spears, bay edition /obj/item/weapon/material/twohanded/spear icon_state = "spearglass0" diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index 43acc06908e..6a236e1b8f7 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -151,8 +151,14 @@ attack_verb = list() icon_state = initial(icon_state) -/obj/item/weapon/melee/energy/sword/IsShield() - if(active) +/obj/item/weapon/melee/energy/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(active && default_parry_check(user, attacker, damage_source) && prob(50)) + user.visible_message("\The [user] parries [attack_text] with \the [src]!") + + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, user.loc) + spark_system.start() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) return 1 return 0 diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm index 02b412232de..8b807e803fe 100644 --- a/code/game/objects/items/weapons/shields.dm +++ b/code/game/objects/items/weapons/shields.dm @@ -1,5 +1,52 @@ +//** Shield Helpers +//These are shared by various items that have shield-like behaviour + +//bad_arc is the ABSOLUTE arc of directions from which we cannot block. If you want to fix it to e.g. the user's facing you will need to rotate the dirs yourself. +/proc/check_shield_arc(mob/user, var/bad_arc, atom/damage_source = null, mob/attacker = null) + //check attack direction + var/attack_dir = 0 //direction from the user to the source of the attack + if(istype(damage_source, /obj/item/projectile)) + var/obj/item/projectile/P = damage_source + attack_dir = get_dir(get_turf(user), P.starting) + else if(attacker) + attack_dir = get_dir(get_turf(user), get_turf(attacker)) + else if(damage_source) + attack_dir = get_dir(get_turf(user), get_turf(damage_source)) + + if(!(attack_dir && (attack_dir & bad_arc))) + return 1 + return 0 + +/proc/default_parry_check(mob/user, mob/attacker, atom/damage_source) + //parry only melee attacks + if(istype(damage_source, /obj/item/projectile) || (attacker && get_dist(user, attacker) > 1) || user.incapacitated()) + return 0 + + //block as long as they are not directly behind us + var/bad_arc = reverse_direction(user.dir) //arc of directions from which we cannot block + if(!check_shield_arc(user, bad_arc, damage_source, attacker)) + return 0 + + return 1 + /obj/item/weapon/shield name = "shield" + var/base_block_chance = 50 + +/obj/item/weapon/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(user.incapacitated()) + return 0 + + //block as long as they are not directly behind us + var/bad_arc = reverse_direction(user.dir) //arc of directions from which we cannot block + if(check_shield_arc(user, bad_arc, damage_source, attacker)) + if(prob(get_block_chance(user, damage, damage_source, attacker))) + user.visible_message("\The [user] blocks [attack_text] with \the [src]!") + return 1 + return 0 + +/obj/item/weapon/shield/proc/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null) + return base_block_chance /obj/item/weapon/shield/riot name = "riot shield" @@ -18,17 +65,26 @@ attack_verb = list("shoved", "bashed") var/cooldown = 0 //shield bash cooldown. based on world.time - IsShield() - return 1 +/obj/item/weapon/shield/riot/handle_shield(mob/user) + . = ..() + if(.) playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1) - attackby(obj/item/weapon/W as obj, mob/user as mob) - if(istype(W, /obj/item/weapon/melee/baton)) - if(cooldown < world.time - 25) - user.visible_message("[user] bashes [src] with [W]!") - playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) - cooldown = world.time - else - ..() +/obj/item/weapon/shield/riot/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null) + if(istype(damage_source, /obj/item/projectile)) + var/obj/item/projectile/P = damage_source + //plastic shields do not stop bullets or lasers, even in space. Will block beanbags, rubber bullets, and stunshots just fine though. + if((is_sharp(P) && damage > 10) || istype(P, /obj/item/projectile/beam)) + return 0 + return base_block_chance + +/obj/item/weapon/shield/riot/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/weapon/melee/baton)) + if(cooldown < world.time - 25) + user.visible_message("[user] bashes [src] with [W]!") + playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) + cooldown = world.time + else + ..() /* * Energy Shield @@ -49,11 +105,23 @@ attack_verb = list("shoved", "bashed") var/active = 0 -/obj/item/weapon/shield/energy/IsShield() - if(active) - return 1 - else - return 0 +/obj/item/weapon/shield/energy/handle_shield(mob/user) + if(!active) + return 0 //turn it on first! + . = ..() + + if(.) + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, user.loc) + spark_system.start() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + +/obj/item/weapon/shield/energy/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null) + if(istype(damage_source, /obj/item/projectile)) + var/obj/item/projectile/P = damage_source + if((is_sharp(P) && damage > 10) || istype(P, /obj/item/projectile/beam)) + return (base_block_chance - round(damage / 3)) //block bullets and beams using the old block chance + return base_block_chance /obj/item/weapon/shield/energy/attack_self(mob/living/user as mob) if ((CLUMSY in user.mutations) && prob(50)) @@ -82,9 +150,10 @@ add_fingerprint(user) return + /obj/item/weapon/cloaking_device name = "cloaking device" - desc = "Use this to become invisible to the human eyesocket." + desc = "Use this to become invisible to the human eye." icon = 'icons/obj/device.dmi' icon_state = "shield0" var/active = 0.0 @@ -114,3 +183,4 @@ if(ismob(loc)) loc:update_icons() ..() + diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 4bcefa5c153..300422bc731 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -63,6 +63,26 @@ armor = list(melee = 10, bullet = 10, laser = 80, energy = 50, bomb = 0, bio = 0, rad = 0) siemens_coefficient = 0 +/obj/item/clothing/suit/armor/laserproof/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(istype(damage_source, /obj/item/projectile/energy) || istype(damage_source, /obj/item/projectile/beam)) + var/obj/item/projectile/P = damage_source + + var/reflectchance = 40 - round(damage/3) + if(!(def_zone in list("chest", "groin"))) + reflectchance /= 2 + if(P.starting && prob(reflectchance)) + visible_message("\The [user]'s [src.name] reflects [attack_text]!") + + // Find a turf near or on the original location to bounce to + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(user) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, user) + + return PROJECTILE_CONTINUE // complete projectile permutation + /obj/item/clothing/suit/armor/swat name = "swat suit" desc = "A heavily armored suit that protects against moderate damage. Used in special operations." @@ -113,9 +133,27 @@ slowdown = 1 armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0) -/obj/item/clothing/suit/armor/reactive/IsShield() - if(active) - return 1 +/obj/item/clothing/suit/armor/reactive/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(prob(50)) + user.visible_message("The reactive teleport system flings [user] clear of the attack!") + var/list/turfs = new/list() + for(var/turf/T in orange(6, user)) + if(istype(T,/turf/space)) continue + if(T.density) continue + if(T.x>world.maxx-6 || T.x<6) continue + if(T.y>world.maxy-6 || T.y<6) continue + turfs += T + if(!turfs.len) turfs += pick(/turf in orange(6)) + var/turf/picked = pick(turfs) + if(!isturf(picked)) return + + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, user.loc) + spark_system.start() + playsound(user.loc, "sparks", 50, 1) + + user.loc = picked + return PROJECTILE_FORCE_MISS return 0 /obj/item/clothing/suit/armor/reactive/attack_self(mob/user as mob) diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm index ce9ac0f225b..bc0f1a376c9 100644 --- a/code/modules/holodeck/HolodeckObjects.dm +++ b/code/modules/holodeck/HolodeckObjects.dm @@ -252,8 +252,14 @@ New() item_color = "red" -/obj/item/weapon/holo/esword/IsShield() - if(active) +/obj/item/weapon/holo/esword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(active && default_parry_check(user, attacker, damage_source) && prob(50)) + user.visible_message("\The [user] parries [attack_text] with \the [src]!") + + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, user.loc) + spark_system.start() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) return 1 return 0 diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index d9535b106c4..68680a432cf 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -19,8 +19,7 @@ // Should this all be in Touch()? if(istype(H)) - if((H != src) && check_shields(0, H.name)) - visible_message("\red [H] attempted to touch [src]!") + if(H != src && check_shields(0, null, H, H.zone_sel.selecting, H.name)) H.do_attack_animation(src) return 0 diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 5ecfa1807c8..e1cfa6f7ff7 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -16,30 +16,14 @@ emp_act var/obj/item/organ/external/organ = get_organ() - //Shields - if(check_shields(P.damage, "the [P.name]")) - P.on_hit(src, 2, def_zone) - return 2 - - //Laserproof armour - if(wear_suit && istype(wear_suit, /obj/item/clothing/suit/armor/laserproof)) - if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) - var/reflectchance = 40 - round(P.damage/3) - if(!(def_zone in list("chest", "groin"))) - reflectchance /= 2 - if(prob(reflectchance)) - visible_message("\red \The [P] gets reflected by \the [src]'s [wear_suit.name]!") - - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(src) - - // redirect the projectile - P.redirect(new_x, new_y, curloc, src) - - return PROJECTILE_CONTINUE // complete projectile permutation + //Shields + var/shield_check = check_shields(P.damage, P, null, def_zone, "the [P.name]") + if(shield_check) + if(shield_check < 0) + return shield_check + else + P.on_hit(src, 2, def_zone) + return 2 //Shrapnel if(P.can_embed()) @@ -148,33 +132,11 @@ emp_act return gear return null -/mob/living/carbon/human/proc/check_shields(var/damage = 0, var/attack_text = "the attack") - if(l_hand && istype(l_hand, /obj/item/weapon))//Current base is the prob(50-d/3) - var/obj/item/weapon/I = l_hand - if(I.IsShield() && (prob(50 - round(damage / 3)))) - visible_message("\red [src] blocks [attack_text] with the [l_hand.name]!") - return 1 - if(r_hand && istype(r_hand, /obj/item/weapon)) - var/obj/item/weapon/I = r_hand - if(I.IsShield() && (prob(50 - round(damage / 3)))) - visible_message("\red [src] blocks [attack_text] with the [r_hand.name]!") - return 1 - if(wear_suit && istype(wear_suit, /obj/item/)) - var/obj/item/I = wear_suit - if(I.IsShield() && (prob(35))) - visible_message("\red The reactive teleport system flings [src] clear of [attack_text]!") - var/list/turfs = new/list() - for(var/turf/T in orange(6)) - if(istype(T,/turf/space)) continue - if(T.density) continue - if(T.x>world.maxx-6 || T.x<6) continue - if(T.y>world.maxy-6 || T.y<6) continue - turfs += T - if(!turfs.len) turfs += pick(/turf in orange(6)) - var/turf/picked = pick(turfs) - if(!isturf(picked)) return - src.loc = picked - return 1 +/mob/living/carbon/human/proc/check_shields(var/damage = 0, var/atom/damage_source = null, var/mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + for(var/obj/item/shield in list(l_hand, r_hand, wear_suit)) + if(!shield) continue + . = shield.handle_shield(src, damage, damage_source, attacker, def_zone, attack_text) + if(.) return return 0 /mob/living/carbon/human/emp_act(severity) @@ -206,7 +168,7 @@ emp_act if(user.a_intent == "disarm") effective_force = round(I.force/2) var/hit_area = affecting.name - if((user != src) && check_shields(effective_force, "the [I.name]")) + if((user != src) && check_shields(effective_force, I, user, target_zone, "the [I.name]")) return 0 if(istype(I,/obj/item/weapon/card/emag)) @@ -329,15 +291,19 @@ emp_act miss_chance = max(15*(distance-2), 0) zone = get_zone_with_miss_chance(zone, src, miss_chance, ranged_attack=1) + if(zone && O.thrower != src) + var/shield_check = check_shields(throw_damage, O, thrower, zone, "[O]") + if(shield_check == PROJECTILE_FORCE_MISS) + zone = null + else if(shield_check) + return + if(!zone) - visible_message("\blue \The [O] misses [src] narrowly!") + visible_message("\The [O] misses [src] narrowly!") return O.throwing = 0 //it hit, so stop moving - if ((O.thrower != src) && check_shields(throw_damage, "[O]")) - return - var/obj/item/organ/external/affecting = get_organ(zone) var/hit_area = affecting.name diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index cc76d40c654..2f0d30ce2cc 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -24,6 +24,7 @@ adjustCloneLoss(damage/(blocked+1)) if(HALLOSS) adjustHalLoss(damage/(blocked+1)) + flash_weak_pain() updatehealth() return 1 diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 871e95c8b40..0d4a5d77b48 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -50,7 +50,6 @@ /mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone) - flash_weak_pain() //Being hit while using a cloaking device var/obj/item/weapon/cloaking_device/C = locate((/obj/item/weapon/cloaking_device) in src) diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index c06b57d54c8..19ac8c62fbc 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -237,7 +237,7 @@ var/hit_area = affecting.name - if((user != target) && H.check_shields(7, "the [src.name]")) + if((user != target) && H.check_shields(7, src, user, "\the [src]")) return if (target != user && H.getarmor(target_zone, "melee") > 5 && prob(50)) diff --git a/html/changelogs/HarpyEagle-dev.yml b/html/changelogs/HarpyEagle-dev.yml new file mode 100644 index 00000000000..08a2ca3b46f --- /dev/null +++ b/html/changelogs/HarpyEagle-dev.yml @@ -0,0 +1,23 @@ +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment + +author: HarpyEagle +changes: + - rscadd: "Shields no longer block attacks from directly behind the player." + - rscadd: "Riot shields no longer stop bullets or beams (except for beanbags and rubber bullets), however they are now more effective at blocking melee attacks and thrown objects." + - rscadd: "Energy shields block melee attacks as effectively as riot shields do. Their ability to block projectiles is largely unchanged." + - tweak: "Melee weapons now only block melee attacks." + - experiment: "Two handed weapons have a small chance of blocking melee attacks when wielded in two hands." + - rscadd: "Sound and visual effects when blocking attacks with an energy shield or energy sword." + - bugfix: "Fixed dead or unconscious people blocking stuff with shields." +delete-after: false