Skip to content

Commit

Permalink
Reworks Tasers (Just for fun) (#439)
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMelbert authored Mar 3, 2024
1 parent c8eb869 commit 7dbcd9d
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 17 deletions.
2 changes: 1 addition & 1 deletion code/game/machinery/porta_turret/portable_turret.dm
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ DEFINE_BITFIELD(turret_flags, list(
else if(iscarbon(A))
var/mob/living/carbon/C = A
//If not emagged, only target carbons that can use items
if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE)))
if(mode != TURRET_LETHAL && (!(C.mobility_flags & MOBILITY_USE) || HAS_TRAIT(C, TRAIT_INCAPACITATED))) // NON-MODULE CHANGE
continue

//If emagged, target all but dead carbons
Expand Down
334 changes: 318 additions & 16 deletions code/modules/projectiles/projectile/energy/stun.dm
Original file line number Diff line number Diff line change
@@ -1,29 +1,331 @@
// NON-MODULE CHANGE : this whole file

/obj/projectile/energy/electrode
name = "electrode"
icon_state = "spark"
color = "#FFFF00"
paralyze = 10 SECONDS
stutter = 10 SECONDS
jitter = 40 SECONDS
color = COLOR_YELLOW
// paralyze = 10 SECONDS
// stutter = 10 SECONDS
// jitter = 40 SECONDS
hitsound = 'sound/weapons/taserhit.ogg'
range = 7
range = 5
tracer_type = /obj/effect/projectile/tracer/stun
muzzle_type = /obj/effect/projectile/muzzle/stun
impact_type = /obj/effect/projectile/impact/stun

/obj/projectile/energy/electrode/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks!
do_sparks(1, TRUE, src)
else if(iscarbon(target))
var/mob/living/carbon/C = target
C.add_mood_event("tased", /datum/mood_event/tased)
SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK)
if(C.dna && C.dna.check_mutation(/datum/mutation/human/hulk))
C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk")
else if(!C.check_stun_immunity(CANKNOCKDOWN))
addtimer(CALLBACK(C, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 5)
if(pierce_hit)
return .
do_sparks(1, TRUE, src)
if(. == BULLET_ACT_BLOCK || !isliving(target) || blocked >= 100)
visible_message(span_warning("The electrodes fail to shock [target], and fall to the ground."))
return .

var/mob/living/tased = target
if(HAS_TRAIT(target, TRAIT_HULK))
tased.say(pick(
";RAAAAAAAARGH!",
";HNNNNNNNNNGGGGGGH!",
";GWAAAAAAAARRRHHH!",
"NNNNNNNNGGGGGGGGHH!",
";AAAAAAARRRGH!",
), forced = "hulk")
if(tased.apply_status_effect(/datum/status_effect/tased, fired_from, firer))
return .
visible_message(span_warning("The electrodes fail to shock [target], and fall to the ground."))
return BULLET_ACT_BLOCK

/obj/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet
do_sparks(1, TRUE, src)
..()
return ..()

#define COMSIG_BEAM_ENTERED "beam_entered"

/// Status effect tracking being tased by someone!
/datum/status_effect/tased
id = "being_tased"
status_type = STATUS_EFFECT_MULTIPLE
alert_type = /atom/movable/screen/alert/status_effect/tazed
tick_interval = 0.25 SECONDS
on_remove_on_mob_delete = TRUE
/// What atom is tasing us?
var/atom/taser
/// What atom is using the atom tasing us? Sometimes the same as the taser, such as with turrets.
var/atom/firer
/// The beam datum representing the taser electrodes
var/datum/beam/tase_line

/datum/status_effect/tased/on_creation(mob/living/new_owner, atom/fired_from, atom/firer)
if(isnull(fired_from) || isnull(firer) || !can_tase_with(fired_from))
qdel(src)
return

if(new_owner.has_status_effect(type) != src)
alert_type = null

. = ..()
if(!.)
return

set_taser(fired_from)
set_firer(firer)

/// Checks if the passed atom is captable of being used to tase someone
/datum/status_effect/tased/proc/can_tase_with(atom/with_what)
if(istype(with_what, /obj/item/gun/energy))
var/obj/item/gun/energy/taser_gun = with_what
if(isnull(taser_gun.cell))
return FALSE

else if(istype(with_what, /obj/machinery))
var/obj/machinery/taser_machine = with_what
if(!taser_machine.is_operational)
return FALSE

return TRUE

/// Actually does the tasing with the passed atom
/// Returns TRUE if the tasing was successful, FALSE if it failed
/datum/status_effect/tased/proc/do_tase_with(atom/with_what, seconds_between_ticks)
if(!can_see(taser, owner, 5))
return FALSE
if(istype(with_what, /obj/item/gun/energy))
var/obj/item/gun/energy/taser_gun = with_what
if(!taser_gun.cell?.use(60 * seconds_between_ticks))
return FALSE
taser_gun.update_appearance()
return TRUE

if(istype(taser, /obj/machinery))
var/obj/machinery/taser_machine = taser
if(!taser_machine.is_operational)
return FALSE
// We can't measure the output of this but if we use too much power the area will depower -> depower the machine -> stop taze next tick
taser_machine.use_power(60 * seconds_between_ticks)
return TRUE

if(istype(taser, /obj/item/mecha_parts/mecha_equipment))
var/obj/item/mecha_parts/mecha_equipment/taser_equipment = taser
if(!taser_equipment.chassis \
|| !taser_equipment.active \
|| taser_equipment.get_integrity() <= 1 \
|| taser_equipment.chassis.is_currently_ejecting \
|| taser_equipment.chassis.equipment_disabled \
|| !taser_equipment.chassis.use_power(60 * seconds_between_ticks))
return FALSE
return TRUE

return TRUE

/datum/status_effect/tased/on_apply()
if(issilicon(owner) \
|| istype(owner, /mob/living/basic/bot) \
|| istype(owner, /mob/living/simple_animal/bot) \
|| HAS_TRAIT(owner, TRAIT_PIERCEIMMUNE))
return FALSE

RegisterSignal(owner, COMSIG_LIVING_RESIST, PROC_REF(try_remove_taser))
SEND_SIGNAL(owner, COMSIG_LIVING_MINOR_SHOCK)
owner.add_mood_event("tased", /datum/mood_event/tased)
owner.add_movespeed_modifier(/datum/movespeed_modifier/being_tased)
if(owner.pain_controller?.pain_modifier > 0.5)
owner.pain_emote("scream")
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
human_owner.force_say()
return TRUE

/datum/status_effect/tased/on_remove()
if(istype(taser, /obj/machinery/porta_turret))
var/obj/machinery/porta_turret/taser_turret = taser
taser_turret.manual_control = initial(taser_turret.manual_control)
taser_turret.always_up = initial(taser_turret.always_up)
taser_turret.check_should_process()
else if(istype(taser, /obj/machinery/power/emitter))
var/obj/machinery/power/emitter/taser_emitter = taser
taser_emitter.manual = initial(taser_emitter.manual)

var/mob/living/mob_firer = firer
if(istype(mob_firer))
mob_firer.remove_movespeed_modifier(/datum/movespeed_modifier/tasing_someone)

owner.remove_movespeed_modifier(/datum/movespeed_modifier/being_tased)
if(!QDELING(owner))
owner.adjust_jitter_up_to(10 SECONDS, 1 MINUTES)

taser = null
firer = null
QDEL_NULL(tase_line)

/datum/status_effect/tased/tick(seconds_between_ticks)
if(!do_tase_with(taser, seconds_between_ticks))
end_tase()
return
if(owner.check_stun_immunity(CANSTUN|CANKNOCKDOWN))
return
// You are damp, that's bad when you're being tased
if(owner.fire_stacks < 0)
owner.apply_damage(max(1, owner.fire_stacks * -0.5 * seconds_between_ticks), FIRE, spread_damage = TRUE)
if(SPT_PROB(25, seconds_between_ticks))
do_sparks(1, FALSE, owner)

owner.set_stutter_if_lower(10 SECONDS)
owner.set_jitter_if_lower(20 SECONDS)
owner.cause_pain(BODY_ZONES_ALL, 2 * seconds_between_ticks, BURN)
owner.apply_damage(120 * seconds_between_ticks * (owner.pain_controller?.pain_modifier || 1), STAMINA)
if(owner.stat <= SOFT_CRIT)
owner.do_jitter_animation(INFINITY) // maximum POWER

/// Sets the passed atom as the "taser"
/datum/status_effect/tased/proc/set_taser(atom/new_taser)
taser = new_taser
RegisterSignals(taser, list(COMSIG_QDELETING, COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED), PROC_REF(end_tase))
RegisterSignal(taser, COMSIG_GUN_TRY_FIRE, PROC_REF(block_firing))
if(istype(taser, /obj/machinery/porta_turret))
var/obj/machinery/porta_turret/taser_turret = taser
taser_turret.manual_control = TRUE
taser_turret.always_up = TRUE
else if(istype(taser, /obj/machinery/power/emitter))
var/obj/machinery/power/emitter/taser_emitter = taser
taser_emitter.manual = TRUE

/// Sets the passed atom as the person operating the taser, the "firer"
/datum/status_effect/tased/proc/set_firer(atom/new_firer)
firer = new_firer
if(taser != firer) // Turrets, notably, are both
RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(end_tase))

// RegisterSignals(firer, list(COMSIG_MOB_SWAP_HANDS), PROC_REF(end_tase))
RegisterSignal(firer, COMSIG_MOB_CLICKON, PROC_REF(user_cancel_tase))

// Ensures AI mobs or turrets don't tase players until they run out of power
var/mob/living/mob_firer = new_firer
if(!istype(mob_firer) || isnull(mob_firer.client))
// If multiple things are tasing the same mob, give up sooner, so they can select a new target potentially
addtimer(CALLBACK(src, PROC_REF(end_tase)), (owner.has_status_effect(type) != src) ? 2 SECONDS : 8 SECONDS)
if(istype(mob_firer))
mob_firer.add_movespeed_modifier(/datum/movespeed_modifier/tasing_someone)

tase_line = firer.Beam(
BeamTarget = owner,
icon = 'maplestation_modules/icons/effects/beam.dmi',
icon_state = "electrodes",
maxdistance = 6,
beam_type = /obj/effect/ebeam/react_to_entry/electrodes,
)
RegisterSignal(tase_line, COMSIG_BEAM_ENTERED, PROC_REF(disrupt_tase))
RegisterSignal(tase_line, COMSIG_QDELETING, PROC_REF(end_tase))
tase_line.RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, TYPE_PROC_REF(/datum/beam, redrawing))

/datum/status_effect/tased/proc/block_firing(...)
SIGNAL_HANDLER
return COMPONENT_CANCEL_GUN_FIRE

/datum/status_effect/tased/proc/user_cancel_tase(mob/living/source, atom/clicked_on, modifiers)
SIGNAL_HANDLER
if(clicked_on != owner)
return NONE
if(LAZYACCESS(modifiers, SHIFT_CLICK))
return NONE
end_tase()
return COMSIG_MOB_CANCEL_CLICKON

/datum/status_effect/tased/proc/end_tase(...)
SIGNAL_HANDLER
if(QDELING(src))
return
owner.visible_message(
span_warning("The electrodes stop shocking [owner], and fall to the ground."),
span_notice("The electrodes stop shocking you, and fall to the ground."),
)
qdel(src)

/datum/status_effect/tased/proc/try_remove_taser(datum/source)
SIGNAL_HANDLER
INVOKE_ASYNC(src, PROC_REF(try_remove_taser_async), source)

/datum/status_effect/tased/proc/try_remove_taser_async()
owner.visible_message(
span_warning("[owner] tries to remove the electrodes!"),
span_notice("You try to remove the electrodes!"),
)
// If embedding was less... difficult to work with, I would make tasers rely on an embedded object to handle this
if(!do_after(src, 5 SECONDS, src, extra_checks = CALLBACK(src, PROC_REF(try_remove_taser_checks)), interaction_key = "tazed"))
return
owner.visible_message(
span_warning("[owner] removes the electrodes from [owner.p_their()] body!"),
span_notice("You remove the electrodes!"),
)
end_tase()

/datum/status_effect/tased/proc/try_remove_taser_checks()
return !QDELETED(src)

/datum/status_effect/tased/proc/disrupt_tase(datum/beam/source, obj/effect/ebeam/beam_effect, atom/movable/entering)
SIGNAL_HANDLER

if(!isliving(entering) || entering == taser || entering == firer || entering == owner)
return
if(entering.pass_flags & (PASSMOB|PASSGRILLE|PASSTABLE))
return
var/mob/living/disruptor = entering
if(disruptor.body_position == LYING_DOWN)
return
disruptor.visible_message(
span_warning("[disruptor] gets tangled in the electrodes!"),
span_warning("You get tangled in the electrodes!"),
)
disruptor.apply_damage(90, STAMINA)
disruptor.Knockdown(5 SECONDS)
disruptor.adjust_jitter_up_to(10 SECONDS, 30 SECONDS)
qdel(src)

/// Screen alert for being tased, clicking does a resist (like being on fire or w/e)
/atom/movable/screen/alert/status_effect/tazed
name = "Tased!"
desc = "Taser electrodes are shocking you! You can resist to try to remove them."
icon_state = "stun"

/atom/movable/screen/alert/status_effect/tazed/Click(location, control, params)
. = ..()
if(!.)
return
var/mob/living/clicker = usr
clicker.resist()

/// Beam subtype which sends a signal to the beam itself when someone walks inside it
/obj/effect/ebeam/react_to_entry

/obj/effect/ebeam/react_to_entry/Initialize(mapload, beam_owner)
. = ..()
if(isnull(owner))
return
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
// Technically the beam is entering the mob but we'll count it
for(var/thing in loc)
on_entered(src, thing)

/obj/effect/ebeam/react_to_entry/proc/on_entered(datum/source, atom/movable/entering)
SIGNAL_HANDLER
SEND_SIGNAL(owner, COMSIG_BEAM_ENTERED, src, entering)

/obj/effect/ebeam/react_to_entry/electrodes
name = "electrodes"
light_system = MOVABLE_LIGHT
light_on = TRUE
light_color = COLOR_YELLOW
light_power = 1
light_range = 1.5

/datum/movespeed_modifier/tasing_someone
multiplicative_slowdown = 2

/datum/movespeed_modifier/being_tased
multiplicative_slowdown = 4

#undef COMSIG_BEAM_ENTERED
1 change: 1 addition & 0 deletions code/modules/vehicles/mecha/equipment/weapons/weapons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
var/obj/projectile/projectile_obj = new projectile(get_turf(src))
projectile_obj.log_override = TRUE //we log being fired ourselves a little further down.
projectile_obj.firer = chassis
projectile_obj.fired_from = src // NON-MODULE CHANGE : fired from the mech, fired by the equipment
projectile_obj.preparePixelProjectile(target, source, modifiers, spread)
if(source.client && isliving(source)) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways
var/mob/living/shooter = source
Expand Down
Binary file added maplestation_modules/icons/effects/beam.dmi
Binary file not shown.

0 comments on commit 7dbcd9d

Please sign in to comment.