Files
30fbcfe9b4 Belts & Trashbags (#7443)
requires #6644 

Entirely replaces how (most) belts perform insertion checks.

This pretty much allows belts to hold most things.

Armor vests are next.

Trashbags will also use the systems we use on main.

Closes #7256 and #7435

---------

Co-authored-by: silicons <silicons@silicons.dev>
2025-12-28 22:05:06 -05:00

418 lines
13 KiB
Plaintext

// todo: /obj/item/stun_baton?
/obj/item/melee/baton
name = "stunbaton"
desc = "A stun baton for incapacitating people with."
w_class = WEIGHT_CLASS_BULKY
icon = 'icons/obj/weapons.dmi'
icon_state = "stunbaton"
item_state = "baton"
rad_flags = RAD_BLOCK_CONTENTS
slot_flags = SLOT_BELT
damage_force = 15
throw_force = 7
atom_flags = NOCONDUCT
w_class = WEIGHT_CLASS_NORMAL
belt_storage_class = BELT_CLASS_LARGE
drop_sound = 'sound/items/drop/metalweapon.ogg'
pickup_sound = 'sound/items/pickup/metalweapon.ogg'
origin_tech = list(TECH_COMBAT = 2)
attack_verb = list("beaten")
worth_intrinsic = 75
/// Starting cell type
var/cell_type
var/cell_accept = CELL_TYPE_SMALL | CELL_TYPE_WEAPON
/// Shock stun power
var/stun_power = 45
/// Electrocute act flags
var/stun_electrocute_flags = ELECTROCUTE_ACT_FLAG_DO_NOT_STUN
/// Sound for the stun
var/stun_sound = 'sound/weapons/Egloves.ogg'
/// Charge cost per hit in cell units
var/charge_cost = POWER_CELL_CAPACITY_WEAPON / 16
/// Are we on?
var/active = FALSE
/// Glow color when on
var/active_color = "#FF6A00"
// todo: use item mounts
var/legacy_use_external_power = FALSE
/obj/item/melee/baton/Initialize(mapload)
. = ..()
init_cell_slot_easy_tool(cell_type, cell_accept)
update_icon()
/obj/item/melee/baton/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot)
return ..() && !legacy_use_external_power
/**
* Turn off if we're out of charge
*/
/obj/item/melee/baton/proc/update_charge()
if(check_charge(charge_cost))
return TRUE
deactivate()
return FALSE
/obj/item/melee/baton/proc/check_charge(amount)
if(legacy_use_external_power)
var/mob/living/silicon/robot/robot = loc
if(!istype(robot))
return FALSE
return robot.cell?.charge >= amount
if(amount > obj_cell_slot.cell?.charge)
return FALSE
return TRUE
/**
* Try to use charge for a hit
*/
/obj/item/melee/baton/proc/use_charge(amount)
if(legacy_use_external_power)
var/mob/living/silicon/robot/robot = loc
if(!istype(robot))
return FALSE
return robot.cell?.checked_use(amount)
return obj_cell_slot.cell?.checked_use(amount)
/obj/item/melee/baton/attack_self(mob/user, datum/event_args/actor/actor)
. = ..()
if(.)
return
if(user_clickchain_toggle_active(actor))
return CLICKCHAIN_DID_SOMETHING
/obj/item/melee/baton/proc/activate(silent, force)
if(active)
return TRUE
if(!force && !check_charge(charge_cost))
return FALSE
active = TRUE
if(!silent)
playsound(src, /datum/soundbyte/sparks, 75, TRUE)
update_icon()
return TRUE
/obj/item/melee/baton/proc/deactivate(silent)
if(!active)
return TRUE
active = FALSE
if(!silent)
playsound(src, /datum/soundbyte/sparks, 75, TRUE)
update_icon()
return TRUE
/obj/item/melee/baton/proc/user_clickchain_toggle_active(datum/event_args/actor/actor)
if(!update_charge())
actor.chat_feedback(
SPAN_WARNING("[src] is out of charge, or lacks a power source."),
target = src,
)
return TRUE
if(!active)
if(activate())
actor.chat_feedback(
SPAN_WARNING("[src] is now on."),
target = src,
)
else
if(deactivate())
actor.chat_feedback(
SPAN_NOTICE("[src] is now off."),
target = src,
)
return TRUE
/obj/item/melee/baton/melee_override(atom/target, mob/user, intent, zone, efficiency, datum/event_args/actor/actor)
var/harm_penalty = intent == INTENT_HARM ? 0.5 : 1
attempt_powered_melee_impact(target, user, actor, zone, harm_penalty)
if(intent != INTENT_HARM)
return TRUE
return ..()
/**
* Basically [powered_melee_impact()] but checks for charge.
*
* @return TRUE if handled, FALSE otherwise
*/
/obj/item/melee/baton/proc/attempt_powered_melee_impact(atom/target, mob/attacker, datum/event_args/actor/actor, use_target_zone, efficiency = 1)
SHOULD_NOT_OVERRIDE(TRUE)
if(!active || !use_charge(charge_cost))
update_charge()
var/obj/item/organ/external/affecting
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
affecting = carbon_target.get_bodypart_for_zone(use_target_zone)
attacker?.visible_message(
SPAN_WARNING("[target] has been prodded [affecting ? "in \the [affecting]" : ""] with [src] by [attacker]. Luckily it was off."),
)
return TRUE
powered_melee_impact(target, attacker, actor, use_target_zone, efficiency)
update_charge()
return TRUE
/**
* Does not affect the normal hit, this only performs the stun.
*
* Calls [apply_powered_melee_impact()]. That's the one you can override.
*
* @params
* * target - what to hit
* * attacker - (optional) attacking user
* * clickchain - (optional) clickchain data
* * use_target_zone - (optional) target that zone
*/
/obj/item/melee/baton/proc/powered_melee_impact(atom/target, mob/attacker, datum/event_args/actor/actor, use_target_zone, efficiency = 1)
SHOULD_NOT_OVERRIDE(TRUE)
actor?.data[ACTOR_DATA_STUNBATON_LOG] = "[efficiency]x[stun_power] f-[stun_electrocute_flags] z-[use_target_zone]"
apply_powered_melee_impact(target, attacker, actor, use_target_zone, efficiency)
/**
* Does not affect the normal hit, this only performs the stun.
*
* Should be only called from [powered_melee_impact()]
*
* @params
* * target - what to hit
* * attacker - (optional) attacking user
* * clickchain - (optional) clickchain data
* * use_target_zone - (optional) target that zone
*/
/obj/item/melee/baton/proc/apply_powered_melee_impact(atom/target, mob/attacker, datum/event_args/actor/actor, use_target_zone, efficiency)
PROTECTED_PROC(TRUE)
var/obj/item/organ/external/affecting
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
affecting = carbon_target.get_bodypart_for_zone(use_target_zone)
attacker?.visible_message(
SPAN_DANGER("[target] has been prodded [affecting ? "in \the [affecting]" : ""] with [src] by [attacker]."),
)
playsound(src, stun_sound, 75, TRUE)
target.electrocute(
DYNAMIC_CELL_UNITS_TO_KJ(charge_cost) * efficiency,
0,
stun_power * efficiency,
stun_electrocute_flags,
use_target_zone || BP_TORSO,
src,
)
/obj/item/melee/baton/object_cell_slot_removed(obj/item/cell/cell, datum/object_system/cell_slot/slot)
. = ..()
update_icon()
/obj/item/melee/baton/object_cell_slot_inserted(obj/item/cell/cell, datum/object_system/cell_slot/slot)
. = ..()
update_icon()
/obj/item/melee/baton/update_icon()
. = ..()
if(active)
icon_state = "[initial(icon_state)]_active"
else if(!obj_cell_slot?.cell)
icon_state = "[initial(icon_state)]_nocell"
else
icon_state = "[initial(icon_state)]"
if(icon_state == "[initial(icon_state)]_active")
set_light(2, 1, active_color)
else
set_light(0)
/obj/item/melee/baton/examine(mob/user, dist)
. = ..()
if(obj_cell_slot?.cell)
. += SPAN_NOTICE("[src] is [obj_cell_slot.cell.percent()]% charged.")
else
. += SPAN_NOTICE("[src] does not have a power source installed.")
/obj/item/melee/baton/loaded
cell_type = /obj/item/cell/basic/tier_1/weapon
//secborg stun baton module
/obj/item/melee/baton/robot
charge_cost = POWER_CELL_CAPACITY_WEAPON / 10
legacy_use_external_power = TRUE
//Makeshift stun baton. Replacement for stun gloves.
/obj/item/melee/baton/cattleprod
name = "stunprod"
desc = "An improvised stun baton."
icon_state = "stunprod"
item_state = "prod"
damage_force = 3
throw_force = 5
stun_power = 60 //same force as a stunbaton, but uses way more charge.
charge_cost = 4000
attack_verb = list("poked")
slot_flags = SLOT_BACK
cell_accept = CELL_TYPE_MEDIUM | CELL_TYPE_SMALL | CELL_TYPE_WEAPON
/obj/item/melee/baton/cattleprod/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/bluespace_crystal))
if(!obj_cell_slot?.cell)
var/obj/item/bluespace_crystal/BSC = W
var/obj/item/melee/baton/cattleprod/teleprod/S = new /obj/item/melee/baton/cattleprod/teleprod
qdel(src)
qdel(BSC)
user.put_in_hands_or_drop(S)
to_chat(user, "<span class='notice'>You place the bluespace crystal firmly into the igniter.</span>")
else
user.visible_message("<span class='warning'>You can't put the crystal onto the stunprod while it has a power cell installed!</span>")
/obj/item/melee/baton/cattleprod/teleprod
name = "teleprod"
desc = "An improvised stun baton with a bluespace crystal attached to the tip."
icon_state = "teleprod"
item_state = "prod"
damage_force = 3
throw_force = 5
stun_power = 60 //same force as a stunbaton, but uses way more charge.
charge_cost = 4000
attack_verb = list("poked")
slot_flags = null
/obj/item/melee/baton/cattleprod/teleprod/apply_powered_melee_impact(atom/target, mob/attacker, datum/event_args/actor/actor, use_target_zone, efficiency)
..()
do_teleport(target, get_turf(target), 15)
// Rare version of a baton that causes lesser lifeforms to really hate the user and attack them.
/obj/item/melee/baton/shocker
name = "shocker"
desc = "A device that appears to arc electricity into a target to incapacitate or otherwise hurt them, similar to a stun baton. It looks inefficient."
description_info = "Hitting a lesser lifeform with this while it is on will compel them to attack you above other nearby targets. Otherwise \
it works like a regular stun baton, just less effectively."
icon_state = "shocker"
damage_force = 10
throw_force = 5
stun_power = 40
attack_verb = list("poked")
/obj/item/melee/baton/shocker/apply_powered_melee_impact(atom/target, mob/attacker, datum/event_args/actor/actor, use_target_zone, efficiency)
..()
if(!isliving(target))
return
var/mob/living/L = target
if(active && L.has_polaris_AI())
L.taunt(attacker)
// Borg version, for the lost module.
/obj/item/melee/baton/shocker/robot
legacy_use_external_power = TRUE
/obj/item/melee/baton/stunsword
name = "stunsword"
desc = "Not actually sharp, this sword is functionally identical to its baton counterpart."
icon_state = "stunsword"
item_state = "baton"
/obj/item/melee/baton/stunlance
name = "stun lance"
desc = "Designed by Nanotrasen for mounted expeditions, the stun lance is useful for running down and incapacitating wildlife for study. Its efficacy on fugitives is tacitly implied."
icon_state = "stunlance"
w_class = WEIGHT_CLASS_NORMAL
reach = 2
/obj/item/melee/baton/stunlance/Initialize(mapload)
. = ..()
AddComponent(/datum/component/jousting)
/obj/item/melee/baton/loaded/mini
name = "Personal Defense Baton"
desc = "A smaller, more potent version of a hand-held tazer, one zap and the target is sure to be on the ground, and the <b>integrated</b> cell empty. Standard issue to Command staff, indentured sex workers and anyone else who might get mobbed by dissatisfied clientele. Do not lick."
icon_state = "mini_baton"
item_state = "mini_baton"
w_class = WEIGHT_CLASS_SMALL
damage_force = 5
throw_force = 2
stun_power = 120
stun_electrocute_flags = NONE
charge_cost = POWER_CELL_CAPACITY_WEAPON / 2
stun_sound = 'sound/effects/lightningshock.ogg'
/obj/item/melee/baton/loaded/mini/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot)
return FALSE
/obj/item/melee/baton/electrostaff
name = "riot suppression electrostaff"
desc = "A large quarterstaff with electrodes mounted at the end. Presumably for riot control. Presumably."
icon = 'icons/items/electrostaff.dmi'
icon_state = "electrostaff"
base_icon_state = "electrostaff"
slot_flags = SLOT_BACK
// TODO: get rid of legacy override for this.
item_icons = null
melee_click_cd_multiply = 0.75
worn_state = "electrostaff"
worn_render_flags = WORN_RENDER_SLOT_ONE_FOR_ALL
stun_power = 40
stun_sound = 'sound/effects/lightningshock.ogg'
// TODO: either do active parry instead or rework this to not be
// able to parry at 100% everything
// TODO: just add a small active parry frame as a test
passive_parry = /datum/passive_parry{
parry_chance_melee = 35;
parry_chance_touch = 40;
parry_frame_efficiency = 0.7
}
var/wielded_damage_force = 15
var/wielded_damage_tier = 3
var/unwielded_damage_force = 7.5
var/unwielded_damage_tier = 3
damage_force = 7.5
damage_tier = 3
// :trol:
cell_type = /obj/item/cell/infinite/weapon
/obj/item/melee/baton/electrostaff/Initialize(mapload)
. = ..()
AddComponent(/datum/component/wielding)
/obj/item/melee/baton/electrostaff/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot)
return FALSE
/obj/item/melee/baton/electrostaff/update_icon()
. = ..()
if(!(item_flags & ITEM_MULTIHAND_WIELDED))
icon_state = inhand_state = base_icon_state
return
var/use_state = "[base_icon_state]-wield"
if(active)
use_state = "[use_state]-blue"
icon_state = inhand_state = use_state
update_worn_icon()
/obj/item/melee/baton/electrostaff/on_wield(mob/user, hands)
..()
damage_force = wielded_damage_force
damage_tier = wielded_damage_tier
/obj/item/melee/baton/electrostaff/on_unwield(mob/user, hands)
// TODO: ..() updates icon. can we skip it?
..()
deactivate()
damage_force = unwielded_damage_force
damage_tier = unwielded_damage_tier
/obj/item/melee/baton/electrostaff/user_clickchain_toggle_active(datum/event_args/actor/actor)
if(!active && !(item_flags & ITEM_MULTIHAND_WIELDED))
actor.chat_feedback(
SPAN_WARNING("[src] needs to be wielded with both hands to be activated."),
target = src,
)
return TRUE
return ..()
/obj/item/melee/baton/electrostaff/activate(silent, force)
// we have no icon for it so don't allow it in literally any case
if(!(item_flags & ITEM_MULTIHAND_WIELDED))
return FALSE
return ..()