Securiblade Redux (#30488)

* Securiblade Redux

* It's back

* Vars

* LINTERS

* Apply AP on burn damage

* Fixes recharger bug

* Slapper, Better code, cleanup, no damage on help intent while off or stun, sounds

* Secblade now makes a sound when turning itself off
This commit is contained in:
PollardTheDragon
2025-10-17 13:00:50 -04:00
committed by GitHub
parent afd37f310f
commit fbd8930ff4
9 changed files with 282 additions and 1 deletions
@@ -70,6 +70,265 @@
"<span class='suicide'>[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!</span>"))
return BRUTELOSS
#define SECSWORD_OFF 0
#define SECSWORD_STUN 1
#define SECSWORD_BURN 2
// MARK: SECURIBLADE
/obj/item/melee/secsword
name = "securiblade"
desc = "A simple, practical blade developed by Shellguard munitions for enhanced riot control."
base_icon_state = "secsword0"
flags = CONDUCT
force = 15
throwforce = 5
w_class = WEIGHT_CLASS_BULKY
origin_tech = "combat=4"
attack_verb = list("stabbed", "slashed", "sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
materials = list(MAT_METAL = 1000)
new_attack_chain = TRUE
/// The icon the sword has when turned off
var/base_icon = "secsword"
/// How much stamina damage the sword does in stamina mode
var/stam_damage = 35 // 3 hits to stamcrit
/// How much burn damage the sword does in burn mode
var/burn_damage = 10
/// How much power does it cost to stun someone
var/stam_hitcost = 1000
/// How much power does it cost to burn someone
var/burn_hitcost = 1500
/// the initial cooldown tracks the time between stamina damage. tracks the world.time when the baton is usable again.
var/cooldown = 3.5 SECONDS
/// The sword's power cell - starts high
var/obj/item/stock_parts/cell/high/cell
/// The sword's current mode. Defaults to off.
var/state = SECSWORD_OFF
/// Stun cooldown
COOLDOWN_DECLARE(stun_cooldown)
/obj/item/melee/secsword/Initialize(mapload)
. = ..()
cell = new(src)
AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 1, _parryable_attack_types = NON_PROJECTILE_ATTACKS)
update_appearance(UPDATE_ICON_STATE)
/obj/item/melee/secsword/Destroy()
QDEL_NULL(cell)
return ..()
/obj/item/melee/secsword/examine(mob/user)
. = ..()
if(!cell)
. += "<span class='notice'>The powercell has been removed!</span>"
return
. += "<span class='notice'>It is [round(cell.percent())]% charged.</span>"
if(round(cell.percent() < 100))
. += "<span class='notice'>Can be recharged with a recharger.</span>"
/obj/item/melee/secsword/examine_more(mob/user)
. = ..()
. += "A simple, practical blade developed by Shellguard munitions for enhanced riot control."
. += ""
. += "The Securiblade is a simple blade of lightweight silver plasteel, augmented with an energy-emitting edge and with a simple \
power pack built into the hilt. A multi-purpose option for the NT Officer with a flair for impracticality, the \
Securiblade serves just as well as a regular sword, as it does a stun weapon or even an energized, heated blade."
. += ""
. += "While not the most popular option among the officers of Nanotrasens security forces, the Securiblade has still been valued for the multiple options it \
presents in combat. Deactivated, its a simple sword, but powered, it can either be utilized as a useful stun weapon, or as a dangerous, heated blade \
that can inflict grievous burn wounds on any suspects unfortunate enough to meet an officer using it. Rest assured, the Securiblade is a reliable tool in the hands of a skilled officer."
/obj/item/melee/secsword/update_icon_state()
if(!cell)
icon_state = "[base_icon]3"
else
icon_state = "[base_icon][state]"
/obj/item/melee/secsword/emp_act(severity)
if(!cell)
return
cell.use(round(cell.charge / severity))
update_icon()
/obj/item/melee/secsword/get_cell()
return cell
/obj/item/melee/secsword/proc/clear_cell()
SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
UnregisterSignal(cell, COMSIG_PARENT_QDELETING)
cell = null
update_icon()
/obj/item/melee/secsword/item_interaction(mob/living/user, obj/item/used, list/modifiers)
. = ..()
if(!istype(used, /obj/item/stock_parts/cell))
return ITEM_INTERACT_COMPLETE
var/obj/item/stock_parts/cell/C = used
if(cell)
to_chat(user, "<span class='warning'>[src] already has a cell!</span>")
return ITEM_INTERACT_COMPLETE
if(C.maxcharge < stam_hitcost)
to_chat(user, "<span class='warning'>[src] requires a higher capacity cell!</span>")
return ITEM_INTERACT_COMPLETE
if(!user.unequip(used))
return ITEM_INTERACT_COMPLETE
used.forceMove(src)
cell = used
to_chat(user, "<span class='notice'>You install [used] into [src].</span>")
update_icon()
return ITEM_INTERACT_COMPLETE
/obj/item/melee/secsword/screwdriver_act(mob/living/user, obj/item/I)
if(!cell)
to_chat(user, "<span class='warning'>There's no cell installed!</span>")
return
if(!I.use_tool(src, user, volume = I.tool_volume))
return
user.put_in_hands(cell)
to_chat(user, "<span class='notice'>You remove [cell] from [src].</span>")
cell.update_icon()
clear_cell()
/obj/item/melee/secsword/activate_self(mob/user)
if(..())
return FINISH_ATTACK
if(!cell)
to_chat(user, "<span class='warning'>[src] does not have a power source!</span>")
return FINISH_ATTACK
add_fingerprint(user)
if(cell.charge < stam_hitcost || (SECSWORD_STUN && cell.charge < burn_hitcost))
state = SECSWORD_OFF
armor_penetration_percentage = 0
to_chat(user, "<span class='notice'>[src] does not have enough charge!</span>")
return FINISH_ATTACK
switch(state)
if(SECSWORD_OFF)
state = SECSWORD_STUN
armor_penetration_percentage = 30
to_chat(user, "<span class='warning'>[src]'s edge is now set to stun.</span>")
if(SECSWORD_STUN)
state = SECSWORD_BURN
armor_penetration_percentage = 60
to_chat(user, "<span class='warning'>[src]'s edge is now set to burn.</span>")
if(SECSWORD_BURN)
state = SECSWORD_OFF
armor_penetration_percentage = 0
to_chat(user, "<span class='notice'>[src]'s edge is now turned off.</span>")
update_icon()
playsound(src, "sparks", 60, TRUE, -1)
return FINISH_ATTACK
/obj/item/melee/secsword/attack(mob/living/M, mob/living/user, params)
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
if(state == SECSWORD_STUN && sword_stun(user, user, skip_cooldown = TRUE))
user.visible_message("<span class='danger'>[user] accidentally hits [user.p_themselves()] with [src]!</span>",
"<span class='userdanger'>You accidentally hit yourself with [src]!</span>")
return FINISH_ATTACK | MELEE_COOLDOWN_PREATTACK
if(user.mind?.martial_art?.no_baton && user.mind?.martial_art?.can_use(user)) // Just like the baton, no sword + judo.
to_chat(user, "<span class='warning'>The sword feels off-balance in your hand due to your specific martial training!</span>")
return FINISH_ATTACK | MELEE_COOLDOWN_PREATTACK
// Off
if(!isliving(M) || state == SECSWORD_OFF)
if(user.a_intent == INTENT_HELP)
if(!COOLDOWN_FINISHED(src, stun_cooldown))
return
if(issilicon(M)) // Can't slap borgs and AIs
user.do_attack_animation(M)
M.visible_message(
"<span class='warning'>[user] has slapped [M] harmlessly with [src].</span>",
"<span class='danger'>[user] has slapped you harmlessly with [src].</span>"
)
return
slap(M, user) // Just a little slap. No harm
return
return ..()
// Stun mode
if(state == SECSWORD_STUN)
if(issilicon(M) && user.a_intent != INTENT_HELP) // Can't stun borgs and AIs
return ..()
else if(issilicon(M))
user.do_attack_animation(M)
M.visible_message(
"<span class='warning'>[user] has slapped [M] harmlessly with [src].</span>",
"<span class='danger'>[user] has slapped you harmlessly with [src].</span>"
)
return
if(sword_stun(M, user))
user.do_attack_animation(M)
if(user.a_intent != INTENT_HELP) // Hurt people only if not help
return ..()
return
// Burn
var/mob/living/L = M
if(ishuman(L))
var/mob/living/carbon/human/H = L
var/obj/item/organ/external/targetlimb = H.get_organ(ran_zone(user.zone_selected))
H.apply_damage(burn_damage, BURN, targetlimb, H.run_armor_check(targetlimb, MELEE, armor_penetration_percentage = armor_penetration_percentage))
else
L.apply_damage(burn_damage, BURN)
deduct_charge(burn_hitcost)
return ..()
/obj/item/melee/secsword/proc/slap(mob/living/carbon/human/target, mob/living/user)
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
playsound(loc, 'sound/effects/woodhit.ogg', 50, TRUE, -1)
target.AdjustConfused(4 SECONDS, 0, 4 SECONDS)
target.apply_damage(10, STAMINA)
add_attack_logs(user, target, "Slapped by [src]", ATKLOG_ALL)
COOLDOWN_START(src, stun_cooldown, cooldown) // Shares cooldown with stun to avoid comboing slap into stun
/obj/item/melee/secsword/proc/sword_stun(mob/living/L, mob/user, skip_cooldown = FALSE)
if(!COOLDOWN_FINISHED(src, stun_cooldown) && !skip_cooldown)
return FALSE
var/user_UID = user.UID()
if(HAS_TRAIT_FROM(L, TRAIT_WAS_BATONNED, user_UID)) // Doesn't work in conjunction with stun batons.
return FALSE
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) // No message; check_shields() handles that
playsound(L, 'sound/weapons/genhit.ogg', 50, TRUE)
return TRUE
// Weaker than a stun baton, less bad effects applied
H.Jitter(6 SECONDS)
H.AdjustConfused(4 SECONDS, 0, 4 SECONDS)
H.SetStuttering(6 SECONDS)
var/obj/item/organ/external/targetlimb = H.get_organ(ran_zone(user.zone_selected))
H.apply_damage(stam_damage, STAMINA, targetlimb, H.run_armor_check(targetlimb, MELEE))
deduct_charge(stam_hitcost)
ADD_TRAIT(L, TRAIT_WAS_BATONNED, user_UID) // So a person cannot hit the same person with a sword AND a baton, or two swords
addtimer(CALLBACK(src, PROC_REF(stun_delay), L, user_UID), 2 SECONDS)
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK, 33)
playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
COOLDOWN_START(src, stun_cooldown, cooldown)
return TRUE
// Proc called to remove trait that prevents repeated stamina damage. Called on a 2 Second timer when hit in stun mode
/obj/item/melee/secsword/proc/stun_delay(mob/living/target, user_UID)
REMOVE_TRAIT(target, TRAIT_WAS_BATONNED, user_UID)
/obj/item/melee/secsword/proc/deduct_charge(amount)
if(!cell)
return
if(cell.rigged)
RegisterSignal(cell, COMSIG_PARENT_QDELETING, PROC_REF(clear_cell))
cell.use(amount)
if(cell.charge < amount) // If after the deduction the sword doesn't have enough charge for a hit it turns off.
state = SECSWORD_OFF
armor_penetration_percentage = 0
playsound(src, "sparks", 60, TRUE, -1)
update_icon()
#undef SECSWORD_OFF
#undef SECSWORD_STUN
#undef SECSWORD_BURN
// MARK: SNAKESFANG
/obj/item/melee/snakesfang
name = "snakesfang"
@@ -808,6 +808,16 @@
new /obj/item/melee/saber(src)
update_appearance(UPDATE_ICON_STATE)
/obj/item/storage/belt/sheath/secsword
name = "securiblade scabbard"
desc = "Can hold securiblades."
base_icon_state = "secsheath"
can_hold = list(/obj/item/melee/secsword)
/obj/item/storage/belt/sheath/secsword/populate_contents()
new /obj/item/melee/secsword(src)
update_appearance(UPDATE_ICON_STATE)
/obj/item/storage/belt/sheath/snakesfang
name = "snakesfang scabbard"
desc = "Can hold scimitars."