diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 86a351fd9b7..f97c495160e 100644 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -11,7 +11,7 @@ active_power_consumption = 200 pass_flags = PASSTABLE - var/list/allowed_devices = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/rcs, /obj/item/bodyanalyzer, /obj/item/handheld_chem_dispenser, /obj/item/clothing/suit/armor/reactive, /obj/item/wormhole_jaunter/wormhole_weaver, /obj/item/clothing/neck/link_scryer) + var/list/allowed_devices = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/rcs, /obj/item/bodyanalyzer, /obj/item/handheld_chem_dispenser, /obj/item/clothing/suit/armor/reactive, /obj/item/wormhole_jaunter/wormhole_weaver, /obj/item/clothing/neck/link_scryer, /obj/item/melee/secsword) var/recharge_coeff = 1 var/obj/item/charging = null // The item that is being charged @@ -210,6 +210,10 @@ var/obj/item/clothing/neck/link_scryer/LS = I return LS.cell + if(istype(I, /obj/item/melee/secsword)) + var/obj/item/melee/secsword/secsword = I + return secsword.cell + return null /obj/machinery/recharger/proc/check_cell_needs_recharging(obj/item/stock_parts/cell/C) diff --git a/code/game/objects/items/weapons/melee/melee.dm b/code/game/objects/items/weapons/melee/melee.dm index f6436de9c54..8634d161cc5 100644 --- a/code/game/objects/items/weapons/melee/melee.dm +++ b/code/game/objects/items/weapons/melee/melee.dm @@ -70,6 +70,265 @@ "[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!")) 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) + . += "The powercell has been removed!" + return + . += "It is [round(cell.percent())]% charged." + if(round(cell.percent() < 100)) + . += "Can be recharged with a recharger." + +/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 Nanotrasen’s security forces, the Securiblade has still been valued for the multiple options it \ + presents in combat. Deactivated, it’s 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, "[src] already has a cell!") + return ITEM_INTERACT_COMPLETE + if(C.maxcharge < stam_hitcost) + to_chat(user, "[src] requires a higher capacity cell!") + return ITEM_INTERACT_COMPLETE + if(!user.unequip(used)) + return ITEM_INTERACT_COMPLETE + used.forceMove(src) + cell = used + to_chat(user, "You install [used] into [src].") + update_icon() + return ITEM_INTERACT_COMPLETE + +/obj/item/melee/secsword/screwdriver_act(mob/living/user, obj/item/I) + if(!cell) + to_chat(user, "There's no cell installed!") + return + if(!I.use_tool(src, user, volume = I.tool_volume)) + return + + user.put_in_hands(cell) + to_chat(user, "You remove [cell] from [src].") + cell.update_icon() + clear_cell() + +/obj/item/melee/secsword/activate_self(mob/user) + if(..()) + return FINISH_ATTACK + if(!cell) + to_chat(user, "[src] does not have a power source!") + 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, "[src] does not have enough charge!") + return FINISH_ATTACK + switch(state) + if(SECSWORD_OFF) + state = SECSWORD_STUN + armor_penetration_percentage = 30 + to_chat(user, "[src]'s edge is now set to stun.") + if(SECSWORD_STUN) + state = SECSWORD_BURN + armor_penetration_percentage = 60 + to_chat(user, "[src]'s edge is now set to burn.") + if(SECSWORD_BURN) + state = SECSWORD_OFF + armor_penetration_percentage = 0 + to_chat(user, "[src]'s edge is now turned off.") + 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("[user] accidentally hits [user.p_themselves()] with [src]!", + "You accidentally hit yourself with [src]!") + 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, "The sword feels off-balance in your hand due to your specific martial training!") + 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( + "[user] has slapped [M] harmlessly with [src].", + "[user] has slapped you harmlessly with [src]." + ) + 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( + "[user] has slapped [M] harmlessly with [src].", + "[user] has slapped you harmlessly with [src]." + ) + 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" diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index f06365d3172..ce759c3c16a 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -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." diff --git a/code/modules/supply/supply_packs/pack_security.dm b/code/modules/supply/supply_packs/pack_security.dm index 2d81da2daa3..87847bee947 100644 --- a/code/modules/supply/supply_packs/pack_security.dm +++ b/code/modules/supply/supply_packs/pack_security.dm @@ -221,6 +221,14 @@ containertype = /obj/structure/closet/crate/secure/plasma containername = "energy shotgun crate" +/datum/supply_packs/security/armory/securiblades + name = "Securiblade Crate" + contains = list(/obj/item/storage/belt/sheath/secsword, + /obj/item/storage/belt/sheath/secsword) + cost = 600 + containertype = /obj/structure/closet/crate/secure/plasma + containername = "securiblade crate" + /datum/supply_packs/security/armory/stingers name = "Stinger Grenade Crate" contains = list(/obj/item/storage/box/stingers, diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi index 35740bd2962..bd8972b155e 100644 Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ diff --git a/icons/mob/inhands/weapons_lefthand.dmi b/icons/mob/inhands/weapons_lefthand.dmi index 612856e7e23..a521b5f84e3 100644 Binary files a/icons/mob/inhands/weapons_lefthand.dmi and b/icons/mob/inhands/weapons_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons_righthand.dmi b/icons/mob/inhands/weapons_righthand.dmi index e632e133264..b8ab9a93524 100644 Binary files a/icons/mob/inhands/weapons_righthand.dmi and b/icons/mob/inhands/weapons_righthand.dmi differ diff --git a/icons/obj/clothing/belts.dmi b/icons/obj/clothing/belts.dmi index 28291510e78..7678f1a83fe 100644 Binary files a/icons/obj/clothing/belts.dmi and b/icons/obj/clothing/belts.dmi differ diff --git a/icons/obj/weapons/melee.dmi b/icons/obj/weapons/melee.dmi index b6e63a420e1..5e7075b9f7b 100644 Binary files a/icons/obj/weapons/melee.dmi and b/icons/obj/weapons/melee.dmi differ