Files
Bubberstation/code/game/objects/items/melee/baton.dm
Ghom 778ed9f1ab The death or internal/external organ pathing (ft. fixed fox ears and recoloring bodypart overlays with dye sprays) (#87434)
## About The Pull Request
This PR kills the abstract internal and external typepaths for organs,
now replaced by an EXTERNAL_ORGAN flag to distinguish the two kinds.

This PR also fixes fox ears (from #87162, no tail is added) and
mushpeople's caps (they should be red, the screenshot is a tad
outdated).

And yes, you can now use a hair dye spray to recolor body parts like
most tails, podpeople hair, mushpeople caps and cat ears. The process
can be reversed by using the spray again.

## Why It's Good For The Game
Time-Green put some effort during the last few months to untie functions
and mechanics from external/internal organ pathing. Now, all that this
pathing is good for are a few typechecks, easily replaceable with
bitflags.

Also podpeople and mushpeople need a way to recolor their "hair". This
kind of applies to fish tails from the fish infusion, which colors can't
be selected right now. The rest is just there if you ever want to
recolor your lizard tail for some reason.

Proof of testing btw (screenshot taken before mushpeople cap fix, right
side has dyed body parts, moth can't be dyed, they're already fabolous):

![immagine](https://github.com/user-attachments/assets/2bb625c9-9233-42eb-b9b8-e0bd6909ce89)

## Changelog

🆑
code: Removed internal/external pathing from organs in favor of a bit
flag. Hopefully this shouldn't break anything about organs.
fix: Fixed invisible fox ears.
fix: Fixed mushpeople caps not being colored red by default.
add: You can now dye most tails, podpeople hair, mushpeople caps etc.
with a hair dye spray.
/🆑
2024-10-30 08:03:02 +01:00

865 lines
32 KiB
Plaintext

/obj/item/melee/baton
name = "police baton"
desc = "A wooden truncheon for beating criminal scum."
desc_controls = "Left click to stun, right click to harm."
icon = 'icons/obj/weapons/baton.dmi'
icon_state = "classic_baton"
inhand_icon_state = "classic_baton"
worn_icon_state = "classic_baton"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
force = 12 //9 hit crit
w_class = WEIGHT_CLASS_NORMAL
wound_bonus = 15
sound_vary = TRUE
/// Whether this baton is active or not
var/active = TRUE
/// Used interally, you don't want to modify
var/cooldown_check = 0
/// Default wait time until can stun again.
var/cooldown = (4 SECONDS)
/// The length of the knockdown applied to a struck living, non-cyborg mob.
var/knockdown_time = (1.5 SECONDS)
/// If affect_cyborg is TRUE, this is how long we stun cyborgs for on a hit.
var/stun_time_cyborg = (5 SECONDS)
/// The length of the knockdown applied to the user on clumsy_check()
var/clumsy_knockdown_time = 18 SECONDS
/// How much stamina damage we deal on a successful hit against a living, non-cyborg mob.
var/stamina_damage = 55
/// Chance of causing force_say() when stunning a human mob
var/force_say_chance = 33
/// Can we stun cyborgs?
var/affect_cyborg = FALSE
/// The path of the default sound to play when we stun something.
var/on_stun_sound = 'sound/effects/woodhit.ogg'
/// The volume of the above.
var/on_stun_volume = 75
/// Do we animate the "hit" when stunning something?
var/stun_animation = TRUE
/// Whether the stun attack is logged. Only relevant for abductor batons, which have different modes.
var/log_stun_attack = TRUE
/// Boolean on whether people with chunky fingers can use this baton.
var/chunky_finger_usable = FALSE
/// The context to show when the baton is active and targeting a living thing
var/context_living_target_active = "Stun"
/// The context to show when the baton is active and targeting a living thing in combat mode
var/context_living_target_active_combat_mode = "Stun"
/// The context to show when the baton is inactive and targeting a living thing
var/context_living_target_inactive = "Prod"
/// The context to show when the baton is inactive and targeting a living thing in combat mode
var/context_living_target_inactive_combat_mode = "Attack"
/// The RMB context to show when the baton is active and targeting a living thing
var/context_living_rmb_active = "Attack"
/// The RMB context to show when the baton is inactive and targeting a living thing
var/context_living_rmb_inactive = "Attack"
/obj/item/melee/baton/Initialize(mapload)
. = ..()
// Adding an extra break for the sake of presentation
if(stamina_damage != 0)
offensive_notes = "It takes [span_warning("[CEILING(100 / stamina_damage, 1)] stunning hit\s")] to stun an enemy."
register_item_context()
/**
* Ok, think of baton attacks like a melee attack chain:
*
* [/baton_attack()] comes first. It checks if the user is clumsy, if the target parried the attack and handles some messages and sounds.
* * Depending on its return value, it'll either do a normal attack, continue to the next step or stop the attack.
*
* [/finalize_baton_attack()] is then called. It handles logging stuff, sound effects and calls baton_effect().
* * The proc is also called in other situations such as stunbatons right clicking or throw impact. Basically when baton_attack()
* * checks are either redundant or unnecessary.
*
* [/baton_effect()] is third in the line. It knockdowns targets, along other effects called in additional_effects_cyborg() and
* * additional_effects_non_cyborg().
*
* Last but not least [/set_batoned()], which gives the target the IWASBATONED trait with REF(user) as source and then removes it
* * after a cooldown has passed. Basically, it stops users from cheesing the cooldowns by dual wielding batons.
*
* TL;DR: [/baton_attack()] -> [/finalize_baton_attack()] -> [/baton_effect()] -> [/set_batoned()]
*/
/obj/item/melee/baton/attack(mob/living/target, mob/living/user, params)
add_fingerprint(user)
var/list/modifiers = params2list(params)
switch(baton_attack(target, user, modifiers))
if(BATON_DO_NORMAL_ATTACK)
return ..()
if(BATON_ATTACKING)
finalize_baton_attack(target, user, modifiers)
/obj/item/melee/baton/apply_fantasy_bonuses(bonus)
. = ..()
stamina_damage = modify_fantasy_variable("stamina_damage", stamina_damage, bonus * 4)
/obj/item/melee/baton/remove_fantasy_bonuses(bonus)
stamina_damage = reset_fantasy_variable("stamina_damage", stamina_damage)
return ..()
/obj/item/melee/baton/add_item_context(datum/source, list/context, atom/target, mob/living/user)
if (isturf(target))
return NONE
if (isobj(target))
context[SCREENTIP_CONTEXT_LMB] = "Attack"
else
if (active)
context[SCREENTIP_CONTEXT_RMB] = context_living_rmb_active
if (user.combat_mode)
context[SCREENTIP_CONTEXT_LMB] = context_living_target_active_combat_mode
else
context[SCREENTIP_CONTEXT_LMB] = context_living_target_active
else
context[SCREENTIP_CONTEXT_RMB] = context_living_rmb_inactive
if (user.combat_mode)
context[SCREENTIP_CONTEXT_LMB] = context_living_target_inactive_combat_mode
else
context[SCREENTIP_CONTEXT_LMB] = context_living_target_inactive
return CONTEXTUAL_SCREENTIP_SET
/obj/item/melee/baton/proc/baton_attack(mob/living/target, mob/living/user, modifiers)
. = BATON_ATTACKING
if(clumsy_check(user, target))
return BATON_ATTACK_DONE
if(!chunky_finger_usable && ishuman(user))
var/mob/living/carbon/human/potential_chunky_finger_human = user
if(potential_chunky_finger_human.check_chunky_fingers() && user.is_holding(src) && !HAS_MIND_TRAIT(user, TRAIT_CHUNKYFINGERS_IGNORE_BATON))
balloon_alert(potential_chunky_finger_human, "fingers are too big!")
return BATON_ATTACK_DONE
if(!active || LAZYACCESS(modifiers, RIGHT_CLICK))
return BATON_DO_NORMAL_ATTACK
if(cooldown_check > world.time)
var/wait_desc = get_wait_description()
if (wait_desc)
to_chat(user, wait_desc)
return BATON_ATTACK_DONE
if(check_parried(target, user))
return BATON_ATTACK_DONE
if(HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user))) //no doublebaton abuse anon!
to_chat(user, span_danger("You fumble and miss [target]!"))
return BATON_ATTACK_DONE
if(stun_animation)
user.do_attack_animation(target)
var/list/desc
if(iscyborg(target))
if(affect_cyborg)
desc = get_cyborg_stun_description(target, user)
else
desc = get_unga_dunga_cyborg_stun_description(target, user)
playsound(get_turf(src), 'sound/effects/bang.ogg', 10, TRUE) //bonk
. = BATON_ATTACK_DONE
else
desc = get_stun_description(target, user)
if(desc)
target.visible_message(desc["visible"], desc["local"])
/obj/item/melee/baton/proc/check_parried(mob/living/carbon/human/human_target, mob/living/user)
if (human_target.check_block(src, 0, "[user]'s [name]", MELEE_ATTACK))
playsound(human_target, 'sound/items/weapons/genhit.ogg', 50, TRUE)
return TRUE
return FALSE
/obj/item/melee/baton/proc/finalize_baton_attack(mob/living/target, mob/living/user, modifiers, in_attack_chain = TRUE)
if(!in_attack_chain && HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user)))
return BATON_ATTACK_DONE
cooldown_check = world.time + cooldown
if(on_stun_sound)
playsound(get_turf(src), on_stun_sound, on_stun_volume, TRUE, -1)
if(user)
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
if(log_stun_attack)
log_combat(user, target, "stun attacked", src)
if(baton_effect(target, user, modifiers) && user)
set_batoned(target, user, cooldown)
/obj/item/melee/baton/proc/baton_effect(mob/living/target, mob/living/user, modifiers, stun_override)
var/trait_check = HAS_TRAIT(target, TRAIT_BATON_RESISTANCE)
if(iscyborg(target))
if(!affect_cyborg)
return FALSE
target.flash_act(affect_silicon = TRUE)
target.Paralyze((isnull(stun_override) ? stun_time_cyborg : stun_override) * (trait_check ? 0.1 : 1))
additional_effects_cyborg(target, user)
else
if(ishuman(target))
var/mob/living/carbon/human/human_target = target
if(prob(force_say_chance))
human_target.force_say()
target.apply_damage(stamina_damage, STAMINA)
if(!trait_check)
target.Knockdown((isnull(stun_override) ? knockdown_time : stun_override))
additional_effects_non_cyborg(target, user)
SEND_SIGNAL(target, COMSIG_MOB_BATONED, user, src)
return TRUE
/// Description for trying to stun when still on cooldown.
/obj/item/melee/baton/proc/get_wait_description()
return
/// Default message for stunning a living, non-cyborg mob.
/obj/item/melee/baton/proc/get_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] knocks [target] down with [src]!")
.["local"] = span_userdanger("[user] knocks you down with [src]!")
return .
/// Default message for stunning a cyborg.
/obj/item/melee/baton/proc/get_cyborg_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] pulses [target]'s sensors with the baton!")
.["local"] = span_danger("You pulse [target]'s sensors with the baton!")
return .
/// Default message for trying to stun a cyborg with a baton that can't stun cyborgs.
/obj/item/melee/baton/proc/get_unga_dunga_cyborg_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] tries to knock down [target] with [src], and predictably fails!") //look at this duuuuuude
.["local"] = span_userdanger("[user] tries to... knock you down with [src]?") //look at the top of his head!
return .
/// Contains any special effects that we apply to living, non-cyborg mobs we stun. Does not include applying a knockdown, dealing stamina damage, etc.
/obj/item/melee/baton/proc/additional_effects_non_cyborg(mob/living/target, mob/living/user)
return
/// Contains any special effects that we apply to cyborgs we stun. Does not include flashing the cyborg's screen, hardstunning them, etc.
/obj/item/melee/baton/proc/additional_effects_cyborg(mob/living/target, mob/living/user)
return
/obj/item/melee/baton/proc/set_batoned(mob/living/target, mob/living/user, cooldown)
if(!cooldown)
return
var/user_ref = REF(user) // avoids harddels.
ADD_TRAIT(target, TRAIT_IWASBATONED, user_ref)
addtimer(TRAIT_CALLBACK_REMOVE(target, TRAIT_IWASBATONED, user_ref), cooldown)
/obj/item/melee/baton/proc/clumsy_check(mob/living/user, mob/living/intented_target)
if(!active || !HAS_TRAIT(user, TRAIT_CLUMSY) || prob(50))
return FALSE
user.visible_message(span_danger("[user] accidentally hits [user.p_them()]self over the head with [src]! What a doofus!"), span_userdanger("You accidentally hit yourself over the head with [src]!"))
if(iscyborg(user))
if(affect_cyborg)
user.flash_act(affect_silicon = TRUE)
user.Paralyze(clumsy_knockdown_time)
additional_effects_cyborg(user, user) // user is the target here
if(on_stun_sound)
playsound(get_turf(src), on_stun_sound, on_stun_volume, TRUE, -1)
else
playsound(get_turf(src), 'sound/effects/bang.ogg', 10, TRUE)
else
//straight up always force say for clumsy humans
if(ishuman(user))
var/mob/living/carbon/human/human_user = user
human_user.force_say()
user.Knockdown(clumsy_knockdown_time)
user.apply_damage(stamina_damage, STAMINA)
additional_effects_non_cyborg(user, user) // user is the target here
if(on_stun_sound)
playsound(get_turf(src), on_stun_sound, on_stun_volume, TRUE, -1)
user.apply_damage(2*force, BRUTE, BODY_ZONE_HEAD, attacking_item = src)
log_combat(user, user, "accidentally stun attacked [user.p_them()]self due to their clumsiness", src)
if(stun_animation)
user.do_attack_animation(user)
return
/obj/item/conversion_kit
name = "conversion kit"
desc = "A strange box containing wood working tools and an instruction paper to turn stun batons into something else."
icon = 'icons/obj/storage/box.dmi'
icon_state = "uk"
custom_price = PAYCHECK_COMMAND * 4.5
/obj/item/melee/baton/telescopic
name = "telescopic baton"
desc = "A compact yet robust personal defense weapon. Can be concealed when folded."
icon = 'icons/obj/weapons/baton.dmi'
icon_state = "telebaton"
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
inhand_icon_state = null
attack_verb_continuous = list("hits", "pokes")
attack_verb_simple = list("hit", "poke")
worn_icon_state = "tele_baton"
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_SMALL
item_flags = NONE
force = 0
bare_wound_bonus = 5
clumsy_knockdown_time = 15 SECONDS
active = FALSE
var/folded_drop_sound = 'sound/items/baton/telescopic_baton_folded_drop.ogg'
var/folded_pickup_sound = 'sound/items/baton/telescopic_baton_folded_pickup.ogg'
var/unfolded_drop_sound = 'sound/items/baton/telescopic_baton_unfolded_drop.ogg'
var/unfolded_pickup_sound = 'sound/items/baton/telescopic_baton_unfolded_pickup.ogg'
pickup_sound = 'sound/items/baton/telescopic_baton_folded_pickup.ogg'
drop_sound = 'sound/items/baton/telescopic_baton_folded_drop.ogg'
sound_vary = TRUE
/// The sound effecte played when our baton is extended.
var/on_sound = 'sound/items/weapons/batonextend.ogg'
/// The inhand iconstate used when our baton is extended.
var/on_inhand_icon_state = "nullrod"
/// The force on extension.
var/active_force = 10
/obj/item/melee/baton/telescopic/Initialize(mapload)
. = ..()
AddComponent( \
/datum/component/transforming, \
force_on = active_force, \
hitsound_on = hitsound, \
w_class_on = WEIGHT_CLASS_NORMAL, \
clumsy_check = FALSE, \
attack_verb_continuous_on = list("smacks", "strikes", "cracks", "beats"), \
attack_verb_simple_on = list("smack", "strike", "crack", "beat"), \
)
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
/obj/item/melee/baton/telescopic/additional_effects_non_cyborg(mob/living/target, mob/living/user)
target.apply_status_effect(/datum/status_effect/next_shove_stuns)
/obj/item/melee/baton/telescopic/suicide_act(mob/living/user)
var/mob/living/carbon/human/human_user = user
var/obj/item/organ/brain/our_brain = human_user.get_organ_by_type(/obj/item/organ/brain)
user.visible_message(span_suicide("[user] stuffs [src] up [user.p_their()] nose and presses the 'extend' button! It looks like [user.p_theyre()] trying to clear [user.p_their()] mind."))
if(active)
playsound(src, on_sound, 50, TRUE)
add_fingerprint(user)
else
attack_self(user)
sleep(0.3 SECONDS)
if (QDELETED(human_user))
return
if(!QDELETED(our_brain))
human_user.organs -= our_brain
qdel(our_brain)
new /obj/effect/gibspawner/generic(human_user.drop_location(), human_user)
return BRUTELOSS
/*
* Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM].
*
* Gives feedback to the user and makes it show up inhand.
*/
/obj/item/melee/baton/telescopic/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
src.active = active
inhand_icon_state = active ? on_inhand_icon_state : null // When inactive, there is no inhand icon_state.
if(user)
balloon_alert(user, active ? "extended" : "collapsed")
if(!active)
drop_sound = folded_drop_sound
pickup_sound = folded_pickup_sound
else
drop_sound = unfolded_drop_sound
pickup_sound = unfolded_pickup_sound
playsound(src, on_sound, 50, TRUE)
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/melee/baton/telescopic/contractor_baton
name = "contractor baton"
desc = "A compact, specialised baton assigned to Syndicate contractors. Applies light electrical shocks to targets."
icon = 'icons/obj/weapons/baton.dmi'
icon_state = "contractor_baton"
worn_icon_state = "contractor_baton"
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_SMALL
item_flags = NONE
force = 5
cooldown = 2.5 SECONDS
force_say_chance = 80 //very high force say chance because it's funny
stamina_damage = 85
clumsy_knockdown_time = 24 SECONDS
affect_cyborg = TRUE
on_stun_sound = 'sound/items/weapons/contractor_baton/contractorbatonhit.ogg'
unfolded_drop_sound = 'sound/items/baton/contractor_baton_unfolded_pickup.ogg'
unfolded_pickup_sound = 'sound/items/baton/contractor_baton_unfolded_pickup.ogg'
on_inhand_icon_state = "contractor_baton_on"
on_sound = 'sound/items/weapons/contractorbatonextend.ogg'
active_force = 16
/obj/item/melee/baton/telescopic/contractor_baton/get_wait_description()
return span_danger("The baton is still charging!")
/obj/item/melee/baton/telescopic/contractor_baton/additional_effects_non_cyborg(mob/living/target, mob/living/user)
. = ..()
target.set_jitter_if_lower(40 SECONDS)
target.set_stutter_if_lower(40 SECONDS)
/obj/item/melee/baton/security
name = "stun baton"
desc = "A stun baton for incapacitating people with."
desc_controls = "Left click to stun, right click to harm."
icon = 'icons/obj/weapons/baton.dmi'
icon_state = "stunbaton"
inhand_icon_state = "baton"
worn_icon_state = "baton"
force = 10
wound_bonus = 0
attack_verb_continuous = list("beats")
attack_verb_simple = list("beat")
armor_type = /datum/armor/baton_security
throwforce = 7
force_say_chance = 50
stamina_damage = 60
knockdown_time = 5 SECONDS
clumsy_knockdown_time = 15 SECONDS
cooldown = 2.5 SECONDS
on_stun_sound = 'sound/items/weapons/egloves.ogg'
on_stun_volume = 50
active = FALSE
context_living_rmb_active = "Harmful Stun"
light_range = 1.5
light_system = OVERLAY_LIGHT
light_on = FALSE
light_color = LIGHT_COLOR_ORANGE
light_power = 0.5
var/inactive_drop_sound = 'sound/items/baton/stun_baton_inactive_drop.ogg'
var/inactive_pickup_sound = 'sound/items/baton/stun_baton_inactive_pickup.ogg'
var/active_drop_sound = 'sound/items/baton/stun_baton_active_drop.ogg'
var/active_pickup_sound = 'sound/items/baton/stun_baton_active_pickup.ogg'
drop_sound = 'sound/items/baton/stun_baton_inactive_drop.ogg'
pickup_sound = 'sound/items/baton/stun_baton_inactive_pickup.ogg'
sound_vary = TRUE
var/throw_stun_chance = 35
var/obj/item/stock_parts/power_store/cell
var/preload_cell_type //if not empty the baton starts with this type of cell
var/cell_hit_cost = STANDARD_CELL_CHARGE
var/can_remove_cell = TRUE
var/convertible = TRUE //if it can be converted with a conversion kit
/datum/armor/baton_security
bomb = 50
fire = 80
acid = 80
/obj/item/melee/baton/security/Initialize(mapload)
. = ..()
if(preload_cell_type)
if(!ispath(preload_cell_type, /obj/item/stock_parts/power_store/cell))
log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].")
else
cell = new preload_cell_type(src)
RegisterSignal(src, COMSIG_ATOM_ATTACKBY, PROC_REF(convert))
update_appearance()
/obj/item/melee/baton/security/get_cell()
return cell
/obj/item/melee/baton/security/suicide_act(mob/living/user)
if(cell?.charge && active)
user.visible_message(span_suicide("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!"))
attack(user, user)
return FIRELOSS
else
user.visible_message(span_suicide("[user] is shoving the [name] down their throat! It looks like [user.p_theyre()] trying to commit suicide!"))
return OXYLOSS
/obj/item/melee/baton/security/Destroy()
if(cell)
QDEL_NULL(cell)
UnregisterSignal(src, COMSIG_ATOM_ATTACKBY)
return ..()
/obj/item/melee/baton/security/proc/convert(datum/source, obj/item/item, mob/user)
SIGNAL_HANDLER
if(!istype(item, /obj/item/conversion_kit) || !convertible)
return
var/turf/source_turf = get_turf(src)
var/obj/item/melee/baton/baton = new (source_turf)
baton.alpha = 20
playsound(source_turf, 'sound/items/tools/drill_use.ogg', 80, TRUE, -1)
animate(src, alpha = 0, time = 1 SECONDS)
animate(baton, alpha = 255, time = 1 SECONDS)
qdel(item)
qdel(src)
/obj/item/melee/baton/security/on_saboteur(datum/source, disrupt_duration)
. = ..()
if(!active)
return
turn_off()
update_appearance()
return TRUE
/obj/item/melee/baton/security/Exited(atom/movable/mov_content)
. = ..()
if(mov_content == cell)
cell = null
turn_off()
update_appearance()
/obj/item/melee/baton/security/update_icon_state()
if(active)
icon_state = "[initial(icon_state)]_active"
return ..()
if(!cell)
icon_state = "[initial(icon_state)]_nocell"
return ..()
icon_state = "[initial(icon_state)]"
return ..()
/obj/item/melee/baton/security/examine(mob/user)
. = ..()
if(cell)
. += span_notice("\The [src] is [round(cell.percent())]% charged.")
else
. += span_warning("\The [src] does not have a power source installed.")
/obj/item/melee/baton/security/screwdriver_act(mob/living/user, obj/item/tool)
if(tryremovecell(user))
tool.play_tool_sound(src)
return TRUE
/obj/item/melee/baton/security/attackby(obj/item/item, mob/user, params)
if(istype(item, /obj/item/stock_parts/power_store/cell))
var/obj/item/stock_parts/power_store/cell/active_cell = item
if(cell)
to_chat(user, span_warning("[src] already has a cell!"))
else
if(active_cell.maxcharge < cell_hit_cost)
to_chat(user, span_notice("[src] requires a higher capacity cell."))
return
if(!user.transferItemToLoc(item, src))
return
cell = item
to_chat(user, span_notice("You install a cell in [src]."))
update_appearance()
else
return ..()
/obj/item/melee/baton/security/proc/tryremovecell(mob/user)
if(cell && can_remove_cell)
cell.forceMove(drop_location())
to_chat(user, span_notice("You remove the cell from [src]."))
return TRUE
return FALSE
/obj/item/melee/baton/security/attack_self(mob/user)
if(cell?.charge >= cell_hit_cost && !active)
turn_on(user)
balloon_alert(user, "turned on")
else
turn_off()
if(!cell)
balloon_alert(user, "no power source!")
else if(cell?.charge < cell_hit_cost)
balloon_alert(user, "out of charge!")
else
balloon_alert(user, "turned off")
add_fingerprint(user)
/// Toggles the stun baton's light
/obj/item/melee/baton/security/proc/toggle_light()
set_light_on(!light_on)
return
/obj/item/melee/baton/security/proc/turn_on(mob/user)
active = TRUE
playsound(src, SFX_SPARKS, 75, TRUE, -1)
update_appearance()
toggle_light()
do_sparks(1, TRUE, src)
drop_sound = active_drop_sound
pickup_sound = active_pickup_sound
/obj/item/melee/baton/security/proc/turn_off()
active = FALSE
set_light_on(FALSE)
update_appearance()
playsound(src, SFX_SPARKS, 75, TRUE, -1)
drop_sound = inactive_drop_sound
pickup_sound = inactive_pickup_sound
/obj/item/melee/baton/security/proc/deductcharge(deducted_charge)
if(!cell)
return
//Note this value returned is significant, as it will determine
//if a stun is applied or not
. = cell.use(deducted_charge)
if(active && cell.charge < cell_hit_cost)
//we're below minimum, turn off
turn_off()
/obj/item/melee/baton/security/clumsy_check(mob/living/carbon/human/user)
. = ..()
if(.)
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
deductcharge(cell_hit_cost)
/// Handles prodding targets with turned off stunbatons and right clicking stun'n'bash
/obj/item/melee/baton/security/baton_attack(mob/living/target, mob/living/user, modifiers)
. = ..()
if(. != BATON_DO_NORMAL_ATTACK)
return
if(LAZYACCESS(modifiers, RIGHT_CLICK))
if(active && cooldown_check <= world.time && !check_parried(target, user))
finalize_baton_attack(target, user, modifiers, in_attack_chain = FALSE)
else if(!user.combat_mode)
target.visible_message(span_warning("[user] prods [target] with [src]. Luckily it was off."), \
span_warning("[user] prods you with [src]. Luckily it was off."))
return BATON_ATTACK_DONE
/obj/item/melee/baton/security/baton_effect(mob/living/target, mob/living/user, modifiers, stun_override)
if(iscyborg(loc))
var/mob/living/silicon/robot/robot = loc
if(!robot || !robot.cell || !robot.cell.use(cell_hit_cost))
return FALSE
else if(!deductcharge(cell_hit_cost))
return FALSE
stun_override = 0 //Avoids knocking people down prematurely.
return ..()
/*
* After a target is hit, we apply some status effects.
* After a period of time, we then check to see what stun duration we give.
*/
/obj/item/melee/baton/security/additional_effects_non_cyborg(mob/living/target, mob/living/user)
target.set_jitter_if_lower(40 SECONDS)
target.set_confusion_if_lower(10 SECONDS)
target.set_stutter_if_lower(16 SECONDS)
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
addtimer(CALLBACK(src, PROC_REF(apply_stun_effect_end), target), 2 SECONDS)
/// After the initial stun period, we check to see if the target needs to have the stun applied.
/obj/item/melee/baton/security/proc/apply_stun_effect_end(mob/living/target)
var/trait_check = HAS_TRAIT(target, TRAIT_BATON_RESISTANCE) //var since we check it in out to_chat as well as determine stun duration
if(!target.IsKnockdown())
to_chat(target, span_warning("Your muscles seize, making you collapse[trait_check ? ", but your body quickly recovers..." : "!"]"))
if(!trait_check)
target.Knockdown(knockdown_time)
/obj/item/melee/baton/security/get_wait_description()
return span_danger("The baton is still charging!")
/obj/item/melee/baton/security/get_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] stuns [target] with [src]!")
.["local"] = span_userdanger("[user] stuns you with [src]!")
/obj/item/melee/baton/security/get_unga_dunga_cyborg_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] tries to stun [target] with [src], and predictably fails!")
.["local"] = span_userdanger("[user] tries to... stun you with [src]?")
/obj/item/melee/baton/security/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(!. && active && prob(throw_stun_chance) && isliving(hit_atom))
finalize_baton_attack(hit_atom, thrownby?.resolve(), in_attack_chain = FALSE)
/obj/item/melee/baton/security/emp_act(severity)
. = ..()
if (!cell)
return
if (!(. & EMP_PROTECT_SELF))
deductcharge(STANDARD_CELL_CHARGE / severity)
if (cell.charge >= cell_hit_cost)
var/scramble_time
scramble_mode()
for(var/loops in 1 to rand(6, 12))
scramble_time = rand(5, 15) / (1 SECONDS)
addtimer(CALLBACK(src, PROC_REF(scramble_mode)), scramble_time*loops * (1 SECONDS))
/obj/item/melee/baton/security/proc/scramble_mode()
if (!cell || cell.charge < cell_hit_cost)
return
active = !active
toggle_light()
do_sparks(1, TRUE, src)
playsound(src, SFX_SPARKS, 75, TRUE, -1)
update_appearance()
/obj/item/melee/baton/security/loaded //this one starts with a cell pre-installed.
preload_cell_type = /obj/item/stock_parts/power_store/cell/high
//Makeshift stun baton. Replacement for stun gloves.
/obj/item/melee/baton/security/cattleprod
name = "stunprod"
desc = "An improvised stun baton."
desc_controls = "Left click to stun, right click to harm."
icon = 'icons/obj/weapons/spear.dmi'
icon_state = "stunprod"
inhand_icon_state = "prod"
worn_icon_state = null
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
w_class = WEIGHT_CLASS_HUGE
force = 3
throwforce = 5
cell_hit_cost = STANDARD_CELL_CHARGE * 2
throw_stun_chance = 10
slot_flags = ITEM_SLOT_BACK
convertible = FALSE
var/obj/item/assembly/igniter/sparkler
///Determines whether or not we can improve the cattleprod into a new type. Prevents turning the cattleprod subtypes into different subtypes, or wasting materials on making it....another version of itself.
var/can_upgrade = TRUE
/obj/item/melee/baton/security/cattleprod/Initialize(mapload)
. = ..()
sparkler = new (src)
/obj/item/melee/baton/security/cattleprod/attackby(obj/item/item, mob/user, params)//handles sticking a crystal onto a stunprod to make an improved cattleprod
if(!istype(item, /obj/item/stack))
return ..()
if(!can_upgrade)
user.visible_message(span_warning("This prod is already improved!"))
return ..()
if(cell)
user.visible_message(span_warning("You can't put the crystal onto the stunprod while it has a power cell installed!"))
return ..()
var/our_prod
if(istype(item, /obj/item/stack/ore/bluespace_crystal))
var/obj/item/stack/ore/bluespace_crystal/our_crystal = item
our_crystal.use(1)
our_prod = /obj/item/melee/baton/security/cattleprod/teleprod
else if(istype(item, /obj/item/stack/telecrystal))
var/obj/item/stack/telecrystal/our_crystal = item
our_crystal.use(1)
our_prod = /obj/item/melee/baton/security/cattleprod/telecrystalprod
else
to_chat(user, span_notice("You don't think the [item.name] will do anything to improve the [src]."))
return ..()
to_chat(user, span_notice("You place the [item.name] firmly into the igniter."))
remove_item_from_storage(user)
qdel(src)
var/obj/item/melee/baton/security/cattleprod/brand_new_prod = new our_prod(user.loc)
user.put_in_hands(brand_new_prod)
/obj/item/melee/baton/security/cattleprod/baton_effect()
if(!sparkler.activate())
return BATON_ATTACK_DONE
return ..()
/obj/item/melee/baton/security/cattleprod/Destroy()
if(sparkler)
QDEL_NULL(sparkler)
return ..()
/obj/item/melee/baton/security/boomerang
name = "\improper OZtek Boomerang"
desc = "A device invented in 2486 for the great Space Emu War by the confederacy of Australicus, these high-tech boomerangs also work exceptionally well at stunning crewmembers. Just be careful to catch it when thrown!"
throw_speed = 1
icon = 'icons/obj/weapons/thrown.dmi'
icon_state = "boomerang"
inhand_icon_state = "boomerang"
force = 5
throwforce = 5
throw_range = 5
cell_hit_cost = STANDARD_CELL_CHARGE * 2
throw_stun_chance = 99 //Have you prayed today?
convertible = FALSE
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*2, /datum/material/silver = SHEET_MATERIAL_AMOUNT*5, /datum/material/gold = SHEET_MATERIAL_AMOUNT)
/obj/item/melee/baton/security/boomerang/Initialize(mapload)
. = ..()
AddComponent(/datum/component/boomerang, throw_range+2, TRUE)
/obj/item/melee/baton/security/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(!active)
return ..()
var/caught = hit_atom.hitby(src, skipcatch = FALSE, hitpush = FALSE, throwingdatum = throwingdatum)
var/mob/thrown_by = thrownby?.resolve()
if(isliving(hit_atom) && !iscyborg(hit_atom) && !caught && prob(throw_stun_chance))//if they are a living creature and they didn't catch it
finalize_baton_attack(hit_atom, thrown_by, in_attack_chain = FALSE)
/obj/item/melee/baton/security/boomerang/loaded //Same as above, comes with a cell.
preload_cell_type = /obj/item/stock_parts/power_store/cell/high
/obj/item/melee/baton/security/cattleprod/teleprod
name = "teleprod"
desc = "A prod with a bluespace crystal on the end. The crystal doesn't look too fun to touch."
w_class = WEIGHT_CLASS_NORMAL
icon_state = "teleprod"
inhand_icon_state = "teleprod"
slot_flags = null
can_upgrade = FALSE
/obj/item/melee/baton/security/cattleprod/teleprod/clumsy_check(mob/living/carbon/human/user)
. = ..()
if(!.)
return
do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/item/melee/baton/security/cattleprod/teleprod/baton_effect(mob/living/target, mob/living/user, modifiers, stun_override)
. = ..()
if(!. || target.move_resist >= MOVE_FORCE_OVERPOWERING)
return
do_teleport(target, get_turf(target), 15, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/item/melee/baton/security/cattleprod/telecrystalprod
name = "snatcherprod"
desc = "A prod with a telecrystal on the end. It sparks with a desire for theft and subversion."
w_class = WEIGHT_CLASS_NORMAL
icon_state = "telecrystalprod"
inhand_icon_state = "telecrystalprod"
slot_flags = null
throw_stun_chance = 50 //I think it'd be funny
can_upgrade = FALSE
/obj/item/melee/baton/security/cattleprod/telecrystalprod/clumsy_check(mob/living/carbon/human/user)
. = ..()
if(!.)
return
do_teleport(src, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE) //Wait, where did it go?
/obj/item/melee/baton/security/cattleprod/telecrystalprod/baton_effect(mob/living/target, mob/living/user, modifiers, stun_override)
. = ..()
if(!.)
return
var/obj/item/stuff_in_hand = target.get_active_held_item()
if(!user || !stuff_in_hand || !target.temporarilyRemoveItemFromInventory(stuff_in_hand))
return
if(user.put_in_inactive_hand(stuff_in_hand))
stuff_in_hand.loc.visible_message(span_warning("[stuff_in_hand] suddenly appears in [user]'s hand!"))
else
stuff_in_hand.forceMove(user.drop_location())
stuff_in_hand.loc.visible_message(span_warning("[stuff_in_hand] suddenly appears!"))