mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-31 12:01:47 +00:00
588 lines
23 KiB
Plaintext
588 lines
23 KiB
Plaintext
//Security modules for MODsuits
|
|
|
|
///Magnetic Harness - Automatically puts guns in your suit storage when you drop them.
|
|
/obj/item/mod/module/magnetic_harness
|
|
name = "MOD magnetic harness module"
|
|
desc = "Based off old TerraGov harness kits, this magnetic harness automatically attaches dropped guns back to the wearer."
|
|
icon_state = "mag_harness"
|
|
complexity = 2
|
|
use_energy_cost = DEFAULT_CHARGE_DRAIN
|
|
incompatible_modules = list(/obj/item/mod/module/magnetic_harness)
|
|
required_slots = list(ITEM_SLOT_OCLOTHING)
|
|
/// Time before we activate the magnet.
|
|
var/magnet_delay = 0.8 SECONDS
|
|
/// The typecache of all guns we allow.
|
|
var/static/list/guns_typecache
|
|
/// The guns already allowed by the modsuit chestplate.
|
|
var/list/already_allowed_guns = list()
|
|
|
|
/obj/item/mod/module/magnetic_harness/Initialize(mapload)
|
|
. = ..()
|
|
if(!guns_typecache)
|
|
guns_typecache = typecacheof(list(/obj/item/gun/ballistic, /obj/item/gun/energy, /obj/item/gun/grenadelauncher, /obj/item/gun/chem, /obj/item/gun/syringe))
|
|
|
|
/obj/item/mod/module/magnetic_harness/on_install()
|
|
var/obj/item/clothing/suit = mod.get_part_from_slot(ITEM_SLOT_OCLOTHING)
|
|
if(!istype(suit))
|
|
return
|
|
already_allowed_guns = guns_typecache & suit.allowed
|
|
suit.allowed |= guns_typecache
|
|
|
|
/obj/item/mod/module/magnetic_harness/on_uninstall(deleting = FALSE)
|
|
if(deleting)
|
|
return
|
|
var/obj/item/clothing/suit = mod.get_part_from_slot(ITEM_SLOT_OCLOTHING)
|
|
if(!istype(suit))
|
|
return
|
|
suit.allowed -= (guns_typecache - already_allowed_guns)
|
|
|
|
/obj/item/mod/module/magnetic_harness/on_part_activation()
|
|
RegisterSignal(mod.wearer, COMSIG_MOB_UNEQUIPPED_ITEM, PROC_REF(check_dropped_item))
|
|
|
|
/obj/item/mod/module/magnetic_harness/on_part_deactivation(deleting = FALSE)
|
|
UnregisterSignal(mod.wearer, COMSIG_MOB_UNEQUIPPED_ITEM)
|
|
|
|
/obj/item/mod/module/magnetic_harness/proc/check_dropped_item(datum/source, obj/item/dropped_item, force, new_location)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!is_type_in_typecache(dropped_item, guns_typecache))
|
|
return
|
|
if(new_location != get_turf(src))
|
|
return
|
|
addtimer(CALLBACK(src, PROC_REF(pick_up_item), dropped_item), magnet_delay)
|
|
|
|
/obj/item/mod/module/magnetic_harness/proc/pick_up_item(obj/item/item)
|
|
if(!isturf(item.loc) || !item.Adjacent(mod.wearer))
|
|
return
|
|
if(!mod.wearer.equip_to_slot_if_possible(item, ITEM_SLOT_SUITSTORE, qdel_on_fail = FALSE, disable_warning = TRUE))
|
|
return
|
|
playsound(src, 'sound/items/modsuit/magnetic_harness.ogg', 50, TRUE)
|
|
balloon_alert(mod.wearer, "[item] reattached")
|
|
drain_power(use_energy_cost)
|
|
|
|
///Pepper Shoulders - When hit, reacts with a spray of pepper spray around the user.
|
|
/obj/item/mod/module/pepper_shoulders
|
|
name = "MOD pepper shoulders module"
|
|
desc = "A module that attaches two pepper sprayers on shoulders of a MODsuit, reacting to touch with a spray around the user."
|
|
icon_state = "pepper_shoulder"
|
|
module_type = MODULE_USABLE
|
|
complexity = 1
|
|
use_energy_cost = DEFAULT_CHARGE_DRAIN
|
|
incompatible_modules = list(/obj/item/mod/module/pepper_shoulders)
|
|
cooldown_time = 5 SECONDS
|
|
overlay_state_inactive = "module_pepper"
|
|
overlay_state_use = "module_pepper_used"
|
|
required_slots = list(ITEM_SLOT_OCLOTHING)
|
|
|
|
/obj/item/mod/module/pepper_shoulders/on_part_activation()
|
|
RegisterSignal(mod.wearer, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(on_check_block))
|
|
|
|
/obj/item/mod/module/pepper_shoulders/on_part_deactivation(deleting = FALSE)
|
|
UnregisterSignal(mod.wearer, COMSIG_LIVING_CHECK_BLOCK)
|
|
|
|
/obj/item/mod/module/pepper_shoulders/on_use()
|
|
playsound(src, 'sound/effects/spray.ogg', 30, TRUE, -6)
|
|
var/datum/reagents/capsaicin_holder = new(10)
|
|
capsaicin_holder.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 10)
|
|
var/datum/effect_system/fluid_spread/smoke/chem/quick/smoke = new
|
|
smoke.set_up(1, holder = src, location = get_turf(src), carry = capsaicin_holder)
|
|
smoke.start(log = TRUE)
|
|
QDEL_NULL(capsaicin_holder) // Reagents have a ref to their holder which has a ref to them. No leaks please.
|
|
|
|
/obj/item/mod/module/pepper_shoulders/proc/on_check_block()
|
|
SIGNAL_HANDLER
|
|
|
|
if(!COOLDOWN_FINISHED(src, cooldown_timer))
|
|
return
|
|
if(!check_power(use_energy_cost))
|
|
return
|
|
mod.wearer.visible_message(span_warning("[src] reacts to the attack with a smoke of pepper spray!"), span_notice("Your [src] releases a cloud of pepper spray!"))
|
|
used()
|
|
|
|
///Holster - Instantly holsters any not huge gun.
|
|
/obj/item/mod/module/holster
|
|
name = "MOD holster module"
|
|
desc = "Based off typical storage compartments, this system allows the suit to holster a \
|
|
standard firearm across its surface and allow for extremely quick retrieval. \
|
|
While some users prefer the chest, others the forearm for quick deployment, \
|
|
some law enforcement prefer the holster to extend from the thigh."
|
|
icon_state = "holster"
|
|
module_type = MODULE_USABLE
|
|
complexity = 2
|
|
incompatible_modules = list(/obj/item/mod/module/holster)
|
|
cooldown_time = 0.5 SECONDS
|
|
allow_flags = MODULE_ALLOW_INACTIVE
|
|
required_slots = list(ITEM_SLOT_OCLOTHING|ITEM_SLOT_GLOVES|ITEM_SLOT_FEET)
|
|
/// Gun we have holstered.
|
|
var/obj/item/gun/holstered
|
|
|
|
/obj/item/mod/module/holster/on_use()
|
|
if(!holstered)
|
|
var/obj/item/gun/holding = mod.wearer.get_active_held_item()
|
|
if(!holding)
|
|
balloon_alert(mod.wearer, "nothing to holster!")
|
|
return
|
|
if(!istype(holding) || holding.w_class > WEIGHT_CLASS_BULKY)
|
|
balloon_alert(mod.wearer, "doesn't fit!")
|
|
return
|
|
if(mod.wearer.transferItemToLoc(holding, src, force = FALSE, silent = TRUE))
|
|
holstered = holding
|
|
balloon_alert(mod.wearer, "weapon holstered")
|
|
playsound(src, 'sound/items/weapons/gun/revolver/empty.ogg', 100, TRUE)
|
|
else if(mod.wearer.put_in_active_hand(holstered, forced = FALSE, ignore_animation = TRUE))
|
|
balloon_alert(mod.wearer, "weapon drawn")
|
|
playsound(src, 'sound/items/weapons/gun/revolver/empty.ogg', 100, TRUE)
|
|
else
|
|
balloon_alert(mod.wearer, "holster full!")
|
|
|
|
/obj/item/mod/module/holster/on_uninstall(deleting = FALSE)
|
|
if(holstered)
|
|
holstered.forceMove(drop_location())
|
|
|
|
/obj/item/mod/module/holster/Exited(atom/movable/gone, direction)
|
|
if(gone == holstered)
|
|
holstered = null
|
|
return ..()
|
|
|
|
/obj/item/mod/module/holster/Destroy()
|
|
QDEL_NULL(holstered)
|
|
return ..()
|
|
|
|
///Megaphone - Lets you speak loud.
|
|
/obj/item/mod/module/megaphone
|
|
name = "MOD megaphone module"
|
|
desc = "A microchip megaphone linked to a MODsuit, for very important purposes, like: loudness."
|
|
icon_state = "megaphone"
|
|
module_type = MODULE_TOGGLE
|
|
complexity = 1
|
|
use_energy_cost = DEFAULT_CHARGE_DRAIN * 0.5
|
|
incompatible_modules = list(/obj/item/mod/module/megaphone)
|
|
required_slots = list(ITEM_SLOT_HEAD|ITEM_SLOT_EYES|ITEM_SLOT_MASK)
|
|
/// List of spans we add to the speaker.
|
|
var/list/voicespan = list(SPAN_COMMAND)
|
|
|
|
/obj/item/mod/module/megaphone/on_activation()
|
|
RegisterSignal(mod.wearer, COMSIG_MOB_SAY, PROC_REF(handle_speech))
|
|
|
|
/obj/item/mod/module/megaphone/on_deactivation(display_message = TRUE, deleting = FALSE)
|
|
UnregisterSignal(mod.wearer, COMSIG_MOB_SAY)
|
|
|
|
/obj/item/mod/module/megaphone/proc/handle_speech(datum/source, list/speech_args)
|
|
SIGNAL_HANDLER
|
|
|
|
speech_args[SPEECH_SPANS] |= voicespan
|
|
drain_power(use_energy_cost)
|
|
|
|
///Criminal Capture - Generates hardlight bags you can put people in and sinch.
|
|
/obj/item/mod/module/criminalcapture
|
|
name = "MOD criminal capture module"
|
|
desc = "The private security that had orders to take in people dead were quite \
|
|
happy with their space-proofed suit, but for those who wanted to bring back \
|
|
whomever their targets were still breathing needed a way to \"share\" the \
|
|
space-proofing. And thus: criminal capture! Creates a hardlight prisoner transport bag \
|
|
around the apprehended that has breathable atmospheric conditions."
|
|
icon_state = "criminal_capture"
|
|
module_type = MODULE_ACTIVE
|
|
complexity = 2
|
|
use_energy_cost = DEFAULT_CHARGE_DRAIN * 0.5
|
|
incompatible_modules = list(/obj/item/mod/module/criminalcapture)
|
|
cooldown_time = 0.5 SECONDS
|
|
required_slots = list(ITEM_SLOT_BACK|ITEM_SLOT_BELT)
|
|
/// Time to capture a prisoner.
|
|
var/capture_time = 2.5 SECONDS
|
|
/// Time to dematerialize a bodybag.
|
|
var/packup_time = 1 SECONDS
|
|
/// Typepath of our bodybag
|
|
var/bodybag_type = /obj/structure/closet/body_bag/environmental/prisoner/hardlight
|
|
/// Our linked bodybag.
|
|
var/obj/structure/closet/body_bag/linked_bodybag
|
|
|
|
/obj/item/mod/module/criminalcapture/on_process(seconds_per_tick)
|
|
idle_power_cost = linked_bodybag ? (DEFAULT_CHARGE_DRAIN * 3) : 0
|
|
return ..()
|
|
|
|
/obj/item/mod/module/criminalcapture/on_deactivation(display_message = TRUE, deleting = FALSE)
|
|
if(!linked_bodybag)
|
|
return
|
|
packup()
|
|
|
|
/obj/item/mod/module/criminalcapture/on_select_use(atom/target)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
if(!mod.wearer.Adjacent(target))
|
|
return
|
|
if(target == linked_bodybag)
|
|
playsound(src, 'sound/machines/ding.ogg', 25, TRUE)
|
|
if(!do_after(mod.wearer, packup_time, target = target))
|
|
balloon_alert(mod.wearer, "interrupted!")
|
|
packup()
|
|
return
|
|
if(linked_bodybag)
|
|
return
|
|
var/turf/target_turf = get_turf(target)
|
|
if(target_turf.is_blocked_turf(exclude_mobs = TRUE))
|
|
return
|
|
playsound(src, 'sound/machines/ding.ogg', 25, TRUE)
|
|
if(!do_after(mod.wearer, capture_time, target = target))
|
|
balloon_alert(mod.wearer, "interrupted!")
|
|
return
|
|
if(linked_bodybag)
|
|
return
|
|
linked_bodybag = new bodybag_type(target_turf)
|
|
linked_bodybag.take_contents()
|
|
playsound(linked_bodybag, 'sound/items/weapons/egloves.ogg', 80, TRUE)
|
|
RegisterSignal(linked_bodybag, COMSIG_MOVABLE_MOVED, PROC_REF(check_range))
|
|
RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(check_range))
|
|
|
|
/obj/item/mod/module/criminalcapture/proc/packup()
|
|
if(!linked_bodybag)
|
|
return
|
|
playsound(linked_bodybag, 'sound/items/weapons/egloves.ogg', 80, TRUE)
|
|
apply_wibbly_filters(linked_bodybag)
|
|
animate(linked_bodybag, 0.5 SECONDS, alpha = 50, flags = ANIMATION_PARALLEL)
|
|
addtimer(CALLBACK(src, PROC_REF(delete_bag), linked_bodybag), 0.5 SECONDS)
|
|
linked_bodybag = null
|
|
|
|
/obj/item/mod/module/criminalcapture/proc/check_range()
|
|
SIGNAL_HANDLER
|
|
|
|
if(get_dist(mod.wearer, linked_bodybag) <= 9)
|
|
return
|
|
packup()
|
|
|
|
/obj/item/mod/module/criminalcapture/proc/delete_bag(obj/structure/closet/body_bag/bag)
|
|
if(mod?.wearer)
|
|
UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(check_range))
|
|
balloon_alert(mod.wearer, "bag dissipated")
|
|
bag.open(force = TRUE)
|
|
qdel(bag)
|
|
|
|
///Mirage grenade dispenser - Dispenses grenades that copy the user's appearance.
|
|
/obj/item/mod/module/dispenser/mirage
|
|
name = "MOD mirage grenade dispenser module"
|
|
desc = "This module can create mirage grenades at the user's liking. These grenades create holographic copies of the user."
|
|
icon_state = "mirage_grenade"
|
|
cooldown_time = 20 SECONDS
|
|
overlay_state_inactive = "module_mirage_grenade"
|
|
dispense_type = /obj/item/grenade/mirage
|
|
|
|
/obj/item/mod/module/dispenser/mirage/on_use()
|
|
var/obj/item/grenade/mirage/grenade = ..()
|
|
grenade.arm_grenade(mod.wearer)
|
|
|
|
/obj/item/grenade/mirage
|
|
name = "mirage grenade"
|
|
desc = "A special device that, when activated, produces a holographic copy of the user."
|
|
icon_state = "mirage"
|
|
inhand_icon_state = "flashbang"
|
|
det_time = 3 SECONDS
|
|
/// Mob that threw the grenade.
|
|
var/mob/living/thrower
|
|
|
|
/obj/item/grenade/mirage/arm_grenade(mob/user, delayoverride, msg, volume)
|
|
. = ..()
|
|
thrower = user
|
|
|
|
/obj/item/grenade/mirage/detonate(mob/living/lanced_by)
|
|
. = ..()
|
|
do_sparks(rand(3, 6), FALSE, src)
|
|
if(thrower)
|
|
var/mob/living/simple_animal/hostile/illusion/mirage/mirage = new(get_turf(src))
|
|
mirage.Copy_Parent(thrower, 15 SECONDS)
|
|
qdel(src)
|
|
|
|
///Projectile Dampener - Weakens projectiles in range.
|
|
/obj/item/mod/module/projectile_dampener
|
|
name = "MOD projectile dampener module"
|
|
desc = "Using technology from peaceborgs, this module weakens all projectiles in nearby range."
|
|
icon_state = "projectile_dampener"
|
|
module_type = MODULE_TOGGLE
|
|
complexity = 3
|
|
active_power_cost = DEFAULT_CHARGE_DRAIN
|
|
incompatible_modules = list(/obj/item/mod/module/projectile_dampener)
|
|
cooldown_time = 1.5 SECONDS
|
|
required_slots = list(ITEM_SLOT_BACK|ITEM_SLOT_BELT)
|
|
/// Radius of the dampening field.
|
|
var/field_radius = 2
|
|
/// Damage multiplier on projectiles.
|
|
var/damage_multiplier = 0.75
|
|
/// Debuff multiplier on projectiles.
|
|
var/debuff_multiplier = 0.66
|
|
/// Speed multiplier on projectiles, higher means slower.
|
|
var/speed_multiplier = 0.4
|
|
/// List of all tracked projectiles.
|
|
var/list/tracked_projectiles = list()
|
|
/// Effect image on projectiles.
|
|
var/image/projectile_effect
|
|
/// The dampening field
|
|
var/datum/proximity_monitor/advanced/projectile_dampener/dampening_field
|
|
|
|
/obj/item/mod/module/projectile_dampener/Initialize(mapload)
|
|
. = ..()
|
|
projectile_effect = image('icons/effects/fields.dmi', "projectile_dampen_effect")
|
|
|
|
/obj/item/mod/module/projectile_dampener/on_activation()
|
|
if(istype(dampening_field))
|
|
QDEL_NULL(dampening_field)
|
|
dampening_field = new(mod.wearer, field_radius, TRUE, src)
|
|
|
|
/obj/item/mod/module/projectile_dampener/on_deactivation(display_message, deleting = FALSE)
|
|
QDEL_NULL(dampening_field)
|
|
|
|
///Active Sonar - Displays a hud circle on the turf of any living creatures in the given radius
|
|
/obj/item/mod/module/active_sonar
|
|
name = "MOD active sonar"
|
|
desc = "Ancient tech from the 20th century, this module uses sonic waves to detect living creatures within the user's radius. \
|
|
Its basic function slowly scans around the user for any bio-signatures, however it can be overclocked to scan everywhere at once.\
|
|
Its loud ping is much harder to hide in an indoor station than in the outdoor operations it was designed for."
|
|
icon_state = "active_sonar"
|
|
module_type = MODULE_USABLE
|
|
idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
|
|
use_energy_cost = DEFAULT_CHARGE_DRAIN * 3
|
|
complexity = 2
|
|
incompatible_modules = list(/obj/item/mod/module/active_sonar)
|
|
cooldown_time = 15 SECONDS
|
|
required_slots = list(ITEM_SLOT_HEAD|ITEM_SLOT_EYES|ITEM_SLOT_MASK)
|
|
/// Time between us displaying radial scans
|
|
var/scan_cooldown_time = 0.5 SECONDS
|
|
/// The current slice we're going to scan
|
|
var/scanned_slice = 1
|
|
/// How many slices we make 360
|
|
var/radar_slices = 8 // 45 degrees each
|
|
|
|
/// A list of all creatures in range sorted by angle.
|
|
var/list/sorted_creatures = list()
|
|
/// A keyed list of all creatures
|
|
var/list/keyed_creatures = list()
|
|
|
|
/// Time between us displaying radial scans
|
|
COOLDOWN_DECLARE(scan_cooldown)
|
|
|
|
/obj/item/mod/module/active_sonar/Initialize(mapload)
|
|
. = ..()
|
|
for(var/i in 1 to radar_slices)
|
|
sorted_creatures += list(list())
|
|
|
|
/obj/item/mod/module/active_sonar/on_part_activation()
|
|
RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(sort_all_creatures))
|
|
|
|
/obj/item/mod/module/active_sonar/on_part_deactivation(deleting = FALSE)
|
|
UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED)
|
|
|
|
/// Detects all living creatures within world.view, and returns the amount.
|
|
/obj/item/mod/module/active_sonar/proc/detect_living_creatures()
|
|
var/creatures_detected = 0
|
|
for(var/mob/living/creature in range(world.view, mod.wearer))
|
|
if(creature == mod.wearer || creature.stat == DEAD)
|
|
continue
|
|
if(keyed_creatures[creature])
|
|
creatures_detected++
|
|
continue
|
|
sort_creature_angle(creature)
|
|
RegisterSignal(creature, COMSIG_MOVABLE_MOVED, PROC_REF(sort_creature_angle))
|
|
creatures_detected++
|
|
return creatures_detected
|
|
|
|
/// Swaps around where a creature is, when they move or when they're first detected
|
|
/obj/item/mod/module/active_sonar/proc/sort_creature_angle(mob/living/creature, atom/old_loc, movement_dir, forced)
|
|
SIGNAL_HANDLER
|
|
var/oldgroup = keyed_creatures[creature]
|
|
var/newgroup = round(get_angle(mod.wearer, creature) / (360 / radar_slices)) + 1
|
|
if(oldgroup)
|
|
if(creature.stat == DEAD || get_dist(get_turf(mod.wearer), get_turf(creature)) > world.view)
|
|
sorted_creatures[oldgroup] -= creature
|
|
keyed_creatures -= creature
|
|
UnregisterSignal(creature, COMSIG_MOVABLE_MOVED)
|
|
return
|
|
|
|
if(oldgroup == newgroup)
|
|
return
|
|
|
|
sorted_creatures[oldgroup] -= creature
|
|
|
|
sorted_creatures[newgroup] += creature
|
|
keyed_creatures[creature] = newgroup
|
|
|
|
/// Swaps all creatures when mod.wearer moves
|
|
/obj/item/mod/module/active_sonar/proc/sort_all_creatures(mob/living/wearer, atom/old_loc, movement_dir, forced)
|
|
SIGNAL_HANDLER
|
|
|
|
for(var/mob/living/creature as anything in keyed_creatures)
|
|
sort_creature_angle(creature) // Kinda spaghetti but it honestly seems like the shortest path to the same result
|
|
|
|
/obj/item/mod/module/active_sonar/on_process(seconds_per_tick)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
if(!COOLDOWN_FINISHED(src, cooldown_timer) || !COOLDOWN_FINISHED(src, scan_cooldown))
|
|
return
|
|
detect_living_creatures()
|
|
for(var/mob/living/creature as anything in sorted_creatures[scanned_slice])
|
|
new /obj/effect/temp_visual/sonar_ping(mod.wearer.loc, mod.wearer, creature, "sonar_ping_small", FALSE)
|
|
// Next slice!
|
|
scanned_slice++
|
|
// IT'S ENOUGH SLICES
|
|
if(scanned_slice > radar_slices)
|
|
scanned_slice = 1
|
|
COOLDOWN_START(src, scan_cooldown, scan_cooldown_time)
|
|
|
|
/obj/item/mod/module/active_sonar/on_use()
|
|
balloon_alert(mod.wearer, "readying sonar...")
|
|
playsound(mod.wearer, 'sound/vehicles/mecha/skyfall_power_up.ogg', vol = 20, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
|
|
if(!do_after(mod.wearer, 1.1 SECONDS, target = mod))
|
|
return
|
|
playsound(mod.wearer, 'sound/effects/ping_hit.ogg', vol = 75, vary = TRUE) // Should be audible for the radius of the sonar
|
|
to_chat(mod.wearer, span_notice("You slam your fist into the ground, sending out a sonic wave that detects [detect_living_creatures()] living beings nearby!"))
|
|
for(var/mob/living/creature as anything in keyed_creatures)
|
|
new /obj/effect/temp_visual/sonar_ping(mod.wearer.loc, mod.wearer, creature)
|
|
|
|
#define SHOOTING_ASSISTANT_OFF "Currently Off"
|
|
#define STORMTROOPER_MODE "Quick Fire Stormtrooper"
|
|
#define SHARPSHOOTER_MODE "Slow Ricochet Sharpshooter"
|
|
|
|
/**
|
|
* A module that enhances the user's ability with firearms, with a couple drawbacks:
|
|
* In 'Stormtrooper' mode, the user will be given faster firerate, but lower accuracy.
|
|
* In 'Sharpshooter' mode, the user will have better accuracy and ricochet to his shots, but slower movement speed.
|
|
* Both modes prevent the user from dual wielding guns.
|
|
*/
|
|
/obj/item/mod/module/shooting_assistant
|
|
name = "MOD shooting assistant module"
|
|
desc = "A botched prototype meant to boost the TGMC crayon eaters' ability with firearms. \
|
|
It has only two modes available in its configurations: \
|
|
'Quick Fire Stormtrooper' and 'Slow Ricochet Sharpshooter', \
|
|
both incompatible with dual wielding firearms."
|
|
icon_state = "shooting_assistant"
|
|
module_type = MODULE_PASSIVE
|
|
complexity = 3
|
|
incompatible_modules = list(/obj/item/mod/module/shooting_assistant)
|
|
required_slots = list(ITEM_SLOT_GLOVES)
|
|
var/selected_mode = SHOOTING_ASSISTANT_OFF
|
|
///Association list, the assoc values are the balloon alerts shown to the user when the mode is set.
|
|
var/static/list/available_modes = list(
|
|
SHOOTING_ASSISTANT_OFF = "assistant off",
|
|
STORMTROOPER_MODE = "stormtrooper mode",
|
|
SHARPSHOOTER_MODE = "sharpshooter mode",
|
|
)
|
|
|
|
/obj/item/mod/module/shooting_assistant/get_configuration()
|
|
. = ..()
|
|
.["shooting_mode"] = add_ui_configuration("Mode", "list", selected_mode, assoc_to_keys(available_modes))
|
|
|
|
/obj/item/mod/module/shooting_assistant/configure_edit(key, value)
|
|
switch(key)
|
|
if("shooting_mode")
|
|
set_shooting_mode(value)
|
|
|
|
/obj/item/mod/module/shooting_assistant/proc/set_shooting_mode(new_mode)
|
|
if(new_mode == selected_mode || !mod.active)
|
|
return
|
|
if(new_mode != SHOOTING_ASSISTANT_OFF && !mod.get_charge())
|
|
balloon_alert(mod.wearer, "no charge!")
|
|
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return
|
|
|
|
//Remove the effects of the previously selected mode
|
|
if(mod.active)
|
|
remove_mode_effects()
|
|
|
|
balloon_alert(mod.wearer, available_modes[new_mode])
|
|
selected_mode = new_mode
|
|
|
|
//Apply the effects of the new mode
|
|
if(mod.active)
|
|
apply_mode_effects()
|
|
|
|
/obj/item/mod/module/shooting_assistant/proc/apply_mode_effects()
|
|
switch(selected_mode)
|
|
if(SHOOTING_ASSISTANT_OFF)
|
|
idle_power_cost = 0
|
|
if(STORMTROOPER_MODE)
|
|
idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.4
|
|
mod.wearer.add_traits(list(TRAIT_NO_GUN_AKIMBO, TRAIT_DOUBLE_TAP), REF(src))
|
|
RegisterSignal(mod.wearer, COMSIG_MOB_FIRED_GUN, PROC_REF(stormtrooper_fired_gun))
|
|
if(SHARPSHOOTER_MODE)
|
|
idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.6
|
|
mod.wearer.add_traits(list(TRAIT_NO_GUN_AKIMBO, TRAIT_NICE_SHOT), REF(src))
|
|
RegisterSignal(mod.wearer, COMSIG_MOB_FIRED_GUN, PROC_REF(sharpshooter_fired_gun))
|
|
RegisterSignal(mod.wearer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, PROC_REF(apply_ricochet))
|
|
mod.wearer.add_movespeed_modifier(/datum/movespeed_modifier/shooting_assistant)
|
|
|
|
/obj/item/mod/module/shooting_assistant/proc/remove_mode_effects()
|
|
switch(selected_mode)
|
|
if(STORMTROOPER_MODE)
|
|
UnregisterSignal(mod.wearer, COMSIG_MOB_FIRED_GUN)
|
|
mod.wearer.remove_traits(list(TRAIT_NO_GUN_AKIMBO, TRAIT_DOUBLE_TAP), REF(src))
|
|
if(SHARPSHOOTER_MODE)
|
|
UnregisterSignal(mod.wearer, list(COMSIG_MOB_FIRED_GUN, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE))
|
|
mod.wearer.remove_traits(list(TRAIT_NO_GUN_AKIMBO, TRAIT_NICE_SHOT), REF(src))
|
|
mod.wearer.remove_movespeed_modifier(/datum/movespeed_modifier/shooting_assistant)
|
|
|
|
/obj/item/mod/module/shooting_assistant/drain_power(amount)
|
|
. = ..()
|
|
if(!.)
|
|
set_shooting_mode(SHOOTING_ASSISTANT_OFF)
|
|
|
|
/obj/item/mod/module/shooting_assistant/on_part_activation()
|
|
apply_mode_effects()
|
|
|
|
/obj/item/mod/module/shooting_assistant/on_part_deactivation(deleting = FALSE)
|
|
remove_mode_effects()
|
|
|
|
/obj/item/mod/module/shooting_assistant/proc/stormtrooper_fired_gun(mob/user, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values)
|
|
SIGNAL_HANDLER
|
|
bonus_spread_values[MIN_BONUS_SPREAD_INDEX] += 15
|
|
bonus_spread_values[MAX_BONUS_SPREAD_INDEX] += 25
|
|
|
|
/obj/item/mod/module/shooting_assistant/proc/sharpshooter_fired_gun(mob/user, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values)
|
|
SIGNAL_HANDLER
|
|
bonus_spread_values[MIN_BONUS_SPREAD_INDEX] -= 20
|
|
bonus_spread_values[MAX_BONUS_SPREAD_INDEX] -= 10
|
|
|
|
/obj/item/mod/module/shooting_assistant/proc/apply_ricochet(mob/user, obj/projectile/projectile, datum/fired_from, atom/clicked_atom)
|
|
SIGNAL_HANDLER
|
|
projectile.ricochets_max += 1
|
|
projectile.min_ricochets += 1
|
|
projectile.ricochet_incidence_leeway = 0 //allows the projectile to bounce at any angle.
|
|
projectile.accuracy_falloff = 0
|
|
|
|
#undef SHOOTING_ASSISTANT_OFF
|
|
#undef STORMTROOPER_MODE
|
|
#undef SHARPSHOOTER_MODE
|
|
|
|
/obj/item/mod/module/shove_blocker
|
|
name = "MOD bulwark module"
|
|
desc = "Layers upon layers of shock dampening plates, just to stop you from getting shoved into a wall by an angry mob."
|
|
icon_state = "bulwark"
|
|
complexity = 3
|
|
incompatible_modules = list(/obj/item/mod/module/shove_blocker)
|
|
required_slots = list(ITEM_SLOT_OCLOTHING)
|
|
|
|
/obj/item/mod/module/shove_blocker/on_part_activation()
|
|
mod.wearer.add_traits(list(TRAIT_BRAWLING_KNOCKDOWN_BLOCKED, TRAIT_NO_STAGGER, TRAIT_NO_THROW_HITPUSH), REF(src))
|
|
|
|
/obj/item/mod/module/shove_blocker/on_part_deactivation(deleting = FALSE)
|
|
mod.wearer.remove_traits(list(TRAIT_BRAWLING_KNOCKDOWN_BLOCKED, TRAIT_NO_STAGGER, TRAIT_NO_THROW_HITPUSH), REF(src))
|
|
|
|
/obj/item/mod/module/shove_blocker/locked
|
|
name = "superglued MOD bulwark module"
|
|
desc = "Layers upon layers of shock dampening plates, just to stop you from getting shoved into a wall by an angry mob. Good luck removing this one."
|
|
removable = FALSE
|
|
complexity = 0
|
|
|
|
/obj/item/mod/module/quick_cuff
|
|
name = "MOD restraint assist module"
|
|
desc = "Enhanced gauntlent grip pads that help with placing individuals in restraints more quickly. Doesn't look like they'll come off."
|
|
removable = FALSE
|
|
complexity = 0
|
|
required_slots = list(ITEM_SLOT_GLOVES)
|
|
|
|
/obj/item/mod/module/quick_cuff/on_part_activation()
|
|
. = ..()
|
|
ADD_TRAIT(mod.wearer, TRAIT_FAST_CUFFING, REF(src))
|
|
|
|
/obj/item/mod/module/quick_cuff/on_part_deactivation(deleting = FALSE)
|
|
. = ..()
|
|
REMOVE_TRAIT(mod.wearer, TRAIT_FAST_CUFFING, REF(src))
|