[MIRROR] Expanding the Experimental MODsuit Bepis Node with three new modules. [MDB IGNORE] (#21851)

* Expanding the Experimental MODsuit Bepis Node with three new modules. (#75801)

## About The Pull Request
So, I've had this idea to make a contribution to the Bepis feature with
some modsuit stuff. The gimmicky stuff is ok and a good way to even out
the better content since it has game of chance design it has (you can
find those disks in space anyway so...). However, the Experimental
MODsuit node feels very underwhelming right now, compared to how big
that feature is.

This PR adds three MOD modules to the Experimental MODsuit node, plus
two more:
- Magneto Charger: While the Modsuit is activated, each step the user
takes will charge the installed power cell by a tiny bit, enough to
sustain a standard modsuit of generic slow speed with only a few, easy
modules installed. It won't work in zero G, while flying, pulled by
someone else, on a conveyor belt, riding a vehicle or crawling on the
floor, though.
- Recycler: It collects (most) garbage and casings off the ground and
recycles them into material sheets that can be dispensed on an adjacent
location or storage with with Middle Mouse Button. Doesn't clean debris,
and scuffed because most trash doesn't yield material anyway.
- - It also has two subtypes, unbound from the node: one that dispenses
riot foam darts and can be found on the black market, and another that
dispenses the more innocuous foam darts, rarely found in maints.
- Shooting Assistant: A configurable module. On Stormtrooper mode, it
will give the user a faster fire rate (the double tap trait) at the cost
of accuracy. On Sharpshooter mode, it will improve the user accuracy and
make their shots ricochet against walls at least once (if the hit atom
allows that, that is, e.g. lasers don't ricochet against iron walls), at
the cost of movement speed. Both modes also prevent the user from dual
wielding guns.

To make the Stormtrooper mode stackable with the poor aim quirk and
refrain from making a new trait for the sharpshooter mode, the gun
spread code in gun.dm has also received a little refactor and cleanup.
Also, it's been tested.

## Why It's Good For The Game
The Experimental MODsuit node is quite shabby and could use something
extra to make it more appealing to MODsuit enjoyers.

Also doubles down as a small addition to the black market and maint
loot, and code cleanup, since gun code gives off some garbled vibes.

## Changelog

🆑
add: Expanded the Experimental MODsuit Bepis node with three new
modules: Magneto Charger, Recycler and Shooting Assistant.
add: Added a Riot Foam Recycler module to the black market, as well a
more innocuous version as maint loot.
/🆑

* Expanding the Experimental MODsuit Bepis Node with three new modules.

* update modular, I hate this file btw

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-07-01 14:21:47 -04:00
committed by GitHub
co-authored by Ghom Tom
parent 182644a00a
commit 02e36ec18e
25 changed files with 474 additions and 69 deletions
+3 -1
View File
@@ -291,8 +291,10 @@
///called in /obj/item/gun/fire_gun (user, target, flag, params)
#define COMSIG_GUN_TRY_FIRE "gun_try_fire"
#define COMPONENT_CANCEL_GUN_FIRE (1<<0)
///called in /obj/item/gun/process_fire (src, target, params, zone_override)
///called in /obj/item/gun/process_fire (src, target, params, zone_override, bonus_spread_values)
#define COMSIG_MOB_FIRED_GUN "mob_fired_gun"
#define MIN_BONUS_SPREAD_INDEX 1
#define MAX_BONUS_SPREAD_INDEX 2
///called in /obj/item/gun/process_fire (user, target, params, zone_override)
#define COMSIG_GUN_FIRED "gun_fired"
///called in /obj/item/gun/process_chamber (src)
+4
View File
@@ -61,6 +61,10 @@
#define MATERIAL_SOURCE(mat) "[mat.name]_material"
///Special return values of [/datum/component/material_container/insert_item]
#define MATERIAL_INSERT_ITEM_NO_MATS -1
#define MATERIAL_INSERT_ITEM_NO_SPACE -2
#define MATERIAL_INSERT_ITEM_FAILURE 0
// Slowdown values.
+5 -1
View File
@@ -492,6 +492,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_HATED_BY_DOGS "hated_by_dogs"
/// Mobs with this trait will not be immobilized when held up
#define TRAIT_NOFEAR_HOLDUPS "no_fear_holdup"
/// Mobs with this trait won't be able to dual wield guns.
#define TRAIT_NO_GUN_AKIMBO "no_gun_akimbo"
/// Projectile with this trait will always hit the defined zone of a struck living mob.
#define TRAIT_ALWAYS_HIT_ZONE "always_hit_zone"
/// Mobs with this trait do care about a few grizzly things, such as digging up graves. They also really do not like bringing people back to life or tending wounds, but love autopies, dissections and amputations.
#define TRAIT_MORBID "morbid"
@@ -667,7 +672,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_SELF_AWARE "self_aware"
#define TRAIT_FREERUNNING "freerunning"
#define TRAIT_SKITTISH "skittish"
#define TRAIT_POOR_AIM "poor_aim"
#define TRAIT_PROSOPAGNOSIA "prosopagnosia"
#define TRAIT_TAGGER "tagger"
#define TRAIT_PHOTOGRAPHER "photographer"
-1
View File
@@ -156,7 +156,6 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SELF_AWARE" = TRAIT_SELF_AWARE,
"TRAIT_FREERUNNING" = TRAIT_FREERUNNING,
"TRAIT_SKITTISH" = TRAIT_SKITTISH,
"TRAIT_POOR_AIM" = TRAIT_POOR_AIM,
"TRAIT_PROSOPAGNOSIA" = TRAIT_PROSOPAGNOSIA,
"TRAIT_TAGGER" = TRAIT_TAGGER,
"TRAIT_PHOTOGRAPHER" = TRAIT_PHOTOGRAPHER,
+32 -16
View File
@@ -30,11 +30,22 @@
var/datum/callback/precondition
/// A callback invoked after materials are inserted into this container
var/datum/callback/after_insert
/// A callback invoked after sheets are retrieve from this container
var/datum/callback/after_retrieve
/// The material container flags. See __DEFINES/materials.dm.
var/mat_container_flags
/// Sets up the proper signals and fills the list of materials with the appropriate references.
/datum/component/material_container/Initialize(list/init_mats, max_amt = 0, _mat_container_flags=NONE, list/allowed_mats=init_mats, list/allowed_items, datum/callback/_insertion_check, datum/callback/_precondition, datum/callback/_after_insert)
/datum/component/material_container/Initialize(list/init_mats,
max_amt = 0,
_mat_container_flags=NONE,
list/allowed_mats=init_mats,
list/allowed_items,
datum/callback/_insertion_check,
datum/callback/_precondition,
datum/callback/_after_insert,
datum/callback/_after_retrieve)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
@@ -52,6 +63,7 @@
insertion_check = _insertion_check
precondition = _precondition
after_insert = _after_insert
after_retrieve = _after_retrieve
for(var/mat in init_mats) //Make the assoc list material reference -> amount
var/mat_ref = GET_MATERIAL_REF(mat)
@@ -79,6 +91,8 @@
QDEL_NULL(precondition)
if(after_insert)
QDEL_NULL(after_insert)
if(after_retrieve)
QDEL_NULL(after_retrieve)
return ..()
@@ -295,40 +309,40 @@
/// Proc specifically for inserting items, returns the amount of materials entered.
/datum/component/material_container/proc/insert_item(obj/item/weapon, multiplier = 1, breakdown_flags = mat_container_flags)
if(QDELETED(weapon))
return -1
return MATERIAL_INSERT_ITEM_NO_MATS
multiplier = CEILING(multiplier, 0.01)
var/obj/item/target = weapon
var/material_amount = get_item_material_amount(target, breakdown_flags) * multiplier
if(!material_amount)
return -1
return MATERIAL_INSERT_ITEM_NO_MATS
var/obj/item/stack/item_stack
if(isstack(weapon) && !has_space(material_amount)) //not enugh space split and feed as many sheets possible
item_stack = weapon
var/space_left = max_amount - total_amount
if(!space_left)
return -2
return MATERIAL_INSERT_ITEM_NO_SPACE
var/material_per_sheet = material_amount / item_stack.amount
var/sheets_to_insert = round(space_left / material_per_sheet)
if(!sheets_to_insert)
return -2
return MATERIAL_INSERT_ITEM_NO_SPACE
target = split_stack(item_stack, sheets_to_insert)
material_amount = get_item_material_amount(target, breakdown_flags) * multiplier
if(!has_space(material_amount))
return -2
return MATERIAL_INSERT_ITEM_NO_SPACE
last_inserted_id = insert_item_materials(target, multiplier, breakdown_flags)
if(!isnull(last_inserted_id))
if(after_insert)
after_insert.Invoke(target, last_inserted_id, material_amount)
after_insert.Invoke(target, last_inserted_id, material_amount, src)
qdel(target) //item gone
return material_amount
else if(!isnull(item_stack) && item_stack != target) //insertion failed, merge the split stack back into the original
var/obj/item/stack/inserting_stack = target
item_stack.add(inserting_stack.amount)
qdel(inserting_stack)
return 0
return MATERIAL_INSERT_ITEM_FAILURE
/**
* Inserts the relevant materials from an item into this material container.
@@ -461,8 +475,8 @@
return total_amount_save - total_amount
/// For spawning mineral sheets at a specific location. Used by machines to output sheets.
/datum/component/material_container/proc/retrieve_sheets(sheet_amt, datum/material/M, atom/target = null)
if(!M.sheet_type)
/datum/component/material_container/proc/retrieve_sheets(sheet_amt, datum/material/material, atom/target = null)
if(!material.sheet_type)
return 0 //Add greyscale sheet handling here later
if(sheet_amt <= 0)
return 0
@@ -470,18 +484,20 @@
if(!target)
var/atom/parent_atom = parent
target = parent_atom.drop_location()
if(materials[M] < (sheet_amt * SHEET_MATERIAL_AMOUNT))
sheet_amt = round(materials[M] / SHEET_MATERIAL_AMOUNT)
if(materials[material] < (sheet_amt * SHEET_MATERIAL_AMOUNT))
sheet_amt = round(materials[material] / SHEET_MATERIAL_AMOUNT)
var/count = 0
while(sheet_amt > MAX_STACK_SIZE)
new M.sheet_type(target, MAX_STACK_SIZE, null, list((M) = SHEET_MATERIAL_AMOUNT))
var/obj/item/stack/sheet/new_sheets = new material.sheet_type(target, MAX_STACK_SIZE, null, list((material) = SHEET_MATERIAL_AMOUNT))
after_retrieve?.Invoke(new_sheets)
count += MAX_STACK_SIZE
use_amount_mat(sheet_amt * SHEET_MATERIAL_AMOUNT, M)
use_amount_mat(sheet_amt * SHEET_MATERIAL_AMOUNT, material)
sheet_amt -= MAX_STACK_SIZE
if(sheet_amt >= 1)
new M.sheet_type(target, sheet_amt, null, list((M) = SHEET_MATERIAL_AMOUNT))
var/obj/item/stack/sheet/new_sheets = new material.sheet_type(target, sheet_amt, null, list((material) = SHEET_MATERIAL_AMOUNT))
after_retrieve?.Invoke(new_sheets)
count += sheet_amt
use_amount_mat(sheet_amt * SHEET_MATERIAL_AMOUNT, M)
use_amount_mat(sheet_amt * SHEET_MATERIAL_AMOUNT, material)
return count
+11 -1
View File
@@ -536,11 +536,21 @@
desc = "You've never hit anything you were aiming for in your life."
icon = FA_ICON_BULLSEYE
value = -4
mob_trait = TRAIT_POOR_AIM
medical_record_text = "Patient possesses a strong tremor in both hands."
hardcore_value = 3
mail_goodies = list(/obj/item/cardboard_cutout) // for target practice
/datum/quirk/poor_aim/add(client/client_source)
RegisterSignal(quirk_holder, COMSIG_MOB_FIRED_GUN, PROC_REF(on_mob_fired_gun))
/datum/quirk/poor_aim/remove(client/client_source)
UnregisterSignal(quirk_holder, COMSIG_MOB_FIRED_GUN)
/datum/quirk/poor_aim/proc/on_mob_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] += 10
bonus_spread_values[MAX_BONUS_SPREAD_INDEX] += 35
/datum/quirk/prosopagnosia
name = "Prosopagnosia"
desc = "You have a mental disorder that prevents you from being able to recognize faces at all."
@@ -12,7 +12,8 @@
/obj/item/mod/module/balloon = 1,
/obj/item/mod/module/paper_dispenser = 1,
/obj/item/mod/module/hat_stabilizer = 2,
/obj/item/mod/module/stamp = 1
/obj/item/mod/module/stamp = 1,
/obj/item/mod/module/recycler/donk/safe = 1,
)
/obj/effect/spawner/random/mod/maint/Initialize(mapload)
@@ -41,6 +41,15 @@
stock_max = 8
availability_prob = 60
/datum/market_item/misc/donk_recycler
name = "MOD Riot Foam Dart Recycler Module"
desc = "If you love toy guns, hate cleaning and got a MODsuit, this module is a must have."
item = /obj/item/mod/module/recycler/donk
price_min = CARGO_CRATE_VALUE * 2
price_max = CARGO_CRATE_VALUE * 4.5
stock_max = 2
availability_prob = 30
/datum/market_item/misc/holywater
name = "Flask of holy water"
desc = "Father Lootius' own brand of ready-made holy water."
+178
View File
@@ -596,3 +596,181 @@
/obj/item/mod/module/signlang_radio/on_suit_deactivation(deleting = FALSE)
REMOVE_TRAIT(mod.wearer, TRAIT_CAN_SIGN_ON_COMMS, MOD_TRAIT)
///A module that recharges the suit by an itsy tiny bit whenever the user takes a step. Originally called "magneto module" but the videogame reference sounds cooler.
/obj/item/mod/module/joint_torsion
name = "MOD joint torsion ratchet module"
desc = "A compact, weak AC generator that charges the suit's internal cell through the power of deambulation. It doesn't work in zero G."
icon_state = "joint_torsion"
complexity = 1
incompatible_modules = list(/obj/item/mod/module/joint_torsion)
var/power_per_step = DEFAULT_CHARGE_DRAIN * 0.3
/obj/item/mod/module/joint_torsion/on_suit_activation()
if(!(mod.wearer.movement_type & (FLOATING|FLYING)))
RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
/// This way we don't even bother to call on_moved() while flying/floating
RegisterSignal(mod.wearer, COMSIG_MOVETYPE_FLAG_ENABLED, PROC_REF(on_movetype_flag_enabled))
RegisterSignal(mod.wearer, COMSIG_MOVETYPE_FLAG_DISABLED, PROC_REF(on_movetype_flag_disabled))
/obj/item/mod/module/joint_torsion/on_suit_deactivation(deleting = FALSE)
UnregisterSignal(mod.wearer, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVETYPE_FLAG_ENABLED, COMSIG_MOVETYPE_FLAG_DISABLED))
/obj/item/mod/module/joint_torsion/proc/on_movetype_flag_enabled(datum/source, flag, old_state)
SIGNAL_HANDLER
if(!(old_state & (FLOATING|FLYING)) && flag & (FLOATING|FLYING))
UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED)
/obj/item/mod/module/joint_torsion/proc/on_movetype_flag_disabled(datum/source, flag, old_state)
SIGNAL_HANDLER
if(old_state & (FLOATING|FLYING) && !(mod.wearer.movement_type & (FLOATING|FLYING)))
RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
/obj/item/mod/module/joint_torsion/proc/on_moved(mob/living/carbon/human/wearer, atom/old_loc, movement_dir, forced)
SIGNAL_HANDLER
//Shouldn't work if the wearer isn't really walking/running around.
if(forced || wearer.throwing || wearer.body_position == LYING_DOWN || wearer.buckled || CHECK_MOVE_LOOP_FLAGS(wearer, MOVEMENT_LOOP_OUTSIDE_CONTROL))
return
mod.core.add_charge(power_per_step)
/// Module that shoves garbage inside its material container when the user crosses it, and eject the recycled material with MMB.
/obj/item/mod/module/recycler
name = "MOD recycler module"
desc = "An innovative garbage collection module that recycles gathered trash into usable material. \
Doesn't work on debris and some items. May recycle live ammunition. \
Activate on a nearby turf or storage to unload stored material."
icon_state = "recycler"
module_type = MODULE_ACTIVE
active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
complexity = 2
incompatible_modules = list(/obj/item/mod/module/recycler)
overlay_state_inactive = "module_recycler"
overlay_state_active = "module_recycler"
/// A multiplier of the amount of material extracted from the item
var/efficiency = 1
/// Items that will be collected
var/list/allowed_item_types = list(
/obj/item/trash,
/obj/item/shard,
/obj/item/light,
/obj/item/broken_bottle,
/obj/item/ammo_casing,
/obj/item/cigbutt,
)
/// Materials that will be extracted.
var/list/accepted_mats = list(
/datum/material/iron,
/datum/material/glass,
/datum/material/silver,
/datum/material/plasma,
/datum/material/gold,
/datum/material/diamond,
/datum/material/plastic,
/datum/material/uranium,
/datum/material/bananium,
/datum/material/titanium,
/datum/material/bluespace,
)
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_obj_entered),
COMSIG_ATOM_INITIALIZED_ON = PROC_REF(on_atom_initialized_on),
)
var/datum/component/connect_loc_behalf/connector
/obj/item/mod/module/recycler/Initialize(mapload)
. = ..()
AddComponent(/datum/component/material_container, accepted_mats, 50 * SHEET_MATERIAL_AMOUNT, MATCONTAINER_EXAMINE|MATCONTAINER_NO_INSERT, _after_retrieve=CALLBACK(src, PROC_REF(attempt_insert_storage)))
/obj/item/mod/module/recycler/on_activation()
. = ..()
if(!.)
return
connector = AddComponent(/datum/component/connect_loc_behalf, mod.wearer, loc_connections)
RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(on_wearer_moved))
/obj/item/mod/module/recycler/on_deactivation(display_message, deleting = FALSE)
. = ..()
if(!.)
return
QDEL_NULL(connector)
UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(on_wearer_moved))
/obj/item/mod/module/recycler/proc/on_wearer_moved(datum/source, atom/old_loc, dir, forced)
SIGNAL_HANDLER
for(var/obj/item/item in mod.wearer.loc)
if(!is_type_in_list(item, allowed_item_types))
return
insert_trash(item)
/obj/item/mod/module/recycler/proc/on_obj_entered(atom/new_loc, atom/movable/arrived, atom/old_loc)
SIGNAL_HANDLER
if(!is_type_in_list(arrived, allowed_item_types))
return
insert_trash(arrived)
/obj/item/mod/module/recycler/proc/on_atom_initialized_on(atom/loc, atom/new_atom)
SIGNAL_HANDLER
if(!is_type_in_list(new_atom, allowed_item_types))
return
//Give the new atom the time to fully initialize and maybe live if the wearer moves away.
addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/item/mod/module/recycler, insert_trash_if_nearby), new_atom), 0.5 SECONDS)
/obj/item/mod/module/recycler/proc/insert_trash_if_nearby(atom/new_atom)
if(new_atom && mod?.wearer && new_atom.loc == mod.wearer.loc)
insert_trash(new_atom)
/obj/item/mod/module/recycler/proc/insert_trash(obj/item/item)
var/datum/component/material_container/container = GetComponent(/datum/component/material_container)
var/retrieved = container.insert_item(item, multiplier = efficiency, breakdown_flags = BREAKDOWN_FLAGS_RECYCLER)
if(retrieved == MATERIAL_INSERT_ITEM_NO_MATS) //even if it doesn't have any material to give, trash is trash.
qdel(item)
playsound(src, SFX_RUSTLE, 50, TRUE, -5)
/obj/item/mod/module/recycler/on_select_use(atom/target)
. = ..()
if(!.)
return
if(!target?.atom_storage)
target = get_turf(target)
if(!isopenturf(target) || !mod.wearer.Adjacent(target))
return FALSE
dispense(target)
/obj/item/mod/module/recycler/proc/dispense(atom/target)
var/datum/component/material_container/container = GetComponent(/datum/component/material_container)
if(container.retrieve_all(target))
balloon_alert(mod.wearer, "material dispensed")
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 50, TRUE)
return
balloon_alert(mod.wearer, "not enough material")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
/obj/item/mod/module/recycler/proc/attempt_insert_storage(obj/item/to_drop)
if(!isturf(to_drop.loc) && !to_drop.loc.atom_storage?.attempt_insert(to_drop, mod.wearer, override = TRUE))
to_drop.forceMove(to_drop.loc.drop_location())
///A black market variant of the above that dispenses riot foam dart boxes
/obj/item/mod/module/recycler/donk
name = "MOD riot foam dart recycler module"
desc = "A mod module collects and repackages fired foam darts (and garbage) into half-sized boxes of riot foam darts. \
Activate on a nearby turf or storage to unload stored ammo boxes."
icon_state = "donk_recycler"
overlay_state_inactive = "module_donk_recycler"
overlay_state_active = "module_donk_recycler"
efficiency = 0.7 // Stops getting as many riot foam darts as one consumes.
accepted_mats = list(/datum/material/iron)
///The type of ammo box that it dispenses
var/ammobox_type = /obj/item/ammo_box/foambox/riot/mini
///The cost of each dispensed ammo box
var/required_amount = SHEET_MATERIAL_AMOUNT*12.5
/obj/item/mod/module/recycler/donk/dispense(atom/target)
var/datum/component/material_container/container = GetComponent(/datum/component/material_container)
if(!container.use_amount_mat(required_amount, /datum/material/iron))
balloon_alert(mod.wearer, "not enough material")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
return
var/obj/item/ammo_box/product = new ammobox_type(target)
attempt_insert_storage(product)
balloon_alert(mod.wearer, "ammo box dispensed.")
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 50, TRUE)
+13
View File
@@ -334,3 +334,16 @@
QDEL_IN(mod.wearer, FLY_TIME)
#undef FLY_TIME
/obj/item/mod/module/recycler/donk/safe
name = "MOD foam dart recycler module"
desc = "A mod module that collects and repackages fired foam darts into half-sized ammo boxes. \
Activate on a nearby turf or storage to unload stored ammo boxes."
icon_state = "donk_safe_recycler"
overlay_state_inactive = "module_donk_safe_recycler"
overlay_state_active = "module_donk_safe_recycler"
complexity = 1
efficiency = 1
allowed_item_types = list(/obj/item/ammo_casing/caseless/foam_dart)
ammobox_type = /obj/item/ammo_box/foambox/mini
required_amount = SMALL_MATERIAL_AMOUNT*2.5
@@ -385,3 +385,116 @@
creatures_detected++
playsound(mod.wearer, 'sound/effects/ping_hit.ogg', vol = 75, vary = TRUE, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE) // 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 [creatures_detected] living beings nearby!"))
#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)
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/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), MOD_TRAIT)
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), MOD_TRAIT)
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), MOD_TRAIT)
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), MOD_TRAIT)
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_suit_activation()
apply_mode_effects()
/obj/item/mod/module/shooting_assistant/on_suit_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.
ADD_TRAIT(projectile, TRAIT_ALWAYS_HIT_ZONE, MOD_TRAIT)
#undef SHOOTING_ASSISTANT_OFF
#undef STORMTROOPER_MODE
#undef SHARPSHOOTER_MODE
@@ -16,3 +16,6 @@
/datum/movespeed_modifier/sphere
multiplicative_slowdown = -0.5
/datum/movespeed_modifier/shooting_assistant
multiplicative_slowdown = 0.5
@@ -110,7 +110,17 @@
max_ammo = 40
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*5)
/obj/item/ammo_box/foambox/mini
icon_state = "foambox_mini"
max_ammo = 20
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*2.5)
/obj/item/ammo_box/foambox/riot
icon_state = "foambox_riot"
ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*25)
/obj/item/ammo_box/foambox/riot/mini
icon_state = "foambox_riot_mini"
max_ammo = 20
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*12.5)
+16 -19
View File
@@ -307,9 +307,8 @@
//DUAL (or more!) WIELDING
var/bonus_spread = 0
var/loop_counter = 0
if(ishuman(user) && user.combat_mode)
var/mob/living/carbon/human/H = user
for(var/obj/item/gun/gun in H.held_items)
if(user.combat_mode && !HAS_TRAIT(user, TRAIT_NO_GUN_AKIMBO))
for(var/obj/item/gun/gun in user.held_items)
if(gun == src || gun.weapon_weight >= WEAPON_MEDIUM)
continue
else if(gun.can_trigger_gun(user, akimbo_usage = TRUE))
@@ -355,7 +354,7 @@
/obj/item/gun/proc/recharge_newshot()
return
/obj/item/gun/proc/process_burst(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0)
/obj/item/gun/proc/process_burst(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", random_spread = 0, burst_spread_mult = 0, iteration = 0)
if(!user || !firing_burst)
firing_burst = FALSE
return FALSE
@@ -368,10 +367,11 @@
if(chambered.harmful) // Is the bullet chambered harmful?
to_chat(user, span_warning("[src] is lethally chambered! You don't want to risk harming anyone..."))
return
var/sprd
if(randomspread)
sprd = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
sprd = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (random_spread))
else //Smart spread
sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread))
sprd = round((((burst_spread_mult/burst_size) * iteration) - (0.5 + (burst_spread_mult * 0.25))) * (random_spread))
before_firing(target,user)
if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd, src))
shoot_with_empty_chamber(user)
@@ -393,8 +393,12 @@
return TRUE
/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
var/base_bonus_spread = 0
if(user)
SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, src, target, params, zone_override)
var/list/bonus_spread_values = list(base_bonus_spread, bonus_spread)
SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, src, target, params, zone_override, bonus_spread_values)
base_bonus_spread = bonus_spread_values[MIN_BONUS_SPREAD_INDEX]
bonus_spread = bonus_spread_values[MAX_BONUS_SPREAD_INDEX]
SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override)
@@ -404,17 +408,10 @@
return
//Vary by at least this much
var/base_bonus_spread = 0
var/sprd = 0
var/randomized_gun_spread = 0
var/rand_spr = rand()
if(user && HAS_TRAIT(user, TRAIT_POOR_AIM)) //Nice job hotshot
bonus_spread += 35
base_bonus_spread += 10
if(spread)
randomized_gun_spread = rand(0,spread)
var/randomized_bonus_spread = rand(base_bonus_spread, bonus_spread)
var/randomized_gun_spread = spread ? rand(0, spread) : 0
var/total_random_spread = max(0, randomized_bonus_spread + randomized_gun_spread)
var/burst_spread_mult = rand()
var/modified_delay = fire_delay
if(user && HAS_TRAIT(user, TRAIT_DOUBLE_TAP))
@@ -423,14 +420,14 @@
if(burst_size > 1)
firing_burst = TRUE
for(var/i = 1 to burst_size)
addtimer(CALLBACK(src, PROC_REF(process_burst), user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i), modified_delay * (i - 1))
addtimer(CALLBACK(src, PROC_REF(process_burst), user, target, message, params, zone_override, total_random_spread, burst_spread_mult, i), modified_delay * (i - 1))
else
if(chambered)
if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal.
if(chambered.harmful) // Is the bullet chambered harmful?
to_chat(user, span_warning("[src] is lethally chambered! You don't want to risk harming anyone..."))
return
sprd = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
var/sprd = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * total_random_spread)
before_firing(target,user)
if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src))
shoot_with_empty_chamber(user)
+4 -3
View File
@@ -251,10 +251,11 @@
stack_trace("Trying to move a qdeleted casing of type [casing.type]!")
chambered = null
else if(casing_ejector || !from_firing)
casing.forceMove(drop_location()) //Eject casing onto ground.
casing.bounce_away(TRUE)
SEND_SIGNAL(casing, COMSIG_CASING_EJECTED)
chambered = null
casing.forceMove(drop_location()) //Eject casing onto ground.
if(!QDELETED(casing))
casing.bounce_away(TRUE)
SEND_SIGNAL(casing, COMSIG_CASING_EJECTED)
else if(empty_chamber)
chambered = null
if (chamber_next_round && (magazine?.max_ammo > 1))
+1 -1
View File
@@ -191,7 +191,7 @@
process_chamber() // If the gun was drained and then recharged, load a new shot.
return ..()
/obj/item/gun/energy/process_burst(mob/living/user, atom/target, message = TRUE, params = null, zone_override="", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0)
/obj/item/gun/energy/process_burst(mob/living/user, atom/target, message = TRUE, params = null, zone_override="", randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0)
if(!chambered && can_shoot())
process_chamber() // Ditto.
return ..()
+6 -3
View File
@@ -93,6 +93,8 @@
var/ricochets = 0
/// how many times we can ricochet max
var/ricochets_max = 0
/// how many times we have to ricochet min (unless we hit an atom we can ricochet off)
var/min_ricochets = 0
/// 0-100 (or more, I guess), the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
var/ricochet_chance = 0
/// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet
@@ -454,8 +456,9 @@
store_hitscan_collision(point_cache)
return TRUE
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
if(!HAS_TRAIT(src, TRAIT_ALWAYS_HIT_ZONE))
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
return process_hit(T, select_target(T, A, A), A) // SELECT TARGET FIRST!
@@ -689,7 +692,7 @@
var/chance = ricochet_chance * A.receive_ricochet_chance_mod
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
chance += NICE_SHOT_RICOCHET_BONUS
if(prob(chance))
if(ricochets < min_ricochets || prob(chance))
return TRUE
return FALSE
@@ -123,7 +123,7 @@
SIGNAL_HANDLER
punishment(user, spell_cast.school)
/datum/brain_trauma/special/honorbound/proc/staff_check(mob/user, obj/item/gun/gun_fired, target, params, zone_override)
/datum/brain_trauma/special/honorbound/proc/staff_check(mob/user, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values)
SIGNAL_HANDLER
if(!istype(gun_fired, /obj/item/gun/magic))
return
@@ -149,7 +149,7 @@
flubbed_match()
///someone used a gun
/datum/sparring_match/proc/gun_violation(datum/offender)
/datum/sparring_match/proc/gun_violation(mob/offender, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values)
SIGNAL_HANDLER
violation(offender, "using guns")
@@ -1927,6 +1927,33 @@
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SUPPLY
)
/datum/design/module/joint_torsion
name = "Joint Torsion Ratchet Module"
id = "mod_joint_torsion"
materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/gold = SMALL_MATERIAL_AMOUNT*2.5, /datum/material/titanium = SMALL_MATERIAL_AMOUNT)
build_path = /obj/item/mod/module/joint_torsion
category = list(
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUITS_MISC
)
/datum/design/module/recycler
name = "Recycler Module"
id = "mod_recycler"
materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plastic = SMALL_MATERIAL_AMOUNT*2)
build_path = /obj/item/mod/module/recycler
category = list(
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE
)
/datum/design/module/shooting_assistant
name = "Shooting Assistant Module"
id = "mod_shooting"
materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT, /datum/material/silver = SMALL_MATERIAL_AMOUNT*2, /datum/material/gold = SMALL_MATERIAL_AMOUNT, /datum/material/diamond = SMALL_MATERIAL_AMOUNT)
build_path = /obj/item/mod/module/shooting_assistant
category = list(
RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SECURITY
)
//MODsuit anomalock modules
/datum/design/module/mod_antigrav
name = "Anti-Gravity Module"
+4 -1
View File
@@ -2397,10 +2397,13 @@
/datum/techweb_node/mod_experimental
id = "mod_experimental"
display_name = "Experimental Modular Suits"
description = "Applications of experimentality when creating MODsuits has created these..."
description = "Applications of experimentality when creating MODsuits have created these..."
prereq_ids = list("base")
design_ids = list(
"mod_disposal",
"mod_joint_torsion",
"mod_recycler",
"mod_shooting",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
hidden = TRUE
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 B

After

Width:  |  Height:  |  Size: 869 B

@@ -328,8 +328,12 @@
// To maintain modularity, I am moving this proc override here.
/obj/item/gun/microfusion/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
var/base_bonus_spread = 0
if(user)
SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override)
var/list/bonus_spread_values = list(base_bonus_spread, bonus_spread)
SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, src, target, params, zone_override, bonus_spread_values)
base_bonus_spread = bonus_spread_values[MIN_BONUS_SPREAD_INDEX]
bonus_spread = bonus_spread_values[MAX_BONUS_SPREAD_INDEX]
SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override)
@@ -339,32 +343,29 @@
return
//Vary by at least this much
var/base_bonus_spread = 0
var/calculated_spread = 0
var/randomized_gun_spread = 0
var/random_spread = rand()
if(user && HAS_TRAIT(user, TRAIT_POOR_AIM)) //Nice job hotshot
bonus_spread += 35
base_bonus_spread += 10
if(spread)
randomized_gun_spread = rand(0,spread)
var/randomized_bonus_spread = rand(base_bonus_spread, bonus_spread)
var/randomized_gun_spread = spread ? rand(0, spread) : 0
var/total_random_spread = max(0, randomized_bonus_spread + randomized_gun_spread)
var/burst_spread_mult = rand()
var/modified_delay = fire_delay
if(phase_emitter)
modified_delay = phase_emitter.fire_delay
if(user && HAS_TRAIT(user, TRAIT_DOUBLE_TAP))
modified_delay = ROUND_UP(fire_delay * 0.5)
if(burst_size > 1)
firing_burst = TRUE
var/fire_delay_to_add = 0
if(phase_emitter)
fire_delay_to_add = phase_emitter.fire_delay
for(var/i = 1 to burst_size)
addtimer(CALLBACK(src, PROC_REF(process_burst), user, target, message, params, zone_override, calculated_spread, randomized_gun_spread, randomized_bonus_spread, random_spread, i), (fire_delay + fire_delay_to_add) * (i - 1))
addtimer(CALLBACK(src, PROC_REF(process_burst), user, target, message, params, zone_override, total_random_spread, burst_spread_mult, i), modified_delay * (i - 1))
else
if(chambered)
if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal.
if(chambered.harmful) // Is the bullet chambered harmful?
balloon_alert(user, "lethally chambered!")
return
calculated_spread = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
var/calculated_spread = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * total_random_spread)
before_firing(target,user)
process_microfusion()
if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, calculated_spread, src))
@@ -395,7 +396,17 @@
return TRUE
// Same goes for this!
/obj/item/gun/microfusion/process_burst(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", calculated_spread = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, random_spread= 0, iteration = 0)
/obj/item/gun/microfusion/process_burst(
mob/living/user,
atom/target,
message = TRUE,
params = null,
zone_override = "",
random_spread = 0,
burst_spread_mult = 0,
iteration = 0,
)
if(!user || !firing_burst)
firing_burst = FALSE
return FALSE
@@ -413,10 +424,11 @@
if(chambered.harmful) // Is the bullet chambered harmful?
balloon_alert(user, "lethally chambered!")
return
var/calculated_spread
if(randomspread)
calculated_spread = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
calculated_spread = round((rand(0, 1) - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (random_spread))
else //Smart spread
calculated_spread = round((((random_spread/burst_size) * iteration) - (0.5 + (random_spread * 0.25))) * (randomized_gun_spread + randomized_bonus_spread))
calculated_spread = round((((burst_spread_mult/burst_size) * iteration) - (0.5 + (burst_spread_mult * 0.25))) * (random_spread))
before_firing(target,user)
process_microfusion()
if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, calculated_spread, src))