diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index d12fe77448..d219736abe 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -159,9 +159,6 @@ #define SHOVE_STAGGER_DURATION 35 /// how long they're off balance for #define SHOVE_OFFBALANCE_DURATION 30 -//Shove disarming item list -GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( - /obj/item/gun))) //Embedded objects diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index e27c42ff75..ded183ba0b 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -215,6 +215,7 @@ #define TRAIT_NO_STAMINA_BUFFER_REGENERATION "block_stamina_buffer_regen" /// Prevents stamina buffer regeneration #define TRAIT_NO_STAMINA_REGENERATION "block_stamina_regen" /// Prevents stamina regeneration #define TRAIT_ARMOR_BROKEN "armor_broken" //acts as if you are wearing no clothing when taking damage, does not affect non-clothing sources of protection +#define TRAIT_IWASBATONED "iwasbatoned" //some dastardly fellow has struck you with a baton and thought to use another to strike you again, the rogue /// forces update_density to make us not dense #define TRAIT_LIVING_NO_DENSITY "living_no_density" /// forces us to not render our overlays diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index ac6ea4e25c..26e0197deb 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -123,7 +123,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_HIGH_BLOOD" = TRAIT_HIGH_BLOOD, "TRAIT_EMPATH" = TRAIT_EMPATH, "TRAIT_FRIENDLY" = TRAIT_FRIENDLY, - "TRAIT_NICE_SHOT" = TRAIT_NICE_SHOT + "TRAIT_IWASBATONED" = TRAIT_IWASBATONED ), /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index a958d20276..1c49f61996 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -108,7 +108,7 @@ /datum/status_effect/off_balance/on_remove() var/active_item = owner.get_active_held_item() - if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) + if(active_item) owner.visible_message("[owner.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE) return ..() diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 88146e4b4d..7721de42ad 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -16,17 +16,21 @@ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) attack_speed = CLICK_CD_MELEE - var/stamforce = 35 + var/stamina_loss_amount = 35 var/turned_on = FALSE var/knockdown = TRUE var/obj/item/stock_parts/cell/cell var/hitcost = 750 var/throw_hit_chance = 35 var/preload_cell_type //if not empty the baton starts with this type of cell + var/cooldown_duration = 5 SECONDS //How long our baton rightclick goes on cooldown for after applying a knockdown + var/status_duration = 5 SECONDS //how long our status effects last for otherwise + COOLDOWN_DECLARE(shove_cooldown) /obj/item/melee/baton/examine(mob/user) . = ..() - . += "Right click attack while in combat mode to disarm instead of stun." + . += "Right click attack while in combat mode to knockdown, but only once per [cooldown_duration / 10] seconds." + . += "This knockdown will also put them off balance for [status_duration / 20] seconds, allowing you to shove a weapon out of their hand with a right click in Disarm intent." /obj/item/melee/baton/get_cell() . = cell @@ -53,8 +57,8 @@ /obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) ..() //Only mob/living types have stun handling - if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom)) - baton_stun(hit_atom) + if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom) && thrownby) + baton_stun(hit_atom, thrownby, shoving = TRUE) /obj/item/melee/baton/loaded //this one starts with a cell pre-installed. preload_cell_type = /obj/item/stock_parts/cell/high/plus @@ -74,6 +78,16 @@ //we're below minimum, turn off switch_status(FALSE) +///Check for our cell to determine how much penetration our weapon does. +/obj/item/melee/baton/proc/get_cell_zap_pen() + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(copper_top) + var/chargepower = copper_top.maxcharge + var/zap_penetration = (chargepower/1000) //This is our effective penetration. Every 1000 max charge, we get 1 pen power. A high capacity cell is equal to 10 armor pen, as an example. + return zap_penetration + else + return 0 + /obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE) if(turned_on != new_status) turned_on = new_status @@ -101,6 +115,7 @@ var/obj/item/stock_parts/cell/copper_top = get_cell() if(copper_top) . += "\The [src] is [round(copper_top.percent())]% charged." + . += "\The [src] currently can penetrate [round(copper_top.maxcharge/1000)]% of enemy armor thanks to it's loaded cell." else . += "\The [src] does not have a power source installed." @@ -150,10 +165,10 @@ /obj/item/melee/baton/alt_pre_attack(atom/A, mob/living/user, params) if(!user.CheckActionCooldown(CLICK_CD_MELEE)) return - . = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-disarming. + . = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-shoving. //return TRUE to interrupt attack chain. -/obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, disarming = FALSE) +/obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, shoving = FALSE) if(iscyborg(M) || !isliving(M)) //can't baton cyborgs return FALSE if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) @@ -167,21 +182,26 @@ if(check_martial_counter(L, user)) return TRUE if(turned_on) - if(baton_stun(M, user, disarming)) + if(baton_stun(M, user, shoving)) user.do_attack_animation(M) else if(user.a_intent != INTENT_HARM) //they'll try to bash in the last proc. M.visible_message("[user] has prodded [M] with [src]. Luckily it was off.", \ "[user] has prodded you with [src]. Luckily it was off") - return disarming || (user.a_intent != INTENT_HARM) + return shoving || (user.a_intent != INTENT_HARM) -/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, disarming = FALSE) +/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, shoving = FALSE) var/list/return_list = list() if(L.mob_run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user, null, return_list) & BLOCK_SUCCESS) //No message; check_shields() handles that playsound(L, 'sound/weapons/genhit.ogg', 50, 1) return FALSE - var/stunpwr = stamforce - stunpwr = block_calculate_resultant_damage(stunpwr, return_list) + var/final_stamina_loss_amount = stamina_loss_amount //Our stunning power for the baton + var/shoved = FALSE //Did we succeed on knocking our target over? + var/zap_penetration = get_cell_zap_pen() //Find out what kind of cell we have, and calculating the resultant armor pen we get from it + var/zap_block = L.run_armor_check(BODY_ZONE_CHEST, "melee", null, null, zap_penetration) //armor check, including calculation for armor penetration, for our attack + final_stamina_loss_amount = block_calculate_resultant_damage(final_stamina_loss_amount, return_list) + var/obj/item/stock_parts/cell/our_cell = get_cell() + if(!our_cell) switch_status(FALSE) return FALSE @@ -194,25 +214,31 @@ L.visible_message("[user] has prodded [L] with [src]. Luckily it was out of charge.", \ "[user] has prodded you with [src]. Luckily it was out of charge.") return FALSE - stunpwr *= round(stuncharge/hitcost, 0.1) + final_stamina_loss_amount *= round(stuncharge/hitcost, 0.1) if(user && !user.UseStaminaBuffer(getweight(user, STAM_COST_BATON_MOB_MULT), warn = TRUE)) return FALSE - if(!disarming) - if(knockdown) - L.DefaultCombatKnockdown(50, override_stamdmg = 0) //knockdown - L.adjustStaminaLoss(stunpwr) - else - L.drop_all_held_items() //no knockdown/stamina damage, instead disarm. + if(shoving && COOLDOWN_FINISHED(src, shove_cooldown) && !HAS_TRAIT(L, TRAIT_IWASBATONED)) //Rightclicking applies a knockdown, but only once every couple of seconds, based on the cooldown_duration var. If they were recently knocked down, they can't be knocked down again by a baton. + L.DefaultCombatKnockdown(50, override_stamdmg = 0) + L.apply_status_effect(STATUS_EFFECT_TASED_WEAK, status_duration) //Even if they shove themselves up, they're still slowed. + L.apply_status_effect(STATUS_EFFECT_OFF_BALANCE, status_duration) //They're very likely to drop items if shoved briefly after a knockdown. + shoved = TRUE + COOLDOWN_START(src, shove_cooldown, cooldown_duration) + ADD_TRAIT(L, TRAIT_IWASBATONED, STATUS_EFFECT_TRAIT) //Prevents swapping to a new baton to avoid the cooldown by just acquiring more batons + addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, STATUS_EFFECT_TRAIT), cooldown_duration) + playsound(loc, 'sound/weapons/zapbang.ogg', 50, 1, -1) + else //If we cannot/don't knock down the target, we apply a stagger, which keeps them from just running off + L.apply_status_effect(STATUS_EFFECT_STAGGERED, status_duration) - L.apply_effect(EFFECT_STUTTER, stamforce) + L.apply_damage (final_stamina_loss_amount, STAMINA, BODY_ZONE_CHEST, zap_block) + L.apply_effect(EFFECT_STUTTER, stamina_loss_amount) SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) if(user) L.set_last_attacker(user) - L.visible_message("[user] has [disarming? "disarmed" : "stunned"] [L] with [src]!", \ - "[user] has [disarming? "disarmed" : "stunned"] you with [src]!") - log_combat(user, L, disarming? "disarmed" : "stunned") + L.visible_message("[user] has [shoved ? "brutally stunned" : "stunned"] [L] with [src]!", \ + "[user] has [shoved ? "brutally stunnned" : "stunned"] you with [src]!") + log_combat(user, L, shoved ? "stunned and attempted knockdown" : "stunned") playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) @@ -227,7 +253,7 @@ user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ "You accidentally hit yourself with [src]!") SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) - user.DefaultCombatKnockdown(stamforce*6) + user.DefaultCombatKnockdown(stamina_loss_amount*6) playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) deductcharge(hitcost) @@ -305,18 +331,20 @@ w_class = WEIGHT_CLASS_BULKY force = 3 throwforce = 5 - stamforce = 25 + stamina_loss_amount = 25 hitcost = 1000 throw_hit_chance = 10 slot_flags = ITEM_SLOT_BACK + cooldown_duration = 7 SECONDS //It's a little on the weak side + status_duration = 3 //Slows someone for a tiny bit var/obj/item/assembly/igniter/sparkler /obj/item/melee/baton/cattleprod/Initialize() . = ..() sparkler = new (src) - sparkler.activate_cooldown = 5 + sparkler.activate_cooldown = 7 //Helps visualize the knockdown -/obj/item/melee/baton/cattleprod/baton_stun() +/obj/item/melee/baton/cattleprod/baton_stun(mob/living/L, mob/living/carbon/user, shoving = FALSE) sparkler?.activate() . = ..() @@ -343,8 +371,8 @@ /obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) if(turned_on) var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum) - if(ishuman(hit_atom) && !caught && prob(throw_hit_chance))//if they are a carbon and they didn't catch it - baton_stun(hit_atom) + if(ishuman(hit_atom) && !caught && prob(throw_hit_chance) && thrownby)//if they are a carbon and they didn't catch it + baton_stun(hit_atom, thrownby, shoving = TRUE) if(thrownby && !caught) sleep(1) if(!QDELETED(src)) diff --git a/code/game/objects/items/teleprod.dm b/code/game/objects/items/teleprod.dm index 63bde36976..94eb6fc85b 100644 --- a/code/game/objects/items/teleprod.dm +++ b/code/game/objects/items/teleprod.dm @@ -6,7 +6,7 @@ item_state = "teleprod" slot_flags = null -/obj/item/melee/baton/cattleprod/teleprod/baton_stun(mob/living/L, mob/living/carbon/user)//handles making things teleport when hit +/obj/item/melee/baton/cattleprod/teleprod/baton_stun(mob/living/L, mob/living/carbon/user, shoving = FALSE)//handles making things teleport when hit . = ..() if(!. || L.anchored) return @@ -16,7 +16,7 @@ user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ "You accidentally hit yourself with [src]!") SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) - user.DefaultCombatKnockdown(stamforce * 6) + user.DefaultCombatKnockdown(stamina_loss_amount * 6) playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) if(do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE)) deductcharge(hitcost) diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index dba8d5de45..01c62a7901 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -554,7 +554,7 @@ if(B.cell) if(B.cell.charge > 0 && B.turned_on) flick("baton_active", src) - var/stunforce = B.stamforce + var/stunforce = B.stamina_loss_amount user.DefaultCombatKnockdown(stunforce * 2) user.stuttering = stunforce/20 B.deductcharge(B.hitcost) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index c1a797dea0..1aa3749589 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -2038,8 +2038,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) user, "You shove [target.name]!") target.Stagger(SHOVE_STAGGER_DURATION) var/obj/item/target_held_item = target.get_active_held_item() - if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types)) - target_held_item = null if(!target.has_status_effect(STATUS_EFFECT_OFF_BALANCE)) if(target_held_item) if(!HAS_TRAIT(target_held_item, TRAIT_NODROP))