diff --git a/citadel.dme b/citadel.dme
index b213a43eea20..c6dc92c38d8a 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -183,6 +183,7 @@
#include "code\__DEFINES\dcs\signals\datums\signals_beam_legacy.dm"
#include "code\__DEFINES\dcs\signals\datums\signals_perspective.dm"
#include "code\__DEFINES\dcs\signals\elements\signals_element_conflict_checking.dm"
+#include "code\__DEFINES\dcs\signals\items\signals_gun.dm"
#include "code\__DEFINES\dcs\signals\items\signals_inducer.dm"
#include "code\__DEFINES\dcs\signals\modules\signals_module_fishing.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-buckling.dm"
@@ -307,7 +308,9 @@
#include "code\__DEFINES\projectiles\ammo_casing.dm"
#include "code\__DEFINES\projectiles\ammo_magazine.dm"
#include "code\__DEFINES\projectiles\gun.dm"
+#include "code\__DEFINES\projectiles\gun_components.dm"
#include "code\__DEFINES\projectiles\guns.dm"
+#include "code\__DEFINES\projectiles\guns_legacy.dm"
#include "code\__DEFINES\projectiles\projectile.dm"
#include "code\__DEFINES\projectiles\system.dm"
#include "code\__DEFINES\radiation\flags.dm"
@@ -427,6 +430,7 @@
#include "code\__HELPERS\lists\asset_sorted.dm"
#include "code\__HELPERS\lists\associations.dm"
#include "code\__HELPERS\lists\bitflag_lists.dm"
+#include "code\__HELPERS\lists\clone.dm"
#include "code\__HELPERS\lists\copy.dm"
#include "code\__HELPERS\lists\counter.dm"
#include "code\__HELPERS\lists\json.dm"
@@ -464,6 +468,8 @@
#include "code\__HELPERS\text\scramble.dm"
#include "code\__HELPERS\type2type\color.dm"
#include "code\__HELPERS\type2type\type2type.dm"
+#include "code\__HELPERS\typepaths\subtypesof_non_abstract.dm"
+#include "code\__HELPERS\typepaths\typesof_non_abstract.dm"
#include "code\__HELPERS\unsorted\contents.dm"
#include "code\__HELPERS\unsorted\locate.dm"
#include "code\__HELPERS\unsorted\radiation.dm"
@@ -934,6 +940,7 @@
#include "code\datums\status_effects\basic\crusher_track.dm"
#include "code\datums\status_effects\basic\incapacitation.dm"
#include "code\datums\status_effects\basic\sight.dm"
+#include "code\datums\status_effects\basic\taser_stun.dm"
#include "code\datums\status_effects\grouped\crusher_mark.dm"
#include "code\datums\status_effects\grouped\staggered.dm"
#include "code\datums\underwear\bottom.dm"
@@ -1093,6 +1100,13 @@
#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_expeditionary-light_rifle.dm"
#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_expeditionary-light_sidearm.dm"
#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_expeditionary.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_isd.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_pmd.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_protomag-ammo.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_protomag-caliber.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_protomag-magazine.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_protomag-projectile.dm"
+#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_protomag.dm"
#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_pulse.dm"
#include "code\game\content\factions\corporations\nanotrasen\nanotrasen-supply\animals.dm"
#include "code\game\content\factions\corporations\nanotrasen\nanotrasen-supply\atmospherics.dm"
@@ -4431,12 +4445,9 @@
#include "code\modules\preferences\preference_setup\vore\08_traits.dm"
#include "code\modules\preferences\preference_setup\vore\09_nif.dm"
#include "code\modules\preferences\preference_setup\vore\10_misc.dm"
-#include "code\modules\projectiles\firing_pin.dm"
-#include "code\modules\projectiles\gun.dm"
-#include "code\modules\projectiles\gun_item_renderer.dm"
-#include "code\modules\projectiles\gun_mob_renderer.dm"
#include "code\modules\projectiles\ammunition\ammo_caliber.dm"
#include "code\modules\projectiles\ammunition\ammo_casing.dm"
+#include "code\modules\projectiles\ammunition\ammo_handful.dm"
#include "code\modules\projectiles\ammunition\ammo_magazine.dm"
#include "code\modules\projectiles\ammunition\calibers\normal\a10g.dm"
#include "code\modules\projectiles\ammunition\calibers\normal\a10mm.dm"
@@ -4466,10 +4477,37 @@
#include "code\modules\projectiles\ammunition\calibers\special\pellet.dm"
#include "code\modules\projectiles\ammunition\calibers\special\rocket.dm"
#include "code\modules\projectiles\guns\ballistic.dm"
+#include "code\modules\projectiles\guns\energy-firemode.dm"
#include "code\modules\projectiles\guns\energy.dm"
+#include "code\modules\projectiles\guns\firemode.dm"
+#include "code\modules\projectiles\guns\firing_pin.dm"
+#include "code\modules\projectiles\guns\gun-firing.dm"
+#include "code\modules\projectiles\guns\gun-modular.dm"
+#include "code\modules\projectiles\guns\gun-projectile-implementation.dm"
+#include "code\modules\projectiles\guns\gun.dm"
+#include "code\modules\projectiles\guns\gun_component.dm"
+#include "code\modules\projectiles\guns\gun_firing_cycle.dm"
+#include "code\modules\projectiles\guns\gun_item_renderer.dm"
+#include "code\modules\projectiles\guns\gun_mob_renderer.dm"
#include "code\modules\projectiles\guns\launcher.dm"
#include "code\modules\projectiles\guns\magic.dm"
+#include "code\modules\projectiles\guns\magnetic.dm"
#include "code\modules\projectiles\guns\vox.dm"
+#include "code\modules\projectiles\guns\ballistic\automatic.dm"
+#include "code\modules\projectiles\guns\ballistic\boltaction.dm"
+#include "code\modules\projectiles\guns\ballistic\bow.dm"
+#include "code\modules\projectiles\guns\ballistic\caseless.dm"
+#include "code\modules\projectiles\guns\ballistic\contender.dm"
+#include "code\modules\projectiles\guns\ballistic\dartgun.dm"
+#include "code\modules\projectiles\guns\ballistic\magnetic.dm"
+#include "code\modules\projectiles\guns\ballistic\musket.dm"
+#include "code\modules\projectiles\guns\ballistic\pistol.dm"
+#include "code\modules\projectiles\guns\ballistic\revolver.dm"
+#include "code\modules\projectiles\guns\ballistic\rocket.dm"
+#include "code\modules\projectiles\guns\ballistic\semiauto.dm"
+#include "code\modules\projectiles\guns\ballistic\shotgun.dm"
+#include "code\modules\projectiles\guns\ballistic\sniper.dm"
+#include "code\modules\projectiles\guns\ballistic\caseless\pellet.dm"
#include "code\modules\projectiles\guns\ballistic\microbattery\medigun.dm"
#include "code\modules\projectiles\guns\ballistic\microbattery\medigun_cells.dm"
#include "code\modules\projectiles\guns\ballistic\microbattery\microbattery-casing.dm"
@@ -4477,6 +4515,7 @@
#include "code\modules\projectiles\guns\ballistic\microbattery\microbattery.dm"
#include "code\modules\projectiles\guns\ballistic\microbattery\revolver.dm"
#include "code\modules\projectiles\guns\ballistic\microbattery\revolver_cells.dm"
+#include "code\modules\projectiles\guns\ballistic\sniper\collapsible_sniper.dm"
#include "code\modules\projectiles\guns\energy\frontier.dm"
#include "code\modules\projectiles\guns\energy\hooklauncher.dm"
#include "code\modules\projectiles\guns\energy\laser.dm"
@@ -4496,6 +4535,11 @@
#include "code\modules\projectiles\guns\energy\modular\modularlenses.dm"
#include "code\modules\projectiles\guns\energy\modular\modularpower.dm"
#include "code\modules\projectiles\guns\energy\special\hardlight_bow.dm"
+#include "code\modules\projectiles\guns\gun_component\acceleration_coil.dm"
+#include "code\modules\projectiles\guns\gun_component\active_cooler.dm"
+#include "code\modules\projectiles\guns\gun_component\energy_handler.dm"
+#include "code\modules\projectiles\guns\gun_component\internal_module.dm"
+#include "code\modules\projectiles\guns\gun_component\power_unit.dm"
#include "code\modules\projectiles\guns\launcher\crossbow.dm"
#include "code\modules\projectiles\guns\launcher\grenade_launcher.dm"
#include "code\modules\projectiles\guns\launcher\pneumatic.dm"
@@ -4512,24 +4556,8 @@
#include "code\modules\projectiles\guns\magic\staff.dm"
#include "code\modules\projectiles\guns\magic\wand.dm"
#include "code\modules\projectiles\guns\magnetic\bore.dm"
-#include "code\modules\projectiles\guns\magnetic\magnetic.dm"
#include "code\modules\projectiles\guns\magnetic\magnetic_construction.dm"
#include "code\modules\projectiles\guns\magnetic\magnetic_railgun.dm"
-#include "code\modules\projectiles\guns\projectile\automatic.dm"
-#include "code\modules\projectiles\guns\projectile\boltaction.dm"
-#include "code\modules\projectiles\guns\projectile\bow.dm"
-#include "code\modules\projectiles\guns\projectile\caseless.dm"
-#include "code\modules\projectiles\guns\projectile\contender.dm"
-#include "code\modules\projectiles\guns\projectile\dartgun.dm"
-#include "code\modules\projectiles\guns\projectile\musket.dm"
-#include "code\modules\projectiles\guns\projectile\pistol.dm"
-#include "code\modules\projectiles\guns\projectile\revolver.dm"
-#include "code\modules\projectiles\guns\projectile\rocket.dm"
-#include "code\modules\projectiles\guns\projectile\semiauto.dm"
-#include "code\modules\projectiles\guns\projectile\shotgun.dm"
-#include "code\modules\projectiles\guns\projectile\sniper.dm"
-#include "code\modules\projectiles\guns\projectile\caseless\pellet.dm"
-#include "code\modules\projectiles\guns\projectile\sniper\collapsible_sniper.dm"
#include "code\modules\projectiles\projectile\helpers.dm"
#include "code\modules\projectiles\projectile\projectile-hitscan_visuals.dm"
#include "code\modules\projectiles\projectile\projectile-physics.dm"
diff --git a/code/__DEFINES/combat/armor.dm b/code/__DEFINES/combat/armor.dm
index 67ede87ad789..7b9db25ae1e9 100644
--- a/code/__DEFINES/combat/armor.dm
+++ b/code/__DEFINES/combat/armor.dm
@@ -38,6 +38,9 @@
#define ARMOR_FIRE "fire"
#define ARMOR_ACID "acid"
+/**
+ * All armor enums that can be stored in an armor datum
+ */
GLOBAL_REAL_LIST(armor_enums) = list(
ARMOR_MELEE,
ARMOR_MELEE_TIER,
@@ -59,6 +62,9 @@ GLOBAL_REAL_LIST(armor_enums) = list(
ARMOR_ACID,
)
+/**
+ * Actual armor types that can be checked for with `damage_flag`
+ */
GLOBAL_REAL_LIST(armor_types) = list(
ARMOR_MELEE,
ARMOR_BULLET,
diff --git a/code/__DEFINES/dcs/signals/items/signals_gun.dm b/code/__DEFINES/dcs/signals/items/signals_gun.dm
new file mode 100644
index 000000000000..fc7d58161d45
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/items/signals_gun.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Called before every fire() call, with (datum/gun_firing_cycle/cycle).
+ */
+#define COMSIG_GUN_FIRING_CYCLE_ITERATION_PREFIRE "gun-firing-iteration"
+
+/**
+ * Called before initiation of a firing cycle, with (datum/gun_firing_cycle/cycle).
+ */
+#define COMSIG_GUN_FIRING_CYCLE_START "gun-firing-start"
+
+/**
+ * Called on end of a firing cycle, with (datum/gun_firing_cycle/cycle).
+ */
+#define COMSIG_GUN_FIRING_CYCLE_END "gun-firing-end"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm
index 4d20c82cc267..8073142f5edd 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) Citadel Station Developers *//
+//* Copyright (c) 2024 Citadel Station Developers *//
// todo: integrity signals?
diff --git a/code/__DEFINES/dcs/signals/signals_item/signals_item-interaction.dm b/code/__DEFINES/dcs/signals/signals_item/signals_item-interaction.dm
index d750540cfca7..1759290107ec 100644
--- a/code/__DEFINES/dcs/signals/signals_item/signals_item-interaction.dm
+++ b/code/__DEFINES/dcs/signals/signals_item/signals_item-interaction.dm
@@ -1,11 +1,15 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 silicons *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/// From base of obj/item/attack_self(): (/datum/event_args/actor/actor)
#define COMSIG_ITEM_ACTIVATE_INHAND "item_activate_inhand"
+ #define RAISE_ITEM_ACTIVATE_INHAND_HANDLED (1<<0)
/// From base of obj/item/unique_action(): (/datum/event_args/actor/actor)
#define COMSIG_ITEM_UNIQUE_ACTION "item_unique_action"
+ #define RAISE_ITEM_UNIQUE_ACTION_HANDLED (1<<0)
/// From base of obj/item/defensive_toggle(): (/datum/event_args/actor/actor)
#define COMSIG_ITEM_DEFENSIVE_TOGGLE "item_defensive_toggle"
+ #define RAISE_ITEM_DEFENSIVE_TOGGLE_HANDLED (1<<0)
/// From base of obj/item/defensive_trigger(): (/datum/event_args/actor/actor)
#define COMSIG_ITEM_DEFENSIVE_TRIGGER "item_defensive_trigger"
+ #define RAISE_ITEM_DEFENSIVE_TRIGGER_HANDLED (1<<0)
diff --git a/code/__DEFINES/power/balancing.dm b/code/__DEFINES/power/balancing.dm
index d966ee58b549..873616268014 100644
--- a/code/__DEFINES/power/balancing.dm
+++ b/code/__DEFINES/power/balancing.dm
@@ -2,8 +2,6 @@
//* Cells
-/// the closest thing we'll get to a cvar - cellrate is kJ per cell unit. kJ to avoid float precision loss.
-GLOBAL_VAR_INIT(cellrate, 0.5)
/**
* current calculations
* cellrate 0.5 = 0.5 kj/unit
@@ -11,7 +9,14 @@ GLOBAL_VAR_INIT(cellrate, 0.5)
* 1 Wh = 60J-S*60s/m = 3600J = 3.6kJ
* 10k cell --> 1388.89 Wh
* damn, future cells be pogging
+ *
+ * * Funnily enough, this puts our cells at just about ~10x the capacity of modern day cells.
+ * That's pretty reasonable given they're meant to power energy weapons and hilariously
+ * sci-fi technologies.
*/
+
+/// the closest thing we'll get to a cvar - cellrate is kJ per cell unit. kJ to avoid float precision loss.
+GLOBAL_VAR_INIT(cellrate, 0.5)
/// the closest thing we'll get to a cvar - affects cell use_scaled - higher = things use less energy. handheld devices usually use this.
GLOBAL_VAR_INIT(cellefficiency, 1)
diff --git a/code/__DEFINES/projectiles/gun_components.dm b/code/__DEFINES/projectiles/gun_components.dm
new file mode 100644
index 000000000000..a5fe19a840f7
--- /dev/null
+++ b/code/__DEFINES/projectiles/gun_components.dm
@@ -0,0 +1,62 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//**** Slots - /obj/item/gun_component ****//
+
+//* Note: These are all suggestions. *//
+//* Components hook the gun via component signals and registration APIs *//
+//* An acceleration coil can hook power draw just like a power unit can. *//
+
+//* All components are defaulted. This is for UX and optimization. *//
+//* Unless a gun otherwise specifies, not having a component just means 'no change'. *//
+//* *//
+//* This is because components are not actually hardcoded APIs that are called *//
+//* during firing, but actually modifiers onto the gun. *//
+//* *//
+//* This is a large departure from traditional guncrafting systems that necessitate *//
+//* invoking procs on all components every cycle. *//
+
+// todo: DEFINE_ENUM on everything
+
+//* - generic - all weaponry -- *//
+
+/// any internal modules like trackers, etc
+#define GUN_COMPONENT_INTERNAL_MODULE "internal-module"
+
+//* - generic - all energy-based weaponry -- *//
+
+/// interacts with energizing the beam lens / acceleration coils
+#define GUN_COMPONENT_ENERGY_HANDLER "energy-handler"
+/// interacts with power draw
+#define GUN_COMPONENT_POWER_UNIT "power-unit"
+/// a ballistic gun can have this but these generally require power to function
+#define GUN_COMPONENT_ACTIVE_COOLER "active-cooler"
+
+//* - generally magnetic - *//
+
+/// component used for accelerating the projectile.
+#define GUN_COMPONENT_ACCELERATION_COIL "magnetic-coil"
+
+//* - generally particle (energy) - *//
+
+/// component used to (re)-focus the energy beam being emit
+#define GUN_COMPONENT_FOCUSING_LENS "focusing-lens"
+/// component that generates the particle stream
+#define GUN_COMPONENT_PARTICLE_ARRAY "particle-array"
+
+GLOBAL_REAL_LIST(gun_component_enum_to_name) = list(
+ GUN_COMPONENT_INTERNAL_MODULE = "internal module",
+ GUN_COMPONENT_ENERGY_HANDLER = "energy handler",
+ GUN_COMPONENT_POWER_UNIT = "power unit",
+ GUN_COMPONENT_ACTIVE_COOLER = "cooling system",
+ GUN_COMPONENT_ACCELERATION_COIL = "acceleration coil",
+ GUN_COMPONENT_FOCUSING_LENS = "focusing lens",
+ GUN_COMPONENT_PARTICLE_ARRAY = "particle array",
+)
+
+//**** Conflict Flags - /obj/item/gun_component ****//
+
+/**
+ * Burst modifiers.
+ */
+#define GUN_COMPONENT_CONFLICT_BURST_MODIFICATION (1<<0)
diff --git a/code/__DEFINES/projectiles/guns.dm b/code/__DEFINES/projectiles/guns.dm
index 86d564dbb74d..2c40eb391c80 100644
--- a/code/__DEFINES/projectiles/guns.dm
+++ b/code/__DEFINES/projectiles/guns.dm
@@ -1,29 +1,35 @@
-///do not do anything after firing. Manual action, like pump shotguns, or guns that want to define custom behaviour
-#define HOLD_CASINGS 0
-///drop spent casings on the ground after firing
-#define EJECT_CASINGS 2
-///cycle casings, like a revolver. Also works for multibarrelled guns
-#define CYCLE_CASINGS 3
-//Gun loading types
-///The gun only accepts ammo_casings. ammo_magazines should never have this as their mag_type.
-#define SINGLE_CASING 1
-///Transfers casings from the mag to the gun when used.
-#define SPEEDLOADER 2
-///The magazine item itself goes inside the gun
-#define MAGAZINE 4
-#define BULLET_IMPACT_NONE "none"
-#define BULLET_IMPACT_METAL "metal"
-#define BULLET_IMPACT_MEAT "meat"
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
-#define SOUNDS_BULLET_MEAT list('sound/effects/projectile_impact/bullet_meat1.ogg', 'sound/effects/projectile_impact/bullet_meat2.ogg', 'sound/effects/projectile_impact/bullet_meat3.ogg', 'sound/effects/projectile_impact/bullet_meat4.ogg')
-#define SOUNDS_BULLET_METAL list('sound/effects/projectile_impact/bullet_metal1.ogg', 'sound/effects/projectile_impact/bullet_metal2.ogg', 'sound/effects/projectile_impact/bullet_metal3.ogg')
-#define SOUNDS_LASER_MEAT list('sound/effects/projectile_impact/energy_meat1.ogg','sound/effects/projectile_impact/energy_meat2.ogg')
-#define SOUNDS_LASER_METAL list('sound/effects/projectile_impact/energy_metal1.ogg','sound/effects/projectile_impact/energy_metal2.ogg')
+//* firing_flags on gun firing procs *//
-// safety states
-/// no safeties are on this gun
-#define GUN_NO_SAFETY -1
-/// safety off
-#define GUN_SAFETY_OFF 0
-/// safety on
-#define GUN_SAFETY_ON 1
+/// perform pointblanking
+#define GUN_FIRING_POINT_BLANK (1<<0)
+/// track the target instead of just using angle
+#define GUN_FIRING_TRACK_TARGET (1<<1)
+/// this is a reflex fire by aiming
+#define GUN_FIRING_BY_REFLEX (1<<2)
+/// do not log
+///
+/// * This is an extremely dangerous flag. Do not use unless you are already logging it somewhere else.
+/// * "This happens all the time" is not a valid excuse to not log a gunshot.
+#define GUN_FIRING_NO_LOGGING (1<<3)
+/// do not call default click empty
+#define GUN_FIRING_NO_CLICK_EMPTY (1<<4)
+/// suppressed shot
+#define GUN_FIRING_SUPPRESSED (1<<5)
+
+//* firing result from firing procs *//
+//* these are flags but should be returned only one at a time. *//
+//* they are flags for fast comparisons. *//
+
+/// fired
+#define GUN_FIRED_SUCCESS 0
+/// unknown failure
+#define GUN_FIRED_FAIL_UNKNOWN (1<<0)
+/// failed - round wasn't live or the right primer type
+#define GUN_FIRED_FAIL_INERT (1<<1)
+/// failed - out of ammo
+#define GUN_FIRED_FAIL_EMPTY (1<<2)
+/// failed - we're no longer being held / mounted / whatever
+#define GUN_FIRED_FAIL_UNMOUNTED (1<<3)
diff --git a/code/__DEFINES/projectiles/guns_legacy.dm b/code/__DEFINES/projectiles/guns_legacy.dm
new file mode 100644
index 000000000000..86d564dbb74d
--- /dev/null
+++ b/code/__DEFINES/projectiles/guns_legacy.dm
@@ -0,0 +1,29 @@
+///do not do anything after firing. Manual action, like pump shotguns, or guns that want to define custom behaviour
+#define HOLD_CASINGS 0
+///drop spent casings on the ground after firing
+#define EJECT_CASINGS 2
+///cycle casings, like a revolver. Also works for multibarrelled guns
+#define CYCLE_CASINGS 3
+//Gun loading types
+///The gun only accepts ammo_casings. ammo_magazines should never have this as their mag_type.
+#define SINGLE_CASING 1
+///Transfers casings from the mag to the gun when used.
+#define SPEEDLOADER 2
+///The magazine item itself goes inside the gun
+#define MAGAZINE 4
+#define BULLET_IMPACT_NONE "none"
+#define BULLET_IMPACT_METAL "metal"
+#define BULLET_IMPACT_MEAT "meat"
+
+#define SOUNDS_BULLET_MEAT list('sound/effects/projectile_impact/bullet_meat1.ogg', 'sound/effects/projectile_impact/bullet_meat2.ogg', 'sound/effects/projectile_impact/bullet_meat3.ogg', 'sound/effects/projectile_impact/bullet_meat4.ogg')
+#define SOUNDS_BULLET_METAL list('sound/effects/projectile_impact/bullet_metal1.ogg', 'sound/effects/projectile_impact/bullet_metal2.ogg', 'sound/effects/projectile_impact/bullet_metal3.ogg')
+#define SOUNDS_LASER_MEAT list('sound/effects/projectile_impact/energy_meat1.ogg','sound/effects/projectile_impact/energy_meat2.ogg')
+#define SOUNDS_LASER_METAL list('sound/effects/projectile_impact/energy_metal1.ogg','sound/effects/projectile_impact/energy_metal2.ogg')
+
+// safety states
+/// no safeties are on this gun
+#define GUN_NO_SAFETY -1
+/// safety off
+#define GUN_SAFETY_OFF 0
+/// safety on
+#define GUN_SAFETY_ON 1
diff --git a/code/__DEFINES/projectiles/projectile.dm b/code/__DEFINES/projectiles/projectile.dm
index f87cff191198..ac906e1ac987 100644
--- a/code/__DEFINES/projectiles/projectile.dm
+++ b/code/__DEFINES/projectiles/projectile.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 silicons *//
+//* Copyright (c) 2024 Citadel Station Developers *//
//* pre_impact(), impact(), bullet_act(), on_impact() impact_flags *//
/// pre_impact, bullet_act, on_impact are called in that order ///
diff --git a/code/__DEFINES/projectiles/system.dm b/code/__DEFINES/projectiles/system.dm
index fafd60aac2ca..7d177322ac9a 100644
--- a/code/__DEFINES/projectiles/system.dm
+++ b/code/__DEFINES/projectiles/system.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 silicons *//
+//* Copyright (c) 2024 Citadel Station Developers *//
//* rendering system
//* this is currently only used on ammo magazines, as guns use composition of datums
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 4e9ca9fe3625..171e1fde9924 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -426,23 +426,6 @@
src.dest_x = dest_x
src.dest_y = dest_y
-/proc/projectile_trajectory(src_x, src_y, rotation, angle, power)
-
- // returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
- // rotated at [rotation] and with the power of [power]
- // Thanks to VistaPOWA for this function
-
- var/power_x = power * cos(angle)
- var/power_y = power * sin(angle)
- var/time = 2* power_y / 10 //10 = g
-
- var/distance = time * power_x
-
- var/dest_x = src_x + distance*sin(rotation);
- var/dest_y = src_y + distance*cos(rotation);
-
- return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
-
/**
* Gets the highest and lowest pressures from the tiles in cardinal directions
* around us, then checks the difference.
diff --git a/code/__HELPERS/lists/clone.dm b/code/__HELPERS/lists/clone.dm
new file mode 100644
index 000000000000..3e81d400c62a
--- /dev/null
+++ b/code/__HELPERS/lists/clone.dm
@@ -0,0 +1,37 @@
+/**
+ * Makes a deep clone of a list.
+ *
+ * * Any datum-types in the list must have clone() implemented.
+ * * This is somewhat expensive. Use sparingly.
+ *
+ * Valid datatypes that can be cloned:
+ *
+ * * numbers
+ * * strings
+ * * lists
+ * * datums with clone() implemented
+ */
+/proc/deep_clone_list(list/L)
+ var/list/copy = L.Copy()
+ for(var/i in 1 to length(copy))
+ var/key = copy[i]
+ var/value = copy[key]
+ // clone key
+ key = deep_clone_value(key)
+ copy[i] = key
+ // clone value if it's there
+ if(isnull(value))
+ continue
+ copy[key] = deep_clone_value(value)
+ return copy
+
+/proc/deep_clone_value(val)
+ if(isnum(val) || istext(val))
+ return val
+ else if(islist(val))
+ return deep_clone_list(val)
+ else if(isdatum(val))
+ var/datum/casted = val
+ return casted.clone()
+ // unimplemented otherwise.
+ return null
diff --git a/code/__HELPERS/math/angle.dm b/code/__HELPERS/math/angle.dm
index 3ba3ce9e7cf7..5e3877aff460 100644
--- a/code/__HELPERS/math/angle.dm
+++ b/code/__HELPERS/math/angle.dm
@@ -52,3 +52,12 @@
. += 180
else if(x < 0)
. += 360
+
+/**
+ * get angle from center of bounding box of entity A to entity B
+ */
+/proc/get_centered_entity_angle(atom/A, atom/B)
+ var/dy
+ var/dx
+ return arctan()
+#warn this
diff --git a/code/__HELPERS/math/distance.dm b/code/__HELPERS/math/distance.dm
index 34841d447aa1..73df0acb6861 100644
--- a/code/__HELPERS/math/distance.dm
+++ b/code/__HELPERS/math/distance.dm
@@ -1,11 +1,15 @@
/**
* checks distance from one thing to another but automatically resolving for turf / nesting
+ *
+ * todo: re-evaluate
*/
/proc/in_range_of(atom/A, atom/B, dist = 1)
return game_range_to(A, B) <= dist
/**
* gets real dist from A to B, including resolving for turf. if not the same Z, returns infinity.
+ *
+ * todo: this is silly, redo?
*/
/proc/game_range_to(atom/A, atom/B)
A = get_turf(A)
@@ -15,23 +19,23 @@
/**
* real dist because byond dist doesn't go above 127 :/
*
- * accepts **TURFS**
+ * * Only accepts **turfs**. Undefined behavior if inputs are not turfs.
*/
-/proc/get_chebyshev_dist(turf/A, turf/B)
+/proc/get_turf_chebyshev_dist(turf/A, turf/B)
return max(abs(A.x - B.x), abs(A.y - B.y))
/**
* real euclidean dist
*
- * accepts **TURFS**
+ * * Only accepts **turfs**. Undefined behavior if inputs are not turfs.
*/
-/proc/get_euclidean_dist(turf/A, turf/B)
+/proc/get_turf_euclidean_dist(turf/A, turf/B)
return sqrt((A.x - B.x) ** 2 + (A.y - B.y) ** 2)
/**
* real taxicab dist
*
- * accepts **TURFS**
+ * * Only accepts **turfs**. Undefined behavior if inputs are not turfs.
*/
-/proc/get_manhattan_dist(turf/A, turf/B)
+/proc/get_turf_manhattan_dist(turf/A, turf/B)
return abs(A.x - B.x) + abs(A.y - B.y)
diff --git a/code/__HELPERS/pathfinding/astar.dm b/code/__HELPERS/pathfinding/astar.dm
index 6120807366c1..8d64733f3e7f 100644
--- a/code/__HELPERS/pathfinding/astar.dm
+++ b/code/__HELPERS/pathfinding/astar.dm
@@ -146,7 +146,7 @@ GLOBAL_VAR_INIT(astar_visualization_persist, 3 SECONDS)
if(src.start == src.goal)
return list()
// too far away
- if(get_manhattan_dist(src.start, src.goal) > max_path_length)
+ if(get_turf_manhattan_dist(src.start, src.goal) > max_path_length)
return null
#ifdef ASTAR_DEBUGGING
var/list/turf/turfs_got_colored = list()
diff --git a/code/__HELPERS/pathfinding/jps.dm b/code/__HELPERS/pathfinding/jps.dm
index e90dcc6770d0..7d053e5fa63e 100644
--- a/code/__HELPERS/pathfinding/jps.dm
+++ b/code/__HELPERS/pathfinding/jps.dm
@@ -112,7 +112,7 @@ GLOBAL_VAR_INIT(jps_visualization_resolve, TRUE)
if(src.start == src.goal)
return list()
// too far away
- if(get_chebyshev_dist(src.start, src.goal) > max_path_length)
+ if(get_turf_chebyshev_dist(src.start, src.goal) > max_path_length)
return null
#ifdef JPS_DEBUGGING
//* set up debugging vars
diff --git a/code/__HELPERS/type_processing.dm b/code/__HELPERS/type_processing.dm
index 5ab1778c6eb6..f49f6dcbdfba 100644
--- a/code/__HELPERS/type_processing.dm
+++ b/code/__HELPERS/type_processing.dm
@@ -51,7 +51,6 @@
/proc/get_fancy_list_of_atom_types()
return make_types_fancy(typesof(/atom))
-
/proc/get_fancy_list_of_datum_types()
return make_types_fancy(typesof(/datum) - typesof(/atom))
diff --git a/code/__HELPERS/typepaths/subtypesof_non_abstract.dm b/code/__HELPERS/typepaths/subtypesof_non_abstract.dm
new file mode 100644
index 000000000000..8c9f29d1ef52
--- /dev/null
+++ b/code/__HELPERS/typepaths/subtypesof_non_abstract.dm
@@ -0,0 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Grabs all datum typepaths under a path that are not abstract.
+ *
+ * * Runtimes if path is not a datum.
+ */
+/proc/subtypesof_non_abstract(datum_path)
+ . = list()
+ for(var/datum/dpath as anything in (typesof(datum_path) - datum_path))
+ if(initial(dpath.abstract_type) == dpath)
+ continue
+ . += dpath
diff --git a/code/__HELPERS/typepaths/typesof_non_abstract.dm b/code/__HELPERS/typepaths/typesof_non_abstract.dm
new file mode 100644
index 000000000000..4554ee528610
--- /dev/null
+++ b/code/__HELPERS/typepaths/typesof_non_abstract.dm
@@ -0,0 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Grabs all datum typepaths under a path that are not abstract.
+ *
+ * * Runtimes if path is not a datum.
+ */
+/proc/typesof_non_abstract(datum_path)
+ . = list()
+ for(var/datum/dpath as anything in typesof(datum_path))
+ if(initial(dpath.abstract_type) == dpath)
+ continue
+ . += dpath
diff --git a/code/controllers/subsystem/mapping/spatial_helpers/distance.dm b/code/controllers/subsystem/mapping/spatial_helpers/distance.dm
index 6a6ea0d5c9b8..fc7a6fdf0ac6 100644
--- a/code/controllers/subsystem/mapping/spatial_helpers/distance.dm
+++ b/code/controllers/subsystem/mapping/spatial_helpers/distance.dm
@@ -15,7 +15,8 @@
*/
/datum/controller/subsystem/mapping/proc/get_virtual_dist(turf/A, turf/B, z_dist)
// todo: get_dist after 515
- return get_manhattan_dist(A, B)
+ // todo: redo this proc / split into multiple; manhattan distance isn't what byond uses
+ return get_turf_manhattan_dist(A, B)
// A = get_turf(A)
// B = get_turf(B)
// if(A.z == B.z)
diff --git a/code/datums/components/items/active_parry.dm b/code/datums/components/items/active_parry.dm
index 1a5dc7dadb9e..6e9bcc8a0fae 100644
--- a/code/datums/components/items/active_parry.dm
+++ b/code/datums/components/items/active_parry.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) Citadel Station Developers *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* generic parry provider on items
diff --git a/code/datums/components/items/passive_parry.dm b/code/datums/components/items/passive_parry.dm
index 678aa914dfca..06d6841ae4dd 100644
--- a/code/datums/components/items/passive_parry.dm
+++ b/code/datums/components/items/passive_parry.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) Citadel Station Developers *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* Shieldcall used as a listener for [/datum/component/passive_parry]
diff --git a/code/datums/components/items/shield_block.dm b/code/datums/components/items/shield_block.dm
index 9e1adc211689..786bfcf499c9 100644
--- a/code/datums/components/items/shield_block.dm
+++ b/code/datums/components/items/shield_block.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) Citadel Station Developers *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* generic shield-like block provider on items
diff --git a/code/datums/components/mobs/block_frame.dm b/code/datums/components/mobs/block_frame.dm
index 6994fae03789..59f6c5b3fd0c 100644
--- a/code/datums/components/mobs/block_frame.dm
+++ b/code/datums/components/mobs/block_frame.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) Citadel Station Developers *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* ## Active Defensives
diff --git a/code/datums/components/mobs/parry_frame.dm b/code/datums/components/mobs/parry_frame.dm
index d56753381192..40eaa336e6d1 100644
--- a/code/datums/components/mobs/parry_frame.dm
+++ b/code/datums/components/mobs/parry_frame.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) Citadel Station Developers *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* ## Active Parry
diff --git a/code/datums/status_effects/basic/taser_stun.dm b/code/datums/status_effects/basic/taser_stun.dm
new file mode 100644
index 000000000000..810817583791
--- /dev/null
+++ b/code/datums/status_effects/basic/taser_stun.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/status_effect/taser_stun
+ identifier = "taser_stun"
+
+ /// pain to inflict per decisecond
+ ///
+ /// * given this usually lasts 5 seconds, 0.75 is around 37.5, which is pretty reasonable for something not meant to be a magdump stun
+ var/pain_per_ds = 0.75
+
+#warn impl - movespeed modifier, pain damage
diff --git a/code/game/click/adjacency.dm b/code/game/click/adjacency.dm
index 85fe1660b9c6..7fc903c75cf3 100644
--- a/code/game/click/adjacency.dm
+++ b/code/game/click/adjacency.dm
@@ -6,6 +6,8 @@
*
* **DO NOT** default recursion to on.
*
+ * * This call is basically just one-tile-reach Reachability().
+ *
* @params
* - neighbor - what we're trying to reach
* - recurse - levels we're allowed to recurse up if we're not on a turf
diff --git a/code/game/click/drag_drop.dm b/code/game/click/drag_drop.dm
index df676da9a9b1..ccf1fe666abb 100644
--- a/code/game/click/drag_drop.dm
+++ b/code/game/click/drag_drop.dm
@@ -158,12 +158,6 @@
/obj/item
var/canMouseDown = FALSE
-/obj/item/gun
- var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
-
-/obj/item/gun/CanItemAutoclick(object, location, params)
- . = automatic
-
/atom/proc/IsAutoclickable()
. = 1
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_isd.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_isd.dm
new file mode 100644
index 000000000000..8d47c9b9b6d6
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_isd.dm
@@ -0,0 +1,244 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/firemode/energy/nt_isd
+ abstract_type = /datum/firemode/energy/nt_isd
+
+/**
+ * Weapons for NT's Internal Security.
+ *
+ * * Above-average energy weapons
+ * * Expensive
+ * * Joint with Hephaestus / Vey-Med, canonically
+ * * There's probably a neat amount of these just floating around the Frontier now from losses.
+ *
+ * Things to keep in mind:
+ *
+ * * Stun does not mean something is cheap as, or cheaper than, lethal.
+ * * Stun in this codebase is not treated as any special or even preferable damage type.
+ * * Nanotrasen uses stun weaponry for arrests, but in-canon security is rarely having to
+ * use physical and ranged force against other employees.
+ * * Stun weapons should generally be worse at stunning than lethal modes of that weapon
+ * are at downing someone who is armored.
+ */
+/obj/item/gun/energy/nt_isd
+ abstract_type = /obj/item/gun/energy/nt_isd
+
+//* Energy Sidearm *//
+
+/datum/firemode/energy/nt_isd/sidearm
+ abstract_type = /datum/firemode/energy/nt_isd/sidearm
+
+/datum/firemode/energy/nt_isd/sidearm/stun
+ name = "disrupt"
+ render_color = "#ffff00"
+ charge_cost = 2400 / 8
+ projectile_type = /obj/projectile/nt_isd/electrode
+
+/datum/firemode/energy/nt_isd/sidearm/disable
+ name = "disable"
+ render_color = "#77ffff"
+ charge_cost = 2400 / 20
+ projectile_type = /obj/projectile/nt_isd/disable
+
+/datum/firemode/energy/nt_isd/sidearm/lethal
+ name = "kill"
+ render_color = "#ff0000"
+ charge_cost = 2400 / 15
+ projectile_type = /obj/projectile/nt_isd/laser/sidearm
+
+/obj/item/gun/energy/nt_isd/sidearm
+ name = "hybrid taser"
+ desc = "A versatile energy sidearm used by corporate security."
+ description_fluff = {"
+ A sidearm designed and manufactured by the Nanotrasen Research Division for its internal
+ security needs. Specialized in non-lethal takedowns of high-risk perpetrators, the ENP-17
+ is reminiscent of older electro-neural disruption devices used by less advanced societies in
+ how it operates.
+
+ After an increase in the presence of non-humanoid threats against Nanotrasen's operations in the
+ Frontier, this standard sidearm received an upgrade adding a more powerful focusing lens used for
+ a lethal setting that can be used in emergencies.
+ "}
+ firemodes = list(
+ /datum/firemode/energy/nt_isd/sidearm/stun,
+ /datum/firemode/energy/nt_isd/sidearm/disable,
+ /datum/firemode/energy/nt_isd/sidearm/lethal,
+ )
+
+#warn impl
+
+//* Energy Carbine *//
+
+/datum/firemode/energy/nt_isd/carbine
+ abstract_type = /datum/firemode/energy/nt_isd/carbine
+
+/datum/firemode/energy/nt_isd/carbine/disable
+ name = "disable"
+ render_color = "#77ffff"
+ charge_cost = 2400 / 20
+ projectile_type = /obj/projectile/nt_isd/disable
+
+/datum/firemode/energy/nt_isd/carbine/shock
+ name = "shock"
+ render_color = "#ffff00"
+ charge_cost = 2400 / 10
+ projectile_type = /obj/projectile/nt_isd/shock
+
+/datum/firemode/energy/nt_isd/carbine/kill
+ name = "kill"
+ render_color = "#ff0000"
+ charge_cost = 2400 / 10
+ projectile_type = /obj/projectile/nt_isd/laser/rifle
+
+/obj/item/gun/energy/nt_isd/carbine
+ name = "energy carbine"
+ desc = "A versatile energy carbine often seen in the hands of frontier groups."
+ description_fluff = {"
+ A production model energy weapon developed in joint between the Nanotrasen Research Division
+ and Hephaestus Industries. Containing multiple focusing modes for its integrated particle
+ projector, the weapon has quickly proliferated to be a common sight on the Frontier.
+
+ An unfortunate consequence of this has been the equal proliferation of protective gear meant to
+ counteract this weapon's capabilities - with many threat-actors and even certain strains of lifeforms
+ developing augmented resistance to the weapon's stun settings - much to Nanotrasen's displeasure.
+ While Nanotrasen has many times attempted to replace this weapon's place in the staples of its
+ security divisions, all attempts to date have thus far failed.
+ "}
+ firemodes = list(
+ /datum/firemode/energy/nt_isd/carbine/disable,
+ /datum/firemode/energy/nt_isd/carbine/shock,
+ /datum/firemode/energy/nt_isd/carbine/kill,
+ )
+
+#warn impl
+
+//* Energy Lance *//
+
+/datum/firemode/energy/nt_isd/lance
+ abstract_type = /datum/firemode/energy/nt_isd/lance
+
+/datum/firemode/energy/nt_isd/lance/kill
+ name = "kill"
+ render_color = "#00ff00"
+ charge_cost = 2400 / 12
+ projectile_type = /obj/projectile/nt_isd/laser/lance
+
+/obj/item/gun/energy/nt_isd/lance
+ name = "energy lance"
+ desc = "A particle rifle used by corporate security. Shoots focused particle beams."
+ description_fluff = {"
+ Developed and used primarily by the Nanotrasen Research Division, the ENR-18 was
+ designed to be a specialized anti-armour weapon supplied to response teams and sparingly
+ stocked on installations operating in the most high-risk sectors.
+
+ Unfortunately, the march of modern technology and weaponry has forced the Research Division
+ to proliferate this weapon to many more of Nanotrasen's holdings due to the low, but
+ non-negligible risk of an incursion resistant to the standard Hephaestus weaponry used
+ at the time by Nanotrasen's internal security.
+ "}
+ firemodes = list(
+ /datum/firemode/energy/nt_isd/lance/kill,
+ )
+
+#warn impl
+
+//* Multiphase Sidearm *//
+
+/datum/firemode/energy/nt_isd/multiphase
+
+/datum/firemode/energy/nt_isd/multiphase/disable
+ name = "disable"
+ render_color = "#77ffff"
+ projectile_type = /obj/projectile/nt_isd/disable
+ charge_cost = 2400 / 20
+
+/datum/firemode/energy/nt_isd/multiphase/kill
+ name = "kill"
+ render_color = "#ff0000"
+ projectile_type = /obj/projectile/nt_isd/laser/multiphase
+ charge_cost = 2400 / 12
+
+// todo: this is an ion beam, not an EMP pulse
+/datum/firemode/energy/nt_isd/multiphase/ion
+ name = "ion"
+ render_color = "#456aaa"
+ projectile_type = /obj/projectile/nt_isd/ion
+ charge_cost = 2400 / 5
+
+/obj/item/gun/energy/nt_isd/multiphase
+ name = "multiphase sidearm"
+ desc = "A prototype sidearm for high-ranking corporate security."
+ description_fluff = {"
+ A very expensive development of the Nanotrasen Research Division, the ENP-19 is
+ a durable sidearm manufactured for usage by the leaders of many internal security teams.
+ Containing a particle generation system closer to those used in Nanotrasen's secretive
+ pulse rifles than that of common Frontier energy eaponry, this weapon can be used in a variety
+ of scenarios.
+ "}
+ firemodes = list(
+ /datum/firemode/energy/nt_isd/multiphase/disable,
+ /datum/firemode/energy/nt_isd/multiphase/kill,
+ /datum/firemode/energy/nt_isd/multiphase/ion,
+ )
+
+#warn impl
+
+//* Projectiles *//
+
+/obj/projectile/nt_isd
+ abstract_type = /obj/projectile/nt_isd
+
+/obj/projectile/nt_isd/laser
+ abstract_type = /obj/projectile/nt_isd/laser
+ damage_type = DAMAGE_TYPE_BURN
+
+/obj/projectile/nt_isd/laser/rifle
+ name = "laser"
+ damage_force = 40
+ damage_tier = LASER_TIER_MEDIUM
+
+/obj/projectile/nt_isd/laser/sidearm
+ name = "phaser blast"
+ damage_force = 20
+ damage_tier = LASER_TIER_HIGH // ;)
+ // todo: remove
+ armor_penetration = 20
+
+/obj/projectile/nt_isd/laser/multiphase
+ name = "focused laser"
+ damage_force = 40
+ damage_tier = LASER_TIER_HIGH
+ // todo: remove
+ armor_penetration = 37.5
+
+/obj/projectile/nt_isd/laser/lance
+ name = "particle beam"
+ damage_force = 30
+ damage_tier = LASER_TIER_HIGH
+ // todo: remove
+ armor_penetration = 50
+
+#warn sprites for above
+
+/obj/projectile/nt_isd/shock
+ name = "energy beam"
+ #warn impl
+
+/obj/projectile/nt_isd/electrode
+ name = "stun bolt"
+ #warn impl
+
+/obj/projectile/nt_isd/disable
+ name = "disabler beam"
+ #warn impl
+
+// todo: this shouldn't be an emp, this should be like synthetik's
+/obj/projectile/nt_isd/ion
+ name = "ion beam"
+ base_projectile_effects = list(
+ /datum/projectile_effect/detonation/legacy_emp{
+ sev_2 = 1;
+ sev_3 = 2;
+ },
+ )
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pmd.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pmd.dm
new file mode 100644
index 000000000000..08a9da177026
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pmd.dm
@@ -0,0 +1,74 @@
+
+
+/**
+ * Transforming service weapon for the Nanotrasen PMD. Sprites & work by Captain277.
+ */
+
+/datum/firemode/energy/nt_pmd/service_revolver
+ abstract_type = /datum/firemode/energy/nt_pmd/service_revolver
+ cycle_cooldown = 0.4 SECONDS
+
+/datum/firemode/energy/nt_pmd/service_revolver/normal
+ name = "normal"
+ projectile_type = /obj/projectile/bullet/pistol/medium/silver
+ charge_cost = 2400 / 8
+
+/datum/firemode/energy/nt_pmd/service_revolver/normal/make_radial_appearance()
+ return image(/obj/item/gun/energy/nt_pmd/service_revolver::icon, "service-normal")
+
+/datum/firemode/energy/nt_pmd/service_revolver/shatter
+ name = "shatter"
+ projectile_type = /obj/projectile/bullet/pellet/shotgun/silvershot
+ cycle_cooldown = 1.5 SECONDS
+ charge_cost = 2400 / 5
+
+/datum/firemode/energy/nt_pmd/service_revolver/shatter/make_radial_appearance()
+ return image(/obj/item/gun/energy/nt_pmd/service_revolver::icon, "service-shatter")
+
+/datum/firemode/energy/nt_pmd/service_revolver/spin
+ name = "spin"
+ projectile_type = /obj/projectile/bullet/pistol/spin
+ cycle_cooldown = 0.1 SECONDS
+ charge_cost = 2400 / 80
+
+/datum/firemode/energy/nt_pmd/service_revolver/spin/make_radial_appearance()
+ return image(/obj/item/gun/energy/nt_pmd/service_revolver::icon, "service-spin")
+
+/datum/firemode/energy/nt_pmd/service_revolver/pierce
+ name = "pierce"
+ projectile_type = /obj/projectile/bullet/rifle/a762/ap/silver
+ cycle_cooldown = 1.5 SECONDS
+ charge_cost = 2400 / 5
+
+/datum/firemode/energy/nt_pmd/service_revolver/pierce/make_radial_appearance()
+ return image(/obj/item/gun/energy/nt_pmd/service_revolver::icon, "service-pierce")
+
+/datum/firemode/energy/nt_pmd/service_revolver/charge
+ name = "charge"
+ projectile_type = /obj/projectile/bullet/burstbullet/service
+ cycle_cooldown = 2 SECONDS
+ charge_cost = 2400 / 4
+
+/datum/firemode/energy/nt_pmd/service_revolver/charge/make_radial_appearance()
+ return image(/obj/item/gun/energy/nt_pmd/service_revolver::icon, "service-charge")
+
+/obj/item/gun/energy/nt_pmd/service_revolver
+ name = "service weapon"
+ icon_state = "service_grip"
+ #warn rename icon states, move icon over.
+ desc = "An anomalous weapon, long kept secure. It has recently been acquired by Nanotrasen's Paracausal Monitoring Division. How did it get here?"
+ damage_force = 5
+ slot_flags = SLOT_BELT
+ w_class = WEIGHT_CLASS_NORMAL
+ origin_tech = null
+ cell_type = /obj/item/cell/device/weapon/recharge/captain
+ legacy_battery_lock = 1
+ one_handed_penalty = 0
+ safety_state = GUN_SAFETY_OFF
+ firemodes = list(
+ /datum/firemode/energy/nt_pmd/service_revolver/normal,
+ /datum/firemode/energy/nt_pmd/service_revolver/shatter,
+ /datum/firemode/energy/nt_pmd/service_revolver/spin,
+ /datum/firemode/energy/nt_pmd/service_revolver/pierce,
+ /datum/firemode/energy/nt_pmd/service_revolver/charge,
+ )
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-ammo.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-ammo.dm
new file mode 100644
index 000000000000..ff0b098a997c
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-ammo.dm
@@ -0,0 +1,110 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/ammo_casing/nt_protomag
+ name = "protomag casing"
+ desc = "An obnoxiously long casing for some kind of rifle."
+ icon = 'icons/content/factions/corporations/nanotrasen/items/guns/protomag/ammo.dmi'
+ icon_state = "slug"
+ caliber = /datum/ammo_caliber/nt_protomag
+
+ /// override strip color
+ var/stripe_color
+
+/obj/item/ammo_casing/nt_protomag/Initialize(mapload)
+ . = ..()
+ var/image/stripe_image = image(icon, "[icon_state]-stripe")
+ var/obj/projectile/nt_protomag/casted_projectile = projectile_type
+ stripe_image.color = stripe_color || initial(casted_projectile.color)
+ add_overlay(stripe_image, TRUE)
+
+/obj/item/ammo_casing/nt_protomag/magboosted
+ name = "protomag round"
+ desc = "A slender bullet. It seems to have less propellant than usual."
+ casing_primer = CASING_PRIMER_MAGNETIC | CASING_PRIMER_CHEMICAL
+
+/obj/item/ammo_casing/nt_protomag/magboosted/standard
+ projectile_type = /obj/projectile/nt_protomag/standard
+
+/obj/item/ammo_casing/nt_protomag/magboosted/sabot
+ name = "protomag round (sabot)"
+ desc = "A slender bullet. While lacking in stopping power, this round is designed to punch through thicker than usual armor."
+
+ projectile_type = /obj/projectile/nt_protomag/sabot
+
+// todo: this is currently disabled as medcode is not verbose enough for this to work
+// /obj/item/ammo_casing/nt_protomag/magboosted/shredder
+// name = "protomag round (shredder)"
+// desc = "A slender bullet. While lacking in penetration, this round is designed to shred soft targets with ease."
+//
+// projectile_type = /obj/projectile/nt_protomag/shredder
+
+/obj/item/ammo_casing/nt_protomag/magboosted/impact
+ name = "protomag round (impact)"
+ desc = "A slender bullet. This round is the magnetic equivalent of a beanbag. That said, it would be a bad idea to detain someone with a railgun, beanbag or not."
+
+ projectile_type = /obj/projectile/nt_protomag/impact
+
+/obj/item/ammo_casing/nt_protomag/magboosted/practice
+ name = "protomag round (practice)"
+ desc = "A slender bullet. This round is just a practice round. While it is made out of relatively soft materials, you should still try to not get shot by this."
+
+ projectile_type = /obj/projectile/nt_protomag/practice
+
+/obj/item/ammo_casing/nt_protomag/magnetic
+ name = "protomag slug"
+ desc = "A slender ferromagnetic slug. A bullet without propellant, for whatever reason."
+ casing_primer = CASING_PRIMER_MAGNETIC
+
+/obj/item/ammo_casing/nt_protomag/magnetic/smoke
+ name = "protomag slug (smoke)"
+ desc = "A slender ferromagnetic slug. While lacking in penetration, this round releases a light smokescreen on impact."
+
+ projectile_type = /obj/projectile/nt_protomag/smoke
+
+/obj/item/ammo_casing/nt_protomag/magnetic/emp
+ name = "protomag slug (emp)"
+ desc = "A slender ferromagnetic slug. While lacking in penetration, this round releases a small electromagnetic burst on impact."
+
+ projectile_type = /obj/projectile/nt_protomag/emp
+
+// todo: this is currently disabled as simplemobs are not complex-AI enough for us to do this, and we don't need a PVP-only tool
+// /obj/item/ammo_casing/nt_protomag/magnetic/concussive
+// name = "protomag slug (concussive)"
+// desc = "A slender ferromagnetic slug. While lacking in penetration, this round contains a small airburst charge that detonates on impact."
+
+// projectile_type = /obj/projectile/nt_protomag/concussive
+
+/obj/item/ammo_casing/nt_protomag/magnetic/penetrator
+ name = "protomag slug (penetrator)"
+ desc = "A slender ferromagnetic slug. This one is made out of dense alloys, and is designed to punch through materials with ease. This round has very high recoil, as well as power draw."
+
+ projectile_type = /obj/projectile/nt_protomag/penetrator
+
+/obj/item/ammo_casing/nt_protomag/magnetic/shock
+ name = "protomag slug (shock)"
+ desc = "A slender ferromagnetic slug. This one is designed to release a burst of energy on imapct for less-than-lethal takedowns. That said, it would probably still be a bad idea to detain someone with a railgun slug."
+
+ projectile_type = /obj/projectile/nt_protomag/shock
+
+/obj/item/ammo_casing/nt_protomag/magnetic/flare
+ name = "protomag slug (flare)"
+ desc = "A slender ferromagnetic slug. Shatters into a lingering chemical illuminant on impact."
+
+ projectile_type = /obj/projectile/nt_protomag/flare
+
+// todo: fuck no, rework fire stacks / fire first, holy crap; even then this should take multiple hits to ignite.
+// /obj/item/ammo_casing/nt_protomag/magnetic/incendiary
+// name = "protomag slug (incendiary)"
+// desc = "A slender ferromagnetic slug. With almost no penetrating power whatsoever, this round is designed to explode into an incendiary material on impact"
+
+// projectile_type = /obj/projectile/nt_protomag/incendiary
+
+// todo: fuck no, not until chloral and chemicals are reworked; this round is meant to take like 2-3 units maximum, on that note.
+// /obj/item/ammo_casing/nt_protomag/magnetic/reagent
+// name = "protomag slug (chemical)"
+// desc = "A slender ferromagnetic slug. Can be laced with a small amount of reagents, which will then splash onto and be injected into a hit target."
+
+// projectile_type = /obj/projectile/nt_protomag/reagent
+
+#warn impl all
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-caliber.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-caliber.dm
new file mode 100644
index 000000000000..75b43fadced0
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-caliber.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+// todo: proper diameter/length def
+/datum/ammo_caliber/nt_protomag
+ caliber = "nt-protomag"
+
+/datum/ammo_caliber/nt_protomag/antimaterial
+ caliber = "nt-protomag-antimaterial"
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-magazine.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-magazine.dm
new file mode 100644
index 000000000000..ceff3dd74665
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-magazine.dm
@@ -0,0 +1,101 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/ammo_magazine/nt_protomag
+ abstract_type = /obj/item/ammo_magazine/nt_protomag
+ desc = "A magazine for a magnetic weapon of some kind."
+ ammo_caliber = /datum/caliber/nt_protomag
+
+#warn first two should fit in webbing, but not boxes
+
+//* Sidearm Magazines *//
+
+/obj/item/ammo_magazine/nt_protomag/sidearm
+ name = "protomag sidearm magazine"
+ ammo_max = 8
+
+ w_class = WEIGHT_CLASS_NORMAL // no boxes
+ weight_volume = WEIGHT_VOLUME_TINY
+ slot_flags = SLOT_POCKET
+
+//* Rifle Magazines *//
+
+/obj/item/ammo_magazine/nt_protomag/rifle
+ name = "protomag rifle magazine"
+ ammo_max = 16
+
+ w_class = WEIGHT_CLASS_NORMAL // no boxes
+ weight_volume = WEIGHT_VOLUME_SMALL
+ slot_flags = SLOT_POCKET
+
+//* Boxes *//
+#warn these are rifle mags; make pistol mags the appropriate size. also, add stripes
+
+/obj/item/ammo_magazine/nt_protomag/box
+ abstract_type = /obj/item/ammo_magazine/nt_protomag/box
+ name = "protomag ammo box"
+ desc = "A box of experimental magnetic ammunition."
+ ammo_max = 32
+
+ w_class = WEIGHT_CLASS_NORMAL // no boxes
+ weight_volume = WEIGHT_VOLUME_NORMAL
+ slot_flags = SLOT_POCKET
+
+/obj/item/ammo_magazine/nt_protomag/box/standard
+ name = "protomag ammo box (standard)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magboosted/standard
+
+/obj/item/ammo_magazine/nt_protomag/box/sabot
+ name = "protomag ammo box (sabot)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magboosted/sabot
+
+// todo: this is currently disabled as medcode is not verbose enough for this to work
+// /obj/item/ammo_magazine/nt_protomag/box/shredder
+// name = "protomag ammo box (shredder)"
+// ammo_preload = /obj/item/ammo_casing/nt_protomag/magboosted/shredder
+
+/obj/item/ammo_magazine/nt_protomag/box/impact
+ name = "protomag ammo box (impact)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magboosted/impact
+
+/obj/item/ammo_magazine/nt_protomag/box/practice
+ name = "protomag ammo box (practice)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magboosted/practice
+
+/obj/item/ammo_magazine/nt_protomag/box/smoke
+ name = "protomag ammo box (smoke)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/smoke
+
+/obj/item/ammo_magazine/nt_protomag/box/emp
+ name = "protomag ammo box (emp)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/emp
+
+// todo: this is currently disabled as simplemobs are not complex-AI enough for us to do this, and we don't need a PVP-only tool
+// /obj/item/ammo_magazine/nt_protomag/box/concussive
+// name = "protomag ammo box (concussive)"
+// ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/concussive
+
+/obj/item/ammo_magazine/nt_protomag/box/penetrator
+ name = "protomag ammo box (penetrator)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/penetrator
+
+/obj/item/ammo_magazine/nt_protomag/box/shock
+ name = "protomag ammo box (shock)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/shock
+
+/obj/item/ammo_magazine/nt_protomag/box/flare
+ name = "protomag ammo box (flare)"
+ ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/flare
+
+// todo: fuck no, rework fire stacks / fire first, holy crap; even then this should take multiple hits to ignite.
+// /obj/item/ammo_magazine/nt_protomag/box/incendiary
+// name = "protomag ammo box (incendiary)"
+// ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/incendiary
+
+// todo: fuck no, not until chloral and chemicals are reworked; this round is meant to take like 2-3 units maximum, on that note.
+// /obj/item/ammo_magazine/nt_protomag/box/reagent
+// name = "protomag ammo box (reagent)"
+// ammo_preload = /obj/item/ammo_casing/nt_protomag/magnetic/reagent
+
+#warn impl all
+#warn materials & R&D designs for all of the abvoe
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-projectile.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-projectile.dm
new file mode 100644
index 000000000000..de6f55882b37
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag-projectile.dm
@@ -0,0 +1,73 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/projectile/nt_protomag
+ abstract_type = /obj/projectile/nt_protomag
+ icon = 'icons/content/factions/corporations/nanotrasen/items/guns/protomag/projectile.dmi'
+ icon_state = "kinetic"
+ overlays = list(
+ /image{
+ icon_state = "kinetic-emissive";
+ plane = EMISSIVE_PLANE;
+ layer = MANGLE_PLANE_AND_LAYER(/obj/projectile/nt_protomag::plane, /obj/projectile/nt_protomag::layer);
+ }
+ )
+
+/obj/projectile/nt_protomag/standard
+ name = "magnetic slug"
+ color = "#ccaa55"
+
+/obj/projectile/nt_protomag/sabot
+ name = "dense slug"
+ color = "#ff7700"
+
+// todo: this is currently disabled as medcode is not verbose enough for this to work
+// /obj/projectile/nt_protomag/shredder
+// name = "fragmenting slug"
+
+/obj/projectile/nt_protomag/impact
+ name = "deforming slug"
+ color = "#3333aa"
+
+/obj/projectile/nt_protomag/practice
+ name = "lightweight slug"
+ color = "#ffffff"
+
+/obj/projectile/nt_protomag/smoke
+ name = "disintegrating slug"
+ color = "#888888"
+
+/obj/projectile/nt_protomag/emp
+ name = "ion slug"
+ color = "#aaaaff"
+ base_projectile_effects = list(
+ /datum/projectile_effect/detonation/legacy_emp{
+ sev_3 = 2;
+ }
+ )
+
+// todo: this is currently disabled as simplemobs are not complex-AI enough for us to do this, and we don't need a PVP-only tool
+// /obj/projectile/nt_protomag/concussive
+// name = "concussive slug"
+
+/obj/projectile/nt_protomag/penetrator
+ name = "high-velocity slug"
+ color = "#aaffaa"
+
+/obj/projectile/nt_protomag/shock
+ name = "piezo slug"
+ color = "#cccc55"
+
+/obj/projectile/nt_protomag/flare
+ name = "tracer shot"
+ color = "#aa3333"
+
+// todo: fuck no, rework fire stacks / fire first, holy crap; even then this should take multiple hits to ignite.
+// /obj/projectile/nt_protomag/incendiary
+// name = "incendiary slug"
+
+// todo: fuck no, not until chloral and chemicals are reworked; this round is meant to take like 2-3 units maximum, on that note.
+// /obj/projectile/nt_protomag/reagent
+// name = "chemical slug"
+
+#warn impl all
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag.dm
new file mode 100644
index 000000000000..debe6774a43f
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_protomag.dm
@@ -0,0 +1,33 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Modular mag-boosted weapons, courtesy of the Nanotrasen Research Division.
+ */
+/obj/item/gun/ballistic/magnetic/modular/nt_protomag
+ abstract_type = /obj/item/gun/ballistic/magnetic/modular/nt_protomag
+ desc = "A modular ferromagnetic-boosted weapon. Uses experimental ferromagnetic ammunition."
+ description_fluff = {"
+ An experimental magnetic weapon from the Nanotrasen Research Division. The 'Protomag' series uses specially
+ made ammunition capable of a hybrid launch, combining conventional propellant with an accelerating burst
+ from a set of acceleration coils to throw a slug down-range. While still lacking in ammo capacity,
+ this 'prototype' is already made in many Nanotrasen fleets for day-to-day usage. As of recent, designs
+ for specialized cartridges have been released for field testing, though many of said rounds require
+ a large amount of energy to discharge, in contrast to more normal hybrid rounds.
+ "}
+
+#warn sounds for everything
+
+//* Sidearm *//
+
+#warn impl all
+
+/obj/item/gun/ballistic/magnetic/modular/nt_protomag/sidearm
+ name = "protomag sidearm"
+
+//* Rifle *//
+
+#warn impl all
+
+/obj/item/gun/ballistic/magnetic/modular/nt_protomag/rifle
+ name = "protomag rifle"
diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dm b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dm
index 94ce7205f12d..c691e74d339d 100644
--- a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dm
+++ b/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dm
@@ -1,42 +1,45 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station Developers *//
-/datum/firemode/energy/nt_pulse/rifle
+/datum/firemode/energy/nt_pulse
+ abstract_type = /datum/firemode/energy/nt_pulse
+ cycle_cooldown = 0.4 SECONDS
-/datum/firemode/energy/nt_pulse/rifle/laser
- name = "laser"
- render_key = "kill"
- settings = list(mode_name = "lethal", projectile_type = /obj/projectile/beam, charge_cost = 80)
+/**
+ * NT's military (Asset Protection & Emergency Responder) energy rifles
+ */
+/obj/item/gun/energy/nt_pulse
+ abstract_type = /obj/item/gun/energy/nt_pulse
+ icon = 'icons/content/factions/corporations/nanotrasen/items/guns/pulse.dmi'
+ description_fluff = {"
+ A breakthrough weapon from Nanotrasen's Research Division, pulse weapons utilize rare crystals in its generation array,
+ allowing for a more laminar and cohesive beam than prior thought possible. Closely guarded designs to this day,
+ pulse weapons are some of the only energy-based armaments able to consistently outperform any kinetic alternative.
+ "}
-/datum/firemode/energy/nt_pulse/rifle/pulse
- name = "pulse"
- render_key = "destroy"
- settings = list(mode_name = "destroy", projectile_type = /obj/projectile/beam/pulse, charge_cost = 180)
+//* Rifle *//
-/datum/firemode/energy/nt_pulse/carbine
+/datum/firemode/energy/nt_pulse/rifle
+ abstract_type = /datum/firemode/energy/nt_pulse/rifle
-/datum/firemode/energy/nt_pulse/carbine/laser
+/datum/firemode/energy/nt_pulse/rifle/laser
name = "laser"
render_key = "kill"
- settings = list(mode_name = "lethal", projectile_type = /obj/projectile/beam, charge_cost = 120)
+ // todo: function of defines for weapon cell standard capacities
+ charge_cost = 80
+ projectile_type = /obj/projectile/beam
-/datum/firemode/energy/nt_pulse/carbine/pulse
+/datum/firemode/energy/nt_pulse/rifle/pulse
name = "pulse"
render_key = "destroy"
- settings = list(mode_name = "destroy", projectile_type = /obj/projectile/beam/pulse, charge_cost = 240)
-
-/obj/item/gun/energy/nt_pulse
- icon = 'icons/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dmi'
+ // todo: function of defines for weapon cell standard capacities
+ charge_cost = 160
+ projectile_type = /obj/projectile/beam/pulse
/obj/item/gun/energy/nt_pulse/rifle
+ prototype_id = "nt-pulse-rifle"
name = "pulse rifle"
desc = "A powerful energy rifle with multiple intensity selectors."
- // intentionally the same as all pulse weapons to save memory
- description_fluff = {"
- A breakthrough weapon from Nanotrasen's Research Division, pulse weapons utilize rare crystals in its generation array,
- allowing for a more laminar and cohesive beam than prior thought possible. Closely guarded designs to this day,
- pulse weapons are some of the only energy-based armaments able to consistently outperform any kinetic alternative.
- "}
icon_state = "rifle"
base_icon_state = "rifle"
base_mob_state = "pulse"
@@ -46,7 +49,6 @@
// todo: firemode this
heavy = TRUE
// todo: firemode this
- fire_delay = 5 // might need to nerf this to 8 later, this is a very powerful weapon.
firemodes = list(
/datum/firemode/energy/nt_pulse/rifle/laser,
@@ -66,21 +68,34 @@
empty_state = TRUE;
}
+//* Carbine *//
+
+/datum/firemode/energy/nt_pulse/carbine
+ abstract_type = /datum/firemode/energy/nt_pulse/carbine
+
+/datum/firemode/energy/nt_pulse/carbine/laser
+ name = "laser"
+ render_key = "kill"
+ // todo: function of defines for weapon cell standard capacities
+ charge_cost = 120
+ projectile_type = /obj/projectile/beam
+
+/datum/firemode/energy/nt_pulse/carbine/pulse
+ name = "pulse"
+ render_key = "destroy"
+ // todo: function of defines for weapon cell standard capacities
+ charge_cost = 240
+ projectile_type = /obj/projectile/beam/pulse
+
/obj/item/gun/energy/nt_pulse/carbine
+ prototype_id = "nt-pulse-carbine"
name = "pulse carbine"
desc = "A powerful energy carbine with multiple intensity selectors."
- // intentionally the same as all pulse weapons to save memory
- description_fluff = {"
- A breakthrough weapon from Nanotrasen's Research Division, pulse weapons utilize rare crystals in its generation array,
- allowing for a more laminar and cohesive beam than prior thought possible. Closely guarded designs to this day,
- pulse weapons are some of the only energy-based armaments able to consistently outperform any kinetic alternative.
- "}
icon_state = "carbine"
base_icon_state = "carbine"
base_mob_state = "pulse"
slot_flags = SLOT_BELT
// todo: firemode this
- fire_delay = 5 // might need to nerf this to 8 later, this is a very powerful weapon.
firemodes = list(
/datum/firemode/energy/nt_pulse/carbine/laser,
@@ -100,6 +115,8 @@
empty_state = TRUE;
}
+//* Projectiles *//
+
/obj/projectile/beam/pulse
name = "pulse"
icon_state = "u_laser"
@@ -113,6 +130,8 @@
tracer_type = /obj/effect/projectile/tracer/laser_pulse
impact_type = /obj/effect/projectile/impact/laser_pulse
+// todo: this shouldn't be here i think
/obj/projectile/beam/pulse/shotgun
damage_force = 50
armor_penetration = 25
+XTREME
diff --git a/code/game/machinery/turrets/turret.dm b/code/game/machinery/turrets/turret.dm
index a22964f65fd1..efaefcd64fc7 100644
--- a/code/game/machinery/turrets/turret.dm
+++ b/code/game/machinery/turrets/turret.dm
@@ -391,7 +391,7 @@
to_chat(user, "You remove the turret and salvage some components.")
if(installation)
var/obj/item/gun/energy/Gun = new installation(loc)
- Gun.power_supply.charge = gun_charge
+ Gun.obj_cell_slot.cell.charge = gun_charge
Gun.update_icon()
if(prob(50))
new /obj/item/stack/material/steel(loc, rand(1,4))
diff --git a/code/game/machinery/turrets/turret_frame.dm b/code/game/machinery/turrets/turret_frame.dm
index d925d4aaeeac..db3d03883524 100644
--- a/code/game/machinery/turrets/turret_frame.dm
+++ b/code/game/machinery/turrets/turret_frame.dm
@@ -81,7 +81,7 @@
return
var/obj/item/gun/energy/E = I //typecasts the item to an energy gun
installation = I.type //installation becomes I.type
- gun_charge = E.power_supply.charge //the gun's charge is stored in gun_charge
+ gun_charge = E.obj_cell_slot.cell.charge //the gun's charge is stored in gun_charge
to_chat(user, "You add [I] to the turret.")
target_type = /obj/machinery/porta_turret
@@ -181,7 +181,7 @@
build_step = 3
var/obj/item/gun/energy/Gun = new installation(loc)
- Gun.power_supply.charge = gun_charge
+ Gun.obj_cell_slot.cell.charge = gun_charge
Gun.update_icon()
installation = null
gun_charge = 0
diff --git a/code/game/objects/items-interaction.dm b/code/game/objects/items-interaction.dm
index 6a7ec7d7eec9..6cb90fe9a26f 100644
--- a/code/game/objects/items-interaction.dm
+++ b/code/game/objects/items-interaction.dm
@@ -185,7 +185,9 @@
/obj/item/proc/attack_self(mob/user, datum/event_args/actor/actor)
// todo: this should realistically be SHOULD_NOT_OVERRIDE but there's a massive number of overrides (some unnecessary), so this is for a later date
// SHOULD_NOT_OVERRIDE(TRUE) // may be re-evaluated later
- SEND_SIGNAL(src, COMSIG_ITEM_ACTIVATE_INHAND, actor)
+ var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_ACTIVATE_INHAND, actor)
+ if(signal_return & RAISE_ITEM_ACTIVATE_INHAND_HANDLED)
+ return TRUE
if(on_attack_self(actor))
return TRUE
if(interaction_flags_item & INTERACT_ITEM_ATTACK_SELF)
@@ -232,7 +234,9 @@
SHOULD_NOT_OVERRIDE(TRUE) // may be re-evaluated later
if(ismob(actor))
actor = new /datum/event_args/actor(actor)
- SEND_SIGNAL(src, COMSIG_ITEM_UNIQUE_ACTION, actor)
+ var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_UNIQUE_ACTION, actor)
+ if(signal_return & RAISE_ITEM_UNIQUE_ACTION_HANDLED)
+ return TRUE
if(on_unique_action(actor))
return TRUE
@@ -258,7 +262,9 @@
SHOULD_NOT_OVERRIDE(TRUE) // may be re-evaluated later
if(ismob(actor))
actor = new /datum/event_args/actor(actor)
- SEND_SIGNAL(src, COMSIG_ITEM_DEFENSIVE_TOGGLE, actor)
+ var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_DEFENSIVE_TOGGLE, actor)
+ if(signal_return & RAISE_ITEM_DEFENSIVE_TOGGLE_HANDLED)
+ return TRUE
if(on_defensive_toggle(actor))
return TRUE
@@ -284,7 +290,9 @@
SHOULD_NOT_OVERRIDE(TRUE) // may be re-evaluated later
if(ismob(actor))
actor = new /datum/event_args/actor(actor)
- SEND_SIGNAL(src, COMSIG_ITEM_DEFENSIVE_TRIGGER, actor)
+ var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_DEFENSIVE_TRIGGER, actor)
+ if(signal_return & RAISE_ITEM_DEFENSIVE_TRIGGER_HANDLED)
+ return TRUE
if(on_defensive_trigger(actor))
return TRUE
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 1b53f9f27386..bfb09ced177c 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -68,66 +68,6 @@
/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit/skirt,
/obj/item/clothing/glasses/sunglasses)
-/*
-/obj/structure/closet/secure_closet/hos
- name = "head of security's locker"
- req_access = list(ACCESS_SECURITY_HOS)
- icon_state = "hossecure1"
- icon_closed = "hossecure"
- icon_locked = "hossecure1"
- icon_opened = "hossecureopen"
- icon_broken = "hossecurebroken"
- icon_off = "hossecureoff"
- req_access = list(ACCESS_SECURITY_HOS)
- storage_capacity = 2.5 * MOB_MEDIUM
-
- starts_with = list(
- /obj/item/clothing/head/helmet/HoS,
- /obj/item/clothing/head/helmet/HoS/hat,
- /obj/item/clothing/suit/storage/vest/hos,
- /obj/item/clothing/under/rank/head_of_security/jensen,
- /obj/item/clothing/under/rank/head_of_security/corp,
- /obj/item/clothing/suit/storage/vest/hoscoat/jensen,
- /obj/item/clothing/suit/storage/vest/hoscoat,
- /obj/item/clothing/under/bodysuit/bodysuitseccom,
- /obj/item/clothing/head/helmet/dermal,
- /obj/item/cartridge/hos,
- /obj/item/radio/headset/heads/hos,
- /obj/item/radio/headset/heads/hos/alt,
- /obj/item/clothing/glasses/sunglasses/sechud,
- /obj/item/barrier_tape_roll/police,
- /obj/item/shield/riot,
- /obj/item/shield/transforming/telescopic,
- /obj/item/storage/box/holobadge/hos,
- /obj/item/storage/box/firingpins,
- /obj/item/clothing/accessory/badge/holo/hos,
- /obj/item/reagent_containers/spray/pepper,
- /obj/item/tool/crowbar/red,
- /obj/item/storage/box/flashbangs,
- /obj/item/storage/belt/security,
- /obj/item/flash,
- /obj/item/melee/baton/loaded,
- /obj/item/gun/magnetic/railgun/heater/pistol/hos,
- /obj/item/cell/device/weapon,
- /obj/item/clothing/accessory/holster/waist,
- /obj/item/melee/telebaton,
- /obj/item/clothing/head/beret/sec/corporate/hos,
- /obj/item/clothing/suit/storage/hooded/wintercoat/security/hos,
- /obj/item/clothing/shoes/boots/winter/security,
- /obj/item/gps/security/hos,
- /obj/item/flashlight/maglight,
- /obj/item/clothing/mask/gas/half)
-
-/obj/structure/closet/secure_closet/hos/Initialize(mapload)
- if(prob(50))
- starts_with += /obj/item/storage/backpack/security
- else
- starts_with += /obj/item/storage/backpack/satchel/sec
- if(prob(50))
- starts_with += /obj/item/storage/backpack/dufflebag/sec
- return ..()
-*/
-
//_vr file contents:
/obj/structure/closet/secure_closet/hos
name = "head of security's attire"
@@ -180,7 +120,7 @@
/obj/item/tool/crowbar/red,
/obj/item/flash,
/obj/item/melee/baton/loaded,
- /obj/item/gun/energy/gun/multiphase,
+ /obj/item/gun/energy/nt_isd/multiphase,
/obj/item/melee/telebaton,
/obj/item/storage/box/survival_knife,
/obj/item/gps/security/hos,
diff --git a/code/game/objects/systems/cell_slot.dm b/code/game/objects/systems/cell_slot.dm
index e298cb519a2b..38740bd81c90 100644
--- a/code/game/objects/systems/cell_slot.dm
+++ b/code/game/objects/systems/cell_slot.dm
@@ -199,8 +199,8 @@
* cell function wrapper - checks if the specified amount can be provided. If it can, it removes the amount from the cell and returns TRUE otherwise does nothing and returns FALSE
* returns FALSE if cell is null
*/
-/datum/object_system/cell_slot/proc/checked_use(var/amount)
- return cell?.checked_use(amount) ? TRUE : FALSE
+/datum/object_system/cell_slot/proc/checked_use(amount, reserve)
+ return cell?.checked_use(amount, reserve) ? TRUE : FALSE
/**
* cell function wrapper - use x cell units, affected by GLOB.cellefficiency, returns the amount actually used or 0 if null
diff --git a/code/game/rendering/plane_masters/plane_master.dm b/code/game/rendering/plane_masters/plane_master.dm
index 93acb24cf66d..66278e7ee4b1 100644
--- a/code/game/rendering/plane_masters/plane_master.dm
+++ b/code/game/rendering/plane_masters/plane_master.dm
@@ -94,6 +94,7 @@
/atom/movable/screen/plane_master/emissive/Initialize(mapload)
. = ..()
add_filter("em_block_masking", 1, color_matrix_filter(GLOB.em_mask_matrix))
+ #warn bloom filter
/atom/movable/screen/plane_master/lightmask
plane = LIGHTMASK_PLANE
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 80f006814d16..ccad0cb9974f 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -14,64 +14,6 @@
feedback_add_details("admin_verb","DG2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-// callproc moved to code/modules/admin/callproc
-
-/client/proc/simple_DPS()
- set name = "Simple DPS"
- set category = "Debug"
- set desc = "Gives a really basic idea of how much hurt something in-hand does."
-
- var/obj/item/I = null
- var/mob/living/user = null
- if(isliving(usr))
- user = usr
- I = user.get_active_held_item()
- if(!I || !istype(I))
- to_chat(user, "You need to have something in your active hand, to use this verb.")
- return
- var/weapon_attack_speed = user.get_attack_speed(I) / 10
- var/weapon_damage = I.damage_force
- var/modified_damage_percent = 1
-
- for(var/datum/modifier/M in user.modifiers)
- if(!isnull(M.outgoing_melee_damage_percent))
- weapon_damage *= M.outgoing_melee_damage_percent
- modified_damage_percent *= M.outgoing_melee_damage_percent
-
- if(istype(I, /obj/item/gun))
- var/obj/item/gun/G = I
- var/obj/projectile/P
-
- if(istype(I, /obj/item/gun/energy))
- var/obj/item/gun/energy/energy_gun = G
- P = new energy_gun.projectile_type()
-
- else if(istype(I, /obj/item/gun/ballistic))
- var/obj/item/gun/ballistic/projectile_gun = G
- var/obj/item/ammo_casing/ammo = projectile_gun.chambered
- P = ammo.get_projectile()
-
- else
- to_chat(user, "DPS calculation by this verb is not supported for \the [G]'s type. Energy or Ballistic only, sorry.")
-
- weapon_damage = P.damage_force
- weapon_attack_speed = G.fire_delay / 10
- qdel(P)
-
- var/DPS = weapon_damage / weapon_attack_speed
- to_chat(user, "Damage: [weapon_damage][modified_damage_percent != 1 ? " (Modified by [modified_damage_percent*100]%)":""]")
- to_chat(user, "Attack Speed: [weapon_attack_speed]/s")
- to_chat(user, "\The [I] does [DPS] damage per second.")
- if(DPS > 0)
- to_chat(user, "At your maximum health ([user.getMaxHealth()]), it would take approximately;")
- to_chat(user, "[(user.getMaxHealth() - config_legacy.health_threshold_softcrit) / DPS] seconds to softcrit you. ([config_legacy.health_threshold_softcrit] health)")
- to_chat(user, "[(user.getMaxHealth() - config_legacy.health_threshold_crit) / DPS] seconds to hardcrit you. ([config_legacy.health_threshold_crit] health)")
- to_chat(user, "[(user.getMaxHealth() - config_legacy.health_threshold_dead) / DPS] seconds to kill you. ([config_legacy.health_threshold_dead] health)")
-
- else
- to_chat(user, "You need to be a living mob, with hands, and for an object to be in your active hand, to use this verb.")
- return
-
/client/proc/Cell()
set category = "Debug"
set name = "Cell"
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index f1c6e2033a18..78d84f601e8d 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -366,7 +366,7 @@
projectile_type = /obj/projectile/chameleon
charge_meter = 0
charge_cost = 48 //uses next to no power, since it's just holograms
- battery_lock = 1
+ legacy_battery_lock = 1
var/obj/projectile/copy_projectile
var/global/list/gun_choices
@@ -380,9 +380,9 @@
var/obj/item/gun/G = gun_type
src.gun_choices[initial(G.name)] = gun_type
-/obj/item/gun/energy/chameleon/consume_next_projectile()
+/obj/item/gun/energy/chameleon/consume_next_projectile(datum/gun_firing_cycle/cycle)
var/obj/projectile/P = ..()
- if(P && ispath(copy_projectile))
+ if(istype(P) && ispath(copy_projectile))
P.name = initial(copy_projectile.name)
P.icon = initial(copy_projectile.icon)
P.icon_state = initial(copy_projectile.icon_state)
diff --git a/code/modules/examine/descriptions/weapons.dm b/code/modules/examine/descriptions/weapons.dm
index 1120bdcfb6ce..ccbccab48de1 100644
--- a/code/modules/examine/descriptions/weapons.dm
+++ b/code/modules/examine/descriptions/weapons.dm
@@ -14,19 +14,6 @@
description_antag = "This is a stealthy weapon which fires poisoned bolts at your target. When it hits someone, they will suffer a stun effect, in \
addition to toxins. The energy crossbow recharges itself slowly, and can be concealed in your pocket or bag."
-/obj/item/gun/energy/gun
- description_info = "This is an energy weapon. To fire the weapon, ensure your intent is *not* set to 'help', have your gun mode set to 'fire', \
- then click where you want to fire. Most energy weapons can fire through windows harmlessly. To switch between stun and lethal, click the weapon \
- in your hand. To recharge this weapon, use a weapon recharger."
-
-/obj/item/gun/energy/gun/taser
- description_info = "This is an energy weapon. To fire the weapon, ensure your intent is *not* set to 'help', have your gun mode set to 'fire', \
- then click where you want to fire. Most energy weapons can fire through windows harmlessly. To recharge this weapon, use a weapon recharger."
-
-/obj/item/gun/energy/gun/stunrevolver
- description_info = "This is an energy weapon. To fire the weapon, ensure your intent is *not* set to 'help', have your gun mode set to 'fire', \
- then click where you want to fire. Most energy weapons can fire through windows harmlessly. To recharge this weapon, use a weapon recharger."
-
/obj/item/gun/energy/gun/nuclear
description_info = "This is an energy weapon. To fire the weapon, ensure your intent is *not* set to 'help', have your gun mode set to 'fire', \
then click where you want to fire. Most energy weapons can fire through windows harmlessly. To switch between stun and lethal, click the weapon \
diff --git a/code/modules/examine/examine.dm b/code/modules/examine/examine.dm
index 72868544f8b7..fb31d33a324d 100644
--- a/code/modules/examine/examine.dm
+++ b/code/modules/examine/examine.dm
@@ -8,6 +8,8 @@
#define EXAMINE_PANEL_PADDING " "
/atom/
+ // todo: this is ass, we need a better help system.
+ // a combination system of screentips and examines, maybe?
var/description_info = null //Helpful blue text.
/**
@@ -26,14 +28,12 @@
* * This is appended at the end of [description_fluff]. Useful for things like "this is part of a group of similar blah blah blah's".
*/
var/description_fluff_categorizer
-
+ // todo: this is ass, find out a better way to give info via skills system and not special roles
var/description_antag = null //Malicious red text, for the antags.
//Override these if you need special behaviour for a specific type.
/atom/proc/get_description_info()
- if(description_info)
- return description_info
- return
+ return description_info
/atom/proc/get_description_fluff()
. = description_fluff
@@ -45,9 +45,7 @@
. = description_fluff_categorizer
/atom/proc/get_description_antag()
- if(description_antag)
- return description_antag
- return
+ return description_antag
// This one is slightly different, in that it must return a list.
/atom/proc/get_description_interaction(mob/user)
diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm
index 1853251f7c19..059eb5c857c7 100644
--- a/code/modules/integrated_electronics/subtypes/manipulation.dm
+++ b/code/modules/integrated_electronics/subtypes/manipulation.dm
@@ -916,8 +916,7 @@
if(!T)
return
- installed_gun.Fire_userless(T)
-
+ installed_gun.start_firing_cycle_async(assembly, get_centered_entity_angle(assembly, T))
/obj/item/integrated_circuit/manipulation/grenade
name = "grenade primer"
diff --git a/code/modules/mining/tools/kinetic_accelerator.dm b/code/modules/mining/tools/kinetic_accelerator.dm
index cfcb7ae64758..afc196f32422 100644
--- a/code/modules/mining/tools/kinetic_accelerator.dm
+++ b/code/modules/mining/tools/kinetic_accelerator.dm
@@ -26,7 +26,7 @@
projectile_type = /obj/projectile/kinetic
charge_cost = 1200
- battery_lock = TRUE
+ legacy_battery_lock = TRUE
fire_sound = 'sound/weapons/kenetic_accel.ogg'
render_use_legacy_by_default = FALSE
var/overheat_time = 16
@@ -40,15 +40,15 @@
var/recharge_timerid
-/obj/item/gun/energy/kinetic_accelerator/consume_next_projectile()
+/obj/item/gun/energy/kinetic_accelerator/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(overheat)
- return
+ return GUN_FIRED_FAIL_EMPTY
. = ..()
if(.)
var/obj/projectile/P = .
modify_projectile(P)
-/obj/item/gun/energy/kinetic_accelerator/handle_post_fire(mob/user, atom/target, pointblank, reflex)
+/obj/item/gun/energy/kinetic_accelerator/on_firing_cycle_end(datum/gun_firing_cycle/cycle)
. = ..()
attempt_reload()
@@ -140,7 +140,7 @@
/obj/item/gun/energy/kinetic_accelerator/equipped(mob/user, slot, flags)
. = ..()
- if(power_supply.charge < charge_cost)
+ if(obj_cell_slot.cell.charge < charge_cost)
attempt_reload()
/obj/item/gun/energy/kinetic_accelerator/dropped(mob/user, flags, atom/newLoc)
@@ -155,12 +155,12 @@
empty()
/obj/item/gun/energy/kinetic_accelerator/proc/empty()
- if(power_supply)
- power_supply.use(power_supply.charge)
+ if(obj_cell_slot.cell)
+ obj_cell_slot.cell.use(obj_cell_slot.cell.charge)
update_icon()
/obj/item/gun/energy/kinetic_accelerator/proc/attempt_reload(recharge_time)
- if(!power_supply)
+ if(!obj_cell_slot.cell)
return
if(overheat)
return
@@ -178,7 +178,7 @@
return
/obj/item/gun/energy/kinetic_accelerator/proc/reload()
- power_supply.give(power_supply.maxcharge)
+ obj_cell_slot.cell.give(obj_cell_slot.cell.maxcharge)
// process_chamber()
// if(!suppressed)
playsound(src, 'sound/weapons/kenetic_reload.ogg', 60, 1)
@@ -189,7 +189,7 @@
/obj/item/gun/energy/kinetic_accelerator/update_overlays()
. = ..()
- if(overheat || (power_supply.charge == 0))
+ if(overheat || (obj_cell_slot.cell.charge == 0))
. += emptystate
//Projectiles
diff --git a/code/modules/mob/living/bot/ed209bot.dm b/code/modules/mob/living/bot/ed209bot.dm
index afd256631f05..96ea2fa21c29 100644
--- a/code/modules/mob/living/bot/ed209bot.dm
+++ b/code/modules/mob/living/bot/ed209bot.dm
@@ -44,7 +44,7 @@
new /obj/item/secbot_assembly/ed209_assembly(Tsec)
var/obj/item/gun/energy/taser/G = new used_weapon(Tsec)
- G.power_supply.charge = 0
+ G.obj_cell_slot.cell.set_charge(0)
if(prob(50))
new /obj/item/robot_parts/l_leg(Tsec)
if(prob(50))
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm
index 275de5796971..2453f9831977 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm
@@ -58,8 +58,8 @@
else if(F.times_used)
F.times_used--
var/obj/item/gun/energy/taser/mounted/cyborg/T = locate() in src.modules
- if(T.power_supply.charge < T.power_supply.maxcharge)
- T.power_supply.give(T.charge_cost * amount)
+ if(T.obj_cell_slot.cell.charge < T.obj_cell_slot.cell.maxcharge)
+ T.obj_cell_slot.cell.give(T.charge_cost * amount)
T.update_icon()
else
T.charge_tick = 0
@@ -137,8 +137,8 @@
else if(F.times_used)
F.times_used--
var/obj/item/gun/energy/taser/mounted/cyborg/T = locate() in src.modules
- if(T.power_supply.charge < T.power_supply.maxcharge)
- T.power_supply.give(T.charge_cost * amount)
+ if(T.obj_cell_slot.cell.charge < T.obj_cell_slot.cell.maxcharge)
+ T.obj_cell_slot.cell.give(T.charge_cost * amount)
T.update_icon()
else
T.charge_tick = 0
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index 9757c4173c31..06b60f18d115 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -128,8 +128,8 @@
// Checks if the specified amount can be provided. If it can, it removes the amount
// from the cell and returns 1. Otherwise does nothing and returns 0.
-/obj/item/cell/proc/checked_use(var/amount)
- if(!check_charge(amount))
+/obj/item/cell/proc/checked_use(amount, reserve)
+ if(!check_charge(amount + reserve))
return 0
use(amount)
return 1
@@ -274,3 +274,10 @@
var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
user.visible_message("\The [user] is licking the electrodes of \the [src]! It looks like [TU.he] [TU.is] trying to commit suicide.")
return (FIRELOSS)
+
+//* Setters *//
+
+/obj/item/cell/proc/set_charge(amount, update)
+ charge = clamp(amount, 0, maxcharge)
+ if(update)
+ update_icon()
diff --git a/code/modules/projectiles/ammunition/README.md b/code/modules/projectiles/ammunition/README.md
new file mode 100644
index 000000000000..832c30ffdbe2
--- /dev/null
+++ b/code/modules/projectiles/ammunition/README.md
@@ -0,0 +1,4 @@
+# Ammunition
+
+Ammo system, including calibers, casings, and magazines are here.
+
diff --git a/code/modules/projectiles/ammunition/ammo_handful.dm b/code/modules/projectiles/ammunition/ammo_handful.dm
new file mode 100644
index 000000000000..9f060b4aa25c
--- /dev/null
+++ b/code/modules/projectiles/ammunition/ammo_handful.dm
@@ -0,0 +1,18 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * TODO: Marker file.
+ *
+ * We will eventually want an /obj/item/ammo_handful type to achieve feature
+ * parity with combat servers like Colonial Marines.
+ *
+ * This is to make moving rounds around a bit easier.
+ * We will potentially lose the behavior of being able to move single rounds.
+ *
+ * There is, however, a way to get around this.
+ * We can have individual casings be dispensed on something like alt-click,
+ * so if you really want you can still do mix-and-match style;
+ * we do not have enough casings laying around in game that it's a major performance concern
+ * to allow people to just take them all out now and then.
+ */
diff --git a/code/modules/projectiles/ammunition/calibers/special/microbattery.dm b/code/modules/projectiles/ammunition/calibers/special/microbattery.dm
index 894ffcd4c0d6..4d1a6684e058 100644
--- a/code/modules/projectiles/ammunition/calibers/special/microbattery.dm
+++ b/code/modules/projectiles/ammunition/calibers/special/microbattery.dm
@@ -1,4 +1,4 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 silicons *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/datum/ammo_caliber/microbattery
diff --git a/code/modules/projectiles/effects.dm b/code/modules/projectiles/effects.dm
deleted file mode 100644
index 8ecd0c6ae682..000000000000
--- a/code/modules/projectiles/effects.dm
+++ /dev/null
@@ -1,288 +0,0 @@
-/obj/effect/projectile
- icon = 'icons/effects/projectiles.dmi'
- icon_state = "bolt"
- plane = ABOVE_PLANE
- mouse_opacity = 0
-
-/obj/effect/projectile/proc/set_transform(var/matrix/M)
- if(istype(M))
- transform = M
-
-/obj/effect/projectile/proc/activate(var/kill_delay = 5)
- update_light()
- spawn(kill_delay)
- qdel(src) //see effect_system.dm - sets loc to null and lets GC handle removing these effects
-
- return
-
-//----------------------------
-// Laser beam
-//----------------------------
-/obj/effect/projectile/laser/tracer
- icon_state = "beam"
- light_range = 2
- light_power = 0.5
- light_color = "#FF0D00"
-
-/obj/effect/projectile/laser/muzzle
- icon_state = "muzzle_laser"
- light_range = 2
- light_power = 0.5
- light_color = "#FF0D00"
-
-/obj/effect/projectile/laser/impact
- icon_state = "impact_laser"
- light_range = 2
- light_power = 0.5
- light_color = "#FF0D00"
-
-//----------------------------
-// Blue laser beam
-//----------------------------
-/obj/effect/projectile/laser_blue/tracer
- icon_state = "beam_blue"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-/obj/effect/projectile/laser_blue/muzzle
- icon_state = "muzzle_blue"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-/obj/effect/projectile/laser_blue/impact
- icon_state = "impact_blue"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-//----------------------------
-// Omni laser beam
-//----------------------------
-/obj/effect/projectile/laser_omni/tracer
- icon_state = "beam_omni"
- light_range = 2
- light_power = 0.5
- light_color = "#00C6FF"
-
-/obj/effect/projectile/laser_omni/muzzle
- icon_state = "muzzle_omni"
- light_range = 2
- light_power = 0.5
- light_color = "#00C6FF"
-
-/obj/effect/projectile/laser_omni/impact
- icon_state = "impact_omni"
- light_range = 2
- light_power = 0.5
- light_color = "#00C6FF"
-
-//----------------------------
-// Xray laser beam
-//----------------------------
-/obj/effect/projectile/xray/tracer
- icon_state = "xray"
- light_range = 2
- light_power = 0.5
- light_color = "#00CC33"
-
-/obj/effect/projectile/xray/muzzle
- icon_state = "muzzle_xray"
- light_range = 2
- light_power = 0.5
- light_color = "#00CC33"
-
-/obj/effect/projectile/xray/impact
- icon_state = "impact_xray"
- light_range = 2
- light_power = 0.5
- light_color = "#00CC33"
-
-//----------------------------
-// Heavy laser beam
-//----------------------------
-/obj/effect/projectile/laser_heavy/tracer
- icon_state = "beam_heavy"
- light_range = 3
- light_power = 1
- light_color = "#FF0D00"
-
-/obj/effect/projectile/laser_heavy/muzzle
- icon_state = "muzzle_beam_heavy"
- light_range = 3
- light_power = 1
- light_color = "#FF0D00"
-
-/obj/effect/projectile/laser_heavy/impact
- icon_state = "impact_beam_heavy"
- light_range = 3
- light_power = 1
- light_color = "#FF0D00"
-
-//----------------------------
-// Pulse laser beam
-//----------------------------
-/obj/effect/projectile/laser_pulse/tracer
- icon_state = "u_laser"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-/obj/effect/projectile/laser_pulse/muzzle
- icon_state = "muzzle_u_laser"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-/obj/effect/projectile/laser_pulse/impact
- icon_state = "impact_u_laser"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-//----------------------------
-// Pulse muzzle effect only
-//----------------------------
-/obj/effect/projectile/pulse/muzzle
- icon_state = "muzzle_pulse"
- light_range = 2
- light_power = 0.5
- light_color = "#0066FF"
-
-//----------------------------
-// Emitter beam
-//----------------------------
-/obj/effect/projectile/emitter/tracer
- icon_state = "emitter"
- light_range = 2
- light_power = 0.5
- light_color = "#00CC33"
-
-/obj/effect/projectile/emitter/muzzle
- icon_state = "muzzle_emitter"
- light_range = 2
- light_power = 0.5
- light_color = "#00CC33"
-
-/obj/effect/projectile/emitter/impact
- icon_state = "impact_emitter"
- light_range = 2
- light_power = 0.5
- light_color = "#00CC33"
-
-//----------------------------
-// Stun beam
-//----------------------------
-/obj/effect/projectile/stun/tracer
- icon_state = "stun"
- light_range = 2
- light_power = 0.5
- light_color = "#FFFFFF"
-
-/obj/effect/projectile/stun/muzzle
- icon_state = "muzzle_stun"
- light_range = 2
- light_power = 0.5
- light_color = "#FFFFFF"
-
-/obj/effect/projectile/stun/impact
- icon_state = "impact_stun"
- light_range = 2
- light_power = 0.5
- light_color = "#FFFFFF"
-
-//----------------------------
-// Bullet
-//----------------------------
-/obj/effect/projectile/bullet/muzzle
- icon_state = "muzzle_bullet"
- light_range = 2
- light_power = 0.5
- light_color = "#FFFFFF"
-
-//----------------------------
-// Lightning beam
-//----------------------------
-/obj/effect/projectile/lightning/tracer
- icon_state = "lightning"
- light_range = 2
- light_power = 0.5
- light_color = "#00C6FF"
-
-/obj/effect/projectile/lightning/muzzle
- icon_state = "muzzle_lightning"
- light_range = 2
- light_power = 0.5
- light_color = "#00C6FF"
-
-/obj/effect/projectile/lightning/impact
- icon_state = "impact_lightning"
- light_range = 2
- light_power = 0.5
- light_color = "#00C6FF"
-
-//----------------------------
-// Dark matter stun
-//----------------------------
-
-/obj/effect/projectile/darkmatterstun/tracer
- icon_state = "darkt"
- light_range = 2
- light_power = 0.5
- light_color = "#8837A3"
-
-/obj/effect/projectile/darkmatterstun/muzzle
- icon_state = "muzzle_darkt"
- light_range = 2
- light_power = 0.5
- light_color = "#8837A3"
-
-/obj/effect/projectile/darkmatterstun/impact
- icon_state = "impact_darkt"
- light_range = 2
- light_power = 0.5
- light_color = "#8837A3"
-
-//----------------------------
-// Dark matter
-//----------------------------
-
-/obj/effect/projectile/darkmatter/tracer
- icon_state = "darkb"
- light_range = 2
- light_power = 0.5
- light_color = "#8837A3"
-
-/obj/effect/projectile/darkmatter/muzzle
- icon_state = "muzzle_darkb"
- light_range = 2
- light_power = 0.5
- light_color = "#8837A3"
-
-/obj/effect/projectile/darkmatter/impact
- icon_state = "impact_darkb"
- light_range = 2
- light_power = 0.5
- light_color = "#8837A3"
-
-//----------------------------
-// Inversion / Cult
-//----------------------------
-/obj/effect/projectile/inversion/tracer
- icon_state = "invert"
- light_range = 2
- light_power = -2
- light_color = "#FFFFFF"
-
-/obj/effect/projectile/inversion/muzzle
- icon_state = "muzzle_invert"
- light_range = 2
- light_power = -2
- light_color = "#FFFFFF"
-
-/obj/effect/projectile/inversion/impact
- icon_state = "impact_invert"
- light_range = 2
- light_power = -2
- light_color = "#FFFFFF"
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 8ba4989426f2..c171264ef0d8 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -1,3 +1,11 @@
+/**
+ * Ballistic Guns
+ *
+ * These are guns that fire primarily ammo casings.
+ *
+ * They have simulation / support for both direct-load / internal magazines, as well as
+ * attached / inserted external magazines.
+ */
/obj/item/gun/ballistic
name = "gun"
desc = "A gun that fires bullets."
@@ -95,7 +103,8 @@
if(magazine_type)
icon_state = "[silenced_state][magazine_state]"
-/obj/item/gun/ballistic/consume_next_projectile()
+// todo: rework
+/obj/item/gun/ballistic/consume_next_projectile(datum/gun_firing_cycle/cycle)
//get the next casing
if(loaded.len)
chambered = loaded[1] //load next casing.
@@ -105,19 +114,17 @@
chambered = ammo_magazine.pop(src)
if (chambered)
- return chambered.get_projectile()
+ return chambered.expend()
return null
-/obj/item/gun/ballistic/handle_post_fire()
- ..()
- if(chambered)
- chambered.expend()
- process_chambered()
-
-/obj/item/gun/ballistic/handle_click_empty()
- ..()
- process_chambered()
+/obj/item/gun/ballistic/post_fire(atom/firer, angle, firing_flags, datum/firemode/firemode, iteration, firing_result, atom/target, datum/event_args/actor/actor)
+ . = ..()
+ switch(firing_result)
+ // process chamber
+ if(GUN_FIRED_FAIL_INERT, GUN_FIRED_SUCCESS, GUN_FIRED_FAIL_EMPTY)
+ process_chambered()
+// todo: refactor
/obj/item/gun/ballistic/proc/process_chambered()
if (!chambered) return
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
similarity index 97%
rename from code/modules/projectiles/guns/projectile/automatic.dm
rename to code/modules/projectiles/guns/ballistic/automatic.dm
index 4cdf03148e99..caa5d45931bf 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -123,6 +123,23 @@
/obj/item/gun/ballistic/automatic/wt550/lethal
magazine_type = /obj/item/ammo_magazine/a9mm/top_mount
+/datum/firemode/z8_bulldog
+ burst_delay = 2
+
+/datum/firemode/z8_bulldog/one
+ name = "semiauto"
+ burst_amount = 1
+ legacy_direct_varedits = list(use_launcher=null, burst_accuracy=null, dispersion=null)
+
+/datum/firemode/z8_bulldog/two
+ name = "2-round bursts"
+ burst_amount = 2
+ legacy_direct_varedits = list(use_launcher=null, burst_accuracy=list(60,45), dispersion=list(0.0, 0.6))
+
+/datum/firemode/z8_bulldog/grenade
+ name = "fire grenades"
+ legacy_direct_varedits = list(use_launcher=1, burst_accuracy=null, dispersion=null)
+
/obj/item/gun/ballistic/automatic/z8
name = "designated marksman rifle"
desc = "The Z8 Bulldog is an older model designated marksman rifle, made by the now defunct Zendai Foundries. Makes you feel like a space marine when you hold it, even though it can only hold 10 round magazines. Uses 7.62mm rounds and has an under barrel grenade launcher."
@@ -146,12 +163,11 @@
one_handed_penalty = 60
worth_intrinsic = 650 // milrp time
- burst_delay = 4
firemodes = list(
- list(mode_name="semiauto", burst=1, fire_delay=0, move_delay=null, use_launcher=null, burst_accuracy=null, dispersion=null),
- list(mode_name="2-round bursts", burst=2, fire_delay=null, move_delay=6, use_launcher=null, burst_accuracy=list(60,45), dispersion=list(0.0, 0.6)),
- list(mode_name="fire grenades", burst=null, fire_delay=null, move_delay=null, use_launcher=1, burst_accuracy=null, dispersion=null)
- )
+ /datum/firemode/z8_bulldog/one,
+ /datum/firemode/z8_bulldog/two,
+ /datum/firemode/z8_bulldog/grenade,
+ )
var/use_launcher = 0
var/obj/item/gun/launcher/grenade/underslung/launcher
@@ -172,13 +188,13 @@
else
..()
-/obj/item/gun/ballistic/automatic/z8/Fire(atom/target, mob/living/user, params, pointblank=0, reflex=0)
+/obj/item/gun/ballistic/automatic/z8/fire(datum/gun_firing_cycle/cycle)
if(use_launcher)
- launcher.Fire(target, user, params, pointblank, reflex)
+ launcher.fire(cycle)
if(!launcher.chambered)
- switch_firemodes(user) //switch back automatically
- else
- ..()
+ switch_firemodes(cycle.firing_actor?.performer) //switch back automatically
+ return GUN_FIRED_SUCCESS
+ return ..()
/obj/item/gun/ballistic/automatic/z8/update_icon_state()
. = ..()
@@ -543,15 +559,16 @@
load_method = SPEEDLOADER
ammo_type = /obj/item/ammo_casing/a7_62mm
max_shells = 15
- burst = 3
- fire_delay = 7.2
- move_delay = 6
+ firemodes = /datum/firemode{
+ burst_amount = 3;
+ burst_delay = 0.25 SECONDS;
+ cycle_cooldown = 0.72 SECONDS;
+ }
burst_accuracy = list(60,30,15)
dispersion = list(0.0, 0.6,1.0)
/obj/item/gun/ballistic/automatic/automat/holy
ammo_type = /obj/item/ammo_casing/a7_62mm/silver
- holy = TRUE
/obj/item/gun/ballistic/automatic/automat/taj
name = "Adhomai automat"
diff --git a/code/modules/projectiles/guns/projectile/boltaction.dm b/code/modules/projectiles/guns/ballistic/boltaction.dm
similarity index 99%
rename from code/modules/projectiles/guns/projectile/boltaction.dm
rename to code/modules/projectiles/guns/ballistic/boltaction.dm
index 1106d7ea4a3b..49121d84db88 100644
--- a/code/modules/projectiles/guns/projectile/boltaction.dm
+++ b/code/modules/projectiles/guns/ballistic/boltaction.dm
@@ -30,7 +30,6 @@
name = "blessed bolt-action rifle"
desc = "A bolt-action rifle with a heavy, high-quality wood stock that has a beautiful finish. Clearly not intended to be used in combat. Uses 7.62mm rounds."
ammo_type = /obj/item/ammo_casing/a7_62mm/silver
- holy = TRUE
/obj/item/gun/ballistic/shotgun/pump/rifle/taj
name = "Adhomai bolt action rifle"
@@ -87,7 +86,6 @@
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/holy
name = "blessed lever-action"
ammo_type = /obj/item/ammo_casing/a357/silver
- holy = TRUE
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/attackby(var/obj/item/A as obj, mob/user as mob)
if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL)
@@ -131,7 +129,6 @@
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/vintage/holy
name = "blessed lever-action"
ammo_type = /obj/item/ammo_casing/a44/silver
- holy = TRUE
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/vintage/attackby(var/obj/item/A as obj, mob/user as mob)
if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL)
@@ -175,7 +172,6 @@
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/arnold/holy
name = "blessed lever-action shotgun"
ammo_type = /obj/item/ammo_casing/a12g/silver
- holy = TRUE
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/win1895
name = "Winchester 1895"
@@ -193,7 +189,6 @@
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/win1895/holy
name = "blessed lever-action"
ammo_type = /obj/item/ammo_casing/a7_62mm/silver
- holy = TRUE
/obj/item/gun/ballistic/shotgun/pump/scopedrifle
name = "scoped bolt action"
diff --git a/code/modules/projectiles/guns/projectile/bow.dm b/code/modules/projectiles/guns/ballistic/bow.dm
similarity index 100%
rename from code/modules/projectiles/guns/projectile/bow.dm
rename to code/modules/projectiles/guns/ballistic/bow.dm
diff --git a/code/modules/projectiles/guns/projectile/caseless.dm b/code/modules/projectiles/guns/ballistic/caseless.dm
similarity index 100%
rename from code/modules/projectiles/guns/projectile/caseless.dm
rename to code/modules/projectiles/guns/ballistic/caseless.dm
diff --git a/code/modules/projectiles/guns/projectile/caseless/pellet.dm b/code/modules/projectiles/guns/ballistic/caseless/pellet.dm
similarity index 100%
rename from code/modules/projectiles/guns/projectile/caseless/pellet.dm
rename to code/modules/projectiles/guns/ballistic/caseless/pellet.dm
diff --git a/code/modules/projectiles/guns/projectile/contender.dm b/code/modules/projectiles/guns/ballistic/contender.dm
similarity index 90%
rename from code/modules/projectiles/guns/projectile/contender.dm
rename to code/modules/projectiles/guns/ballistic/contender.dm
index c18941c1f455..992245916661 100644
--- a/code/modules/projectiles/guns/projectile/contender.dm
+++ b/code/modules/projectiles/guns/ballistic/contender.dm
@@ -69,7 +69,6 @@
icon_retracted = "pockrifle_c-empty"
ammo_type = /obj/item/ammo_casing/a357/silver
origin_tech = list(TECH_COMBAT = 2, TECH_MATERIAL = 2, TECH_OCCULT = 1)
- holy = TRUE
/obj/item/gun/ballistic/contender/holy/a44
caliber = /datum/ammo_caliber/a44
@@ -104,13 +103,12 @@
projectile_type = /obj/projectile/bullet/shotgun
unstable = 1
-/obj/item/gun/ballistic/contender/pipegun/consume_next_projectile(mob/user as mob)
+/obj/item/gun/ballistic/contender/pipegun/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
- //var/instability = rand(1,100)
if(.)
if(unstable)
if(prob(10))
- to_chat(user, "The pipe bursts open as the gun backfires!")
+ visible_message("The pipe bursts open on [src] as the gun backfires!")
name = "ruptured pipe rifle"
desc = "The barrel has blown wide open."
icon_state = "pipegun-destroyed"
@@ -119,15 +117,7 @@
explosion(get_turf(src), -1, 0, 2, 3)
if(destroyed)
- to_chat(user, "The [src] is broken!")
- handle_click_empty()
- return
-
-/obj/item/gun/ballistic/contender/pipegun/Fire(atom/target, mob/living/user, clickparams, pointblank, reflex)
- . = ..()
- if(destroyed)
- to_chat(user, "\The [src] is completely inoperable!")
- handle_click_empty()
+ return GUN_FIRED_FAIL_INERT
/obj/item/gun/ballistic/contender/pipegun/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(user.get_inactive_held_item() == src && destroyed)
diff --git a/code/modules/projectiles/guns/projectile/dartgun.dm b/code/modules/projectiles/guns/ballistic/dartgun.dm
similarity index 98%
rename from code/modules/projectiles/guns/projectile/dartgun.dm
rename to code/modules/projectiles/guns/ballistic/dartgun.dm
index 23c87c79e17a..6b288b2cdf6a 100644
--- a/code/modules/projectiles/guns/projectile/dartgun.dm
+++ b/code/modules/projectiles/guns/ballistic/dartgun.dm
@@ -52,7 +52,7 @@
else
icon_state = "[base_state]"
-/obj/item/gun/ballistic/dartgun/consume_next_projectile()
+/obj/item/gun/ballistic/dartgun/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
var/obj/projectile/bullet/chemdart/dart = .
if(istype(dart))
diff --git a/code/modules/projectiles/guns/ballistic/magnetic.dm b/code/modules/projectiles/guns/ballistic/magnetic.dm
new file mode 100644
index 000000000000..e5d8a4501729
--- /dev/null
+++ b/code/modules/projectiles/guns/ballistic/magnetic.dm
@@ -0,0 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/gun/ballistic/magnetic
+ cell_system = TRUE
+ cell_system_legacy_use_device = TRUE
+ cell_type = /obj/item/cell/device/weapon
+
+ /// base power draw per shot
+ ///
+ /// * in kilojoules
+ var/base_shot_power = (/obj/item/cell/device/weapon::maxcharge * 0.5) / INFINITY
+
+#warn impl all
diff --git a/code/modules/projectiles/guns/ballistic/microbattery/microbattery.dm b/code/modules/projectiles/guns/ballistic/microbattery/microbattery.dm
index 10bdd2c4d12c..9d2c3afee138 100644
--- a/code/modules/projectiles/guns/ballistic/microbattery/microbattery.dm
+++ b/code/modules/projectiles/guns/ballistic/microbattery/microbattery.dm
@@ -26,7 +26,7 @@
var/max_charge = 0
charge_sections = 5
-/obj/item/gun/ballistic/microbattery/consume_next_projectile()
+/obj/item/gun/ballistic/microbattery/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(chambered && ammo_magazine)
var/obj/item/ammo_casing/microbattery/batt = chambered
if(batt.shots_left)
diff --git a/code/modules/projectiles/guns/projectile/musket.dm b/code/modules/projectiles/guns/ballistic/musket.dm
similarity index 98%
rename from code/modules/projectiles/guns/projectile/musket.dm
rename to code/modules/projectiles/guns/ballistic/musket.dm
index f4e148d2b964..e0a49a23d252 100644
--- a/code/modules/projectiles/guns/projectile/musket.dm
+++ b/code/modules/projectiles/guns/ballistic/musket.dm
@@ -19,7 +19,9 @@
origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 2)
- fire_delay = 35
+ firemodes = /datum/firemode{
+ cycle_cooldown = 3.5 SECONDS;
+ }
fire_sound = 'sound/weapons/gunshot/musket.ogg'
recoil = 4
no_pin_required = 1
diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
similarity index 96%
rename from code/modules/projectiles/guns/projectile/pistol.dm
rename to code/modules/projectiles/guns/ballistic/pistol.dm
index b1ec8d209fc3..e639e1b860e5 100644
--- a/code/modules/projectiles/guns/projectile/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -108,7 +108,6 @@
w_class = WEIGHT_CLASS_NORMAL
caliber = /datum/ammo_caliber/a45
silenced = 1
- fire_delay = 1
recoil = 0
origin_tech = list(TECH_COMBAT = 2, TECH_MATERIAL = 2, TECH_ILLEGAL = 8)
load_method = MAGAZINE
@@ -249,12 +248,13 @@
caliber = initial(ammo.caliber)
return ..()
-/obj/item/gun/ballistic/pirate/consume_next_projectile(mob/user as mob)
+// todo: dumb
+/obj/item/gun/ballistic/pirate/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
if(.)
if(unstable)
if(prob(10))
- to_chat(user, "The barrel bursts open as the gun backfires!")
+ visible_message("The barrel bursts open on [src] as the gun backfires!")
name = "destroyed zip gun"
desc = "The barrel has burst. It seems inoperable."
icon_state = "[initial(icon_state)]-destroyed"
@@ -263,16 +263,8 @@
explosion(get_turf(src), -1, 0, 2, 3)
if(destroyed)
- to_chat(user, "The [src] is broken!")
- handle_click_empty()
return
-/obj/item/gun/ballistic/pirate/Fire(atom/target, mob/living/user, clickparams, pointblank, reflex)
- . = ..()
- if(destroyed)
- to_chat(user, "\The [src] is completely inoperable!")
- handle_click_empty()
-
/obj/item/gun/ballistic/pirate/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(user.get_inactive_held_item() == src && destroyed)
to_chat(user, "\The [src]'s chamber is too warped to extract the casing!")
@@ -352,7 +344,6 @@
name = "Blessed Red 9"
desc = "Ah, the choice of an avid gun collector! It's a nice gun, stranger."
ammo_type = /obj/item/ammo_casing/a9mm/silver
- holy = TRUE
/obj/item/gun/ballistic/clown_pistol
name = "clown pistol"
@@ -409,13 +400,10 @@
else
..()
-/obj/item/gun/ballistic/konigin/Fire(atom/target, mob/living/user, params, pointblank=0, reflex=0)
+/obj/item/gun/ballistic/konigin/fire(datum/gun_firing_cycle/cycle)
if(use_shotgun)
- shotgun.Fire(target, user, params, pointblank, reflex)
- //if(!shotgun.chambered)
- //switch_firemodes(user) //switch back automatically
- else
- ..()
+ return shotgun.fire(cycle)
+ return ..()
/* Having issues with getting this to work atm.
/obj/item/gun/ballistic/konigin/examine(mob/user, dist)
diff --git a/code/modules/projectiles/guns/projectile/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
similarity index 98%
rename from code/modules/projectiles/guns/projectile/revolver.dm
rename to code/modules/projectiles/guns/ballistic/revolver.dm
index 73c2d28eb987..217080e1c7b1 100644
--- a/code/modules/projectiles/guns/projectile/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -30,7 +30,8 @@
if(rand(1,max_shells) > loaded.len)
chamber_offset = rand(0,max_shells - loaded.len)
-/obj/item/gun/ballistic/revolver/consume_next_projectile()
+// todo: dumb
+/obj/item/gun/ballistic/revolver/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(chamber_offset)
chamber_offset--
return
@@ -303,7 +304,9 @@
name = "autorevolver"
icon_state = "mosley"
desc = "A shiny Mosley Autococker automatic revolver, with black accents. Marketed as the 'Revolver for the Modern Era'. Uses .44 magnum rounds."
- fire_delay = 5.7 //Autorevolver. Also synced with the animation
+ firemodes = /datum/firemode{
+ cycle_cooldown = 0.5 SECONDS;
+ }
fire_anim = "mosley_fire"
origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 2)
@@ -329,7 +332,9 @@
desc = "The NT-R-7 'Ogre' combat revolver is tooled for Nanotrasen special operations. Chambered in .44 Magnum with an advanced high-speed firing mechanism, it serves as the perfect sidearm for any off the books endeavor."
icon_state = "combatrevolver"
caliber = /datum/ammo_caliber/a44
- fire_delay = 5.7
+ firemodes = /datum/firemode{
+ cycle_cooldown = 0.5 SECONDS;
+ }
origin_tech = list(TECH_COMBAT = 5, TECH_MATERIAL = 3)
ammo_type = /obj/item/ammo_casing/a44
diff --git a/code/modules/projectiles/guns/projectile/rocket.dm b/code/modules/projectiles/guns/ballistic/rocket.dm
similarity index 89%
rename from code/modules/projectiles/guns/projectile/rocket.dm
rename to code/modules/projectiles/guns/ballistic/rocket.dm
index 382bf99d4ac4..d4dcfa4e1642 100644
--- a/code/modules/projectiles/guns/projectile/rocket.dm
+++ b/code/modules/projectiles/guns/ballistic/rocket.dm
@@ -51,7 +51,7 @@
else
..()
-/obj/item/gun/launcher/rocket/consume_next_projectile()
+/obj/item/gun/launcher/rocket/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(rockets.len)
var/obj/item/ammo_casing/rocket/I = rockets[1]
rockets -= I
@@ -59,10 +59,10 @@
return null
*/
-/obj/item/gun/ballistic/rocket/handle_post_fire(mob/user, atom/target)
- message_admins("[key_name_admin(user)] fired a rocket from a rocket launcher ([src.name]) at [target].")
- log_game("[key_name_admin(user)] used a rocket launcher ([src.name]) at [target].")
- ..()
+// /obj/item/gun/ballistic/rocket/handle_post_fire(mob/user, atom/target)
+// message_admins("[key_name_admin(user)] fired a rocket from a rocket launcher ([src.name]) at [target].")
+// log_game("[key_name_admin(user)] used a rocket launcher ([src.name]) at [target].")
+// ..()
/obj/item/gun/ballistic/rocket/collapsible
name = "disposable rocket launcher"
@@ -108,11 +108,7 @@
item_state = "[initial(item_state)]"
collapsed = 1
-/obj/item/gun/ballistic/rocket/collapsible/examine(mob/user, dist)
- . = ..()
- return
-
-/obj/item/gun/ballistic/rocket/collapsible/consume_next_projectile(mob/user as mob)
+/obj/item/gun/ballistic/rocket/collapsible/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
if(empty)
return
@@ -130,13 +126,14 @@
handle_casings = HOLD_CASINGS
unstable = 1
-/obj/item/gun/ballistic/rocket/tyrmalin/consume_next_projectile(mob/user as mob)
+// todo: dumb
+/obj/item/gun/ballistic/rocket/tyrmalin/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
if(.)
if(unstable)
switch(rand(1,100))
if(1 to 5)
- to_chat(user, "The rocket primer activates early!")
+ visible_message("The rocket primer on [src] activates early!")
icon_state = "rokkitlauncher-malfunction"
spawn(rand(2 SECONDS, 5 SECONDS))
if(src && !destroyed)
@@ -146,26 +143,15 @@
qdel(src)
return ..()
if(6 to 20)
- to_chat(user, "The rocket flares out in the tube!")
+ visible_message("The rocket in [src] flares out in the tube!")
playsound(src, 'sound/machines/button.ogg', 25)
icon_state = "rokkitlauncher-broken"
destroyed = 1
name = "broken rokkit launcher"
desc = "The tube has burst outwards like a sausage."
- return
+ return null
if(21 to 100)
- return 1
-
- if(destroyed)
- to_chat(user, "The [src] is broken!")
- handle_click_empty()
- return
-
-/obj/item/gun/ballistic/rocket/tyrmalin/Fire(atom/target, mob/living/user, clickparams, pointblank, reflex)
- . = ..()
- if(destroyed)
- to_chat(user, "\The [src] is completely inoperable!")
- handle_click_empty()
+ return ..()
/obj/item/gun/ballistic/rocket/tyrmalin/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(user.get_inactive_held_item() == src && destroyed)
diff --git a/code/modules/projectiles/guns/projectile/semiauto.dm b/code/modules/projectiles/guns/ballistic/semiauto.dm
similarity index 100%
rename from code/modules/projectiles/guns/projectile/semiauto.dm
rename to code/modules/projectiles/guns/ballistic/semiauto.dm
diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
similarity index 97%
rename from code/modules/projectiles/guns/projectile/shotgun.dm
rename to code/modules/projectiles/guns/ballistic/shotgun.dm
index 670a4eda27fe..88c5049a96a4 100644
--- a/code/modules/projectiles/guns/projectile/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -21,10 +21,8 @@
var/animated_pump = 0 //This is for cyling animations.
var/empty_sprite = 0 //This is just a dirty var so it doesn't fudge up.
-/obj/item/gun/ballistic/shotgun/pump/consume_next_projectile()
- if(chambered)
- return chambered.get_projectile()
- return null
+/obj/item/gun/ballistic/shotgun/pump/consume_next_projectile(datum/gun_firing_cycle/cycle)
+ return chambered?.get_projectile()
/obj/item/gun/ballistic/shotgun/pump/attack_self(mob/user, datum/event_args/actor/actor)
// todo: this breaks other attack self interactions :(
@@ -162,8 +160,6 @@
origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 1)
ammo_type = /obj/item/ammo_casing/a12g/beanbag
-
- burst_delay = 0
firemodes = list(
list(mode_name="fire one barrel at a time", one_handed_penalty = 15, burst=1),
list(mode_name="fire both barrels at once", one_handed_penalty = 35, burst=2),
@@ -175,7 +171,6 @@
/obj/item/gun/ballistic/shotgun/doublebarrel/holy
ammo_type = /obj/item/ammo_casing/a12g/silver
desc = "Alright you primitive screw heads, listen up. See this? This... is my BOOMSTICK."
- holy = TRUE
/obj/item/gun/ballistic/shotgun/doublebarrel/flare
name = "signal shotgun"
@@ -190,11 +185,9 @@
if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter))
to_chat(user, "You begin to shorten the barrel of \the [src].")
if(loaded.len)
- var/burstsetting = burst
- burst = 2
+ // todo: what happens if it's inside a container?
user.visible_message("The shotgun goes off!", "The shotgun goes off in your face!")
- Fire_userless(user)
- burst = burstsetting
+ start_firing_cycle_async(src, rand(0, 360), firemode = firemodes[2])
return
if(do_after(user, 30)) //SHIT IS STEALTHY EYYYYY
icon_state = "sawnshotgun"
@@ -228,7 +221,6 @@
/obj/item/gun/ballistic/shotgun/doublebarrel/sawn/alt/holy // A Special Skin for the sawn off,makes it look like the sawn off from Blood.
ammo_type = /obj/item/ammo_casing/a12g/silver
- holy = TRUE
/obj/item/gun/ballistic/shotgun/doublebarrel/quad
name = "quad-barreled shotgun"
@@ -248,11 +240,9 @@
origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 1)
ammo_type = /obj/item/ammo_casing/a12g/pellet
- burst_delay = 0
-
firemodes = list(
list(mode_name="fire one barrel at a time", burst=1),
- )
+ )
/obj/item/gun/ballistic/shotgun/doublebarrel/sawn/super
name = "super shotgun"
@@ -303,7 +293,6 @@
desc = "A Brass Flare Gun far more exspensuve and well made then the plastic ones mass produced for signalling. It fires using an odd clockwork mechanism. Loads using 12g"
icon_state = "flareg-holy"
accuracy = 50 //Strong Gun Better Accuracy
- holy = TRUE
/obj/item/gun/ballistic/shotgun/doublebarrel/axe
name = "Shot Axe"
@@ -319,7 +308,6 @@
slot_flags = SLOT_BACK
origin_tech = list(TECH_COMBAT = 4, TECH_MATERIAL = 2, TECH_OCCULT = 1)
damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
- holy = TRUE
/obj/item/gun/ballistic/shotgun/underslung
name = "underslung shotgun"
diff --git a/code/modules/projectiles/guns/projectile/sniper.dm b/code/modules/projectiles/guns/ballistic/sniper.dm
similarity index 100%
rename from code/modules/projectiles/guns/projectile/sniper.dm
rename to code/modules/projectiles/guns/ballistic/sniper.dm
diff --git a/code/modules/projectiles/guns/projectile/sniper/collapsible_sniper.dm b/code/modules/projectiles/guns/ballistic/sniper/collapsible_sniper.dm
similarity index 100%
rename from code/modules/projectiles/guns/projectile/sniper/collapsible_sniper.dm
rename to code/modules/projectiles/guns/ballistic/sniper/collapsible_sniper.dm
diff --git a/code/modules/projectiles/guns/energy-firemode.dm b/code/modules/projectiles/guns/energy-firemode.dm
new file mode 100644
index 000000000000..e405941b7f58
--- /dev/null
+++ b/code/modules/projectiles/guns/energy-firemode.dm
@@ -0,0 +1,33 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/firemode/energy
+ //* Energy Usage *//
+ /// charge cost of using this in cell units.
+ var/charge_cost
+ #warn impl
+
+ //* Projectile Formation *//
+ /// projectile type
+ var/projectile_type
+ #warn impl
+
+// todo: this shouldn't even exist.
+/datum/firemode/energy/New(obj/item/gun/inherit_from_gun, list/direct_varedits)
+ ..()
+ if(!length(direct_varedits))
+ return
+ for(var/varname in direct_varedits)
+ var/value = direct_varedits[varname]
+ // pull out special crap
+ switch(varname)
+ if("charge_cost")
+ src.charge_cost = value
+ if("projectile_type")
+ src.projectile_type = value
+
+/datum/firemode/energy/clone(include_contents)
+ var/datum/firemode/energy/cloning = ..()
+ cloning.charge_cost = charge_cost
+ cloning.projectile_type = projectile_type
+ return cloning
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index 89db582917b5..ba107a471c6e 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -1,3 +1,8 @@
+/**
+ * Energy Guns
+ *
+ * These are guns that generally will only utilize energy to generate their ammunition.
+ */
/obj/item/gun/energy
name = "energy gun"
desc = "A basic energy-based gun. Nanotrasen, Hephaestus, Ward-Takahashi, and countless other smaller corporations have their own version of this reliable design."
@@ -8,11 +13,11 @@
accuracy = 100
dispersion = list(0)
- var/obj/item/cell/power_supply //What type of power cell this uses
+ cell_system = TRUE
+ cell_type = /obj/item/cell/device/weapon
+
var/charge_cost = 240 //How much energy is needed to fire.
- var/accept_cell_type = /obj/item/cell/device
- var/cell_type = /obj/item/cell/device/weapon
projectile_type = /obj/projectile/beam/practice
var/modifystate
@@ -26,19 +31,14 @@
var/charge_tick = 0
var/charge_delay = 75 //delay between firing and charging
- var/battery_lock = 0 //If set, weapon cannot switch batteries
+ var/legacy_battery_lock = 0 //If set, weapon cannot switch batteries
/obj/item/gun/energy/Initialize(mapload)
- . = ..()
if(self_recharge)
- power_supply = new /obj/item/cell/device/weapon(src)
+ cell_system = TRUE
+ cell_type = cell_type || /obj/item/cell/device/weapon
START_PROCESSING(SSobj, src)
- else
- if(cell_type)
- power_supply = new cell_type(src)
- else
- power_supply = null
-
+ . = ..()
update_icon()
/obj/item/gun/energy/Destroy()
@@ -46,20 +46,17 @@
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/item/gun/energy/get_cell(inducer)
- return power_supply
-
/obj/item/gun/energy/process(delta_time)
if(self_recharge) //Every [recharge_time] ticks, recharge a shot for the battery
- if(world.time > last_shot + charge_delay) //Doesn't work if you've fired recently
- if(!power_supply || power_supply.charge >= power_supply.maxcharge)
+ if(world.time > last_fire + charge_delay) //Doesn't work if you've fired recently
+ if(!obj_cell_slot.cell || obj_cell_slot.cell.charge >= obj_cell_slot.cell.maxcharge)
return 0 // check if we actually need to recharge
charge_tick++
if(charge_tick < recharge_time) return 0
charge_tick = 0
- var/rechargeamt = power_supply.maxcharge*0.2
+ var/rechargeamt = obj_cell_slot.cell.maxcharge*0.2
if(use_external_power)
var/obj/item/cell/external = get_external_power_supply()
@@ -87,7 +84,7 @@
if(start_nutrition - max(0, end_nutrition) < rechargeamt / 10)
H.remove_blood((rechargeamt / 10) - (start_nutrition - max(0, end_nutrition)))
- power_supply.give(rechargeamt) //... to recharge 1/5th the battery
+ obj_cell_slot.cell.give(rechargeamt) //... to recharge 1/5th the battery
update_icon()
else
charge_tick = 0
@@ -104,63 +101,15 @@
..()
update_icon()
-/obj/item/gun/energy/consume_next_projectile()
- if(!power_supply)
+/obj/item/gun/energy/consume_next_projectile(datum/gun_firing_cycle/cycle)
+ if(!obj_cell_slot?.cell)
return null
if(!ispath(projectile_type))
return null
- if(!power_supply.checked_use(charge_cost))
+ if(!obj_cell_slot.cell.checked_use(charge_cost))
return null
return new projectile_type(src)
-/obj/item/gun/energy/proc/load_ammo(var/obj/item/C, mob/user)
- if(istype(C, /obj/item/cell))
- if(self_recharge || battery_lock)
- to_chat(user, "[src] does not have a battery port.")
- return
- if(istype(C, accept_cell_type))
- var/obj/item/cell/P = C
- if(power_supply)
- to_chat(user, "[src] already has a power cell.")
- else
- user.visible_message("[user] is reloading [src].", "You start to insert [P] into [src].")
- if(do_after(user, 5 * P.w_class))
- if(!user.attempt_insert_item_for_installation(P, src))
- return
- power_supply = P
- user.visible_message("[user] inserts [P] into [src].", "You insert [P] into [src].")
- playsound(src, 'sound/weapons/flipblade.ogg', 50, 1)
- update_icon()
- update_held_icon()
- else
- to_chat(user, "This cell is not fitted for [src].")
- return
-
-/obj/item/gun/energy/proc/unload_ammo(mob/user)
- if(self_recharge || battery_lock)
- to_chat(user, "[src] does not have a battery port.")
- return
- if(power_supply)
- user.put_in_hands(power_supply)
- power_supply.update_icon()
- user.visible_message("[user] removes [power_supply] from [src].", "You remove [power_supply] from [src].")
- power_supply = null
- playsound(src, 'sound/weapons/empty.ogg', 50, 1)
- update_icon()
- update_held_icon()
- else
- to_chat(user, "[src] does not have a power cell.")
-
-/obj/item/gun/energy/attackby(var/obj/item/A as obj, mob/user as mob)
- ..()
- load_ammo(A, user)
-
-/obj/item/gun/energy/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
- if(user.get_inactive_held_item() == src)
- unload_ammo(user)
- else
- return ..()
-
/obj/item/gun/energy/proc/get_external_power_supply()
if(isrobot(src.loc))
var/mob/living/silicon/robot/R = src.loc
@@ -177,9 +126,9 @@
/obj/item/gun/energy/examine(mob/user, dist)
. = ..()
- if(power_supply)
+ if(obj_cell_slot.cell)
if(charge_cost)
- var/shots_remaining = round(power_supply.charge / max(1, charge_cost)) // Paranoia
+ var/shots_remaining = round(obj_cell_slot.cell.charge / max(1, charge_cost)) // Paranoia
. += "Has [shots_remaining] shot\s remaining."
else
. += "Has infinite shots remaining."
@@ -191,17 +140,17 @@
. = ..()
if((item_renderer || mob_renderer) || !render_use_legacy_by_default)
return // using new system
- if(power_supply == null)
+ if(obj_cell_slot.cell == null)
if(modifystate)
icon_state = "[modifystate]_open"
else
icon_state = "[initial(icon_state)]_open"
return
else if(charge_meter)
- var/ratio = power_supply.percent() * 0.01
+ var/ratio = obj_cell_slot.cell.percent() * 0.01
//make sure that rounding down will not give us the empty state even if we have charge for a shot left.
- if(power_supply.charge < charge_cost)
+ if(obj_cell_slot.cell.charge < charge_cost)
ratio = 0
else
ratio = max(round(ratio, 0.25) * 100, 25)
@@ -211,7 +160,7 @@
else
icon_state = "[initial(icon_state)][ratio]"
- else if(power_supply)
+ else if(obj_cell_slot.cell)
if(modifystate)
icon_state = "[modifystate]"
else
@@ -220,34 +169,18 @@
if(!ignore_inhands)
update_held_icon()
-/obj/item/gun/energy/proc/start_recharge()
- if(power_supply == null)
- power_supply = new /obj/item/cell/device/weapon(src)
- self_recharge = 1
- START_PROCESSING(SSobj, src)
- update_icon()
-
-/obj/item/gun/energy/get_description_interaction(mob/user)
- var/list/results = list()
+//* Ammo *//
- if(!battery_lock && !self_recharge)
- if(power_supply)
- results += "[desc_panel_image("offhand", user)]to remove the weapon cell."
- else
- results += "[desc_panel_image("weapon cell")]to add a new weapon cell."
+/obj/item/gun/energy/get_ammo_ratio()
+ var/obj/item/cell/cell = obj_cell_slot.cell
+ if(!cell)
+ return 0
+ return cell.charge / cell.maxcharge
- results += ..()
- return results
+//* Power *//
-/obj/item/gun/energy/inducer_scan(obj/item/inducer/I, list/things_to_induce, inducer_flags)
- if(inducer_flags & INDUCER_NO_GUNS)
- return
+/obj/item/gun/energy/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot)
+ if(legacy_battery_lock)
+ return FALSE
return ..()
-
-//* Ammo *//
-
-/obj/item/gun/energy/get_ammo_ratio()
- if(!power_supply)
- return 0
- return power_supply.charge / power_supply.maxcharge
diff --git a/code/modules/projectiles/guns/energy/frontier.dm b/code/modules/projectiles/guns/energy/frontier.dm
index ded27a270018..2aba67b663a1 100644
--- a/code/modules/projectiles/guns/energy/frontier.dm
+++ b/code/modules/projectiles/guns/energy/frontier.dm
@@ -8,7 +8,7 @@
fire_sound = 'sound/weapons/laser_rifle_1.wav'
origin_tech = list(TECH_COMBAT = 4, TECH_MAGNET = 2, TECH_POWER = 4)
charge_cost = 300
- battery_lock = 1
+ legacy_battery_lock = 1
var/recharging = 0
var/phase_power = 75
@@ -30,7 +30,7 @@
if(!do_after(user, 10, src))
break
playsound(get_turf(src),'sound/items/change_drill.ogg',25,1)
- if(power_supply.give(phase_power) < phase_power)
+ if(obj_cell_slot?.cell?.give(phase_power) < phase_power)
break
recharging = 0
@@ -119,7 +119,7 @@
if(!do_after(user, 10, src))
break
playsound(get_turf(src),'sound/items/change_drill.ogg',25,1)
- if(power_supply.give(phase_power) < phase_power)
+ if(obj_cell_slot?.cell?.give(phase_power) < phase_power)
break
recharging = 0
diff --git a/code/modules/projectiles/guns/energy/hooklauncher.dm b/code/modules/projectiles/guns/energy/hooklauncher.dm
index e9ee3d4e4bc1..c3d2a50a8ecf 100644
--- a/code/modules/projectiles/guns/energy/hooklauncher.dm
+++ b/code/modules/projectiles/guns/energy/hooklauncher.dm
@@ -9,7 +9,9 @@
item_state = "gravwhip"
fire_sound_text = "laser blast"
- fire_delay = 15
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 1.5 SECONDS;
+ }
charge_cost = 300
cell_type = /obj/item/cell/device/weapon
@@ -27,7 +29,7 @@
w_class = WEIGHT_CLASS_TINY
cell_type = /obj/item/cell/device/weapon/recharge/alien
- battery_lock = TRUE
+ legacy_battery_lock = TRUE
charge_cost = 400
charge_meter = FALSE
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index 9e2f8a0ed448..30a6335a9ccd 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -1,3 +1,18 @@
+/datum/firemode/energy/laser_rifle
+ abstract_type = /datum/firemode/energy/laser_rifle
+
+/datum/firemode/energy/laser_rifle/normal
+ name = "normal"
+ cycle_cooldown = 0.8 SECONDS
+ projectile_type = /obj/projectile/beam/midlaser
+ charge_cost = 2400 / 10
+
+/datum/firemode/energy/laser_rifle/suppression
+ name = "suppressive"
+ cycle_cooldown = 0.4 SECONDS
+ projectile_type = /obj/projectile/beam/weaklaser
+ charge_cost = 2400 / 40
+
/obj/item/gun/energy/laser
name = "laser rifle"
desc = "A Hephaestus Industries G40E rifle, designed to kill with concentrated energy blasts. This variant has the ability to \
@@ -5,23 +20,15 @@
icon_state = "laser"
item_state = "laser"
wielded_item_state = "laser-wielded"
- fire_delay = 8
slot_flags = SLOT_BELT|SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
damage_force = 10
origin_tech = list(TECH_COMBAT = 3, TECH_MAGNET = 2)
materials_base = list(MAT_STEEL = 2000)
- projectile_type = /obj/projectile/beam/midlaser
heavy = TRUE
one_handed_penalty = 30
-
worth_intrinsic = 350
- firemodes = list(
- list(mode_name="normal", fire_delay=8, projectile_type=/obj/projectile/beam/midlaser, charge_cost = 240),
- list(mode_name="suppressive", fire_delay=5, projectile_type=/obj/projectile/beam/weaklaser, charge_cost = 60),
- )
-
/obj/item/gun/energy/laser/mounted
self_recharge = 1
use_external_power = 1
@@ -34,15 +41,13 @@
/obj/item/gun/energy/laser/practice
name = "practice laser carbine"
desc = "A modified version of the HI G40E, this one fires less concentrated energy bolts designed for target practice."
- projectile_type = /obj/projectile/beam/practice
- charge_cost = 48
-
- cell_type = /obj/item/cell/device
- firemodes = list(
- list(mode_name="normal", projectile_type=/obj/projectile/beam/practice, charge_cost = 48),
- list(mode_name="suppressive", projectile_type=/obj/projectile/beam/practice, charge_cost = 12),
- )
+ firemodes = /datum/firemode/energy{
+ name = "normal";
+ projectile_type = /obj/projectile/beam/practice;
+ charge_cost = 2400 / 80;
+ cycle_cooldown = 0.4 SECONDS;
+ }
/obj/item/gun/energy/retro
name = "retro laser"
@@ -51,8 +56,13 @@
desc = "An older model of the basic lasergun. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws."
slot_flags = SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
- projectile_type = /obj/projectile/beam
- fire_delay = 10 //old technology
+
+ firemodes = /datum/firemode/energy{
+ name = "normal";
+ charge_cost = 2400 / 10;
+ projectile_type = /obj/projectile/beam;
+ cycle_cooldown = 1 SECONDS;
+ }
/obj/item/gun/energy/retro/mounted
self_recharge = 1
@@ -101,15 +111,17 @@
catalogue_data = list(/datum/category_item/catalogue/anomalous/precursor_a/alien_pistol)
icon_state = "alienpistol"
item_state = "alienpistol"
- fire_delay = 10 // Handguns should be inferior to two-handed weapons. Even alien ones I suppose.
- charge_cost = 240 // Ten shots.
- projectile_type = /obj/projectile/beam/cyan
+ firemodes = /datum/firemode/energy {
+ projectile_type = /obj/projectile/beam/cyan;
+ charge_cost = 2400 / 10;
+ cycle_cooldown = 1 SECONDS;
+ }
+
cell_type = /obj/item/cell/device/weapon/recharge/alien // Self charges.
origin_tech = list(TECH_COMBAT = 8, TECH_MAGNET = 7)
modifystate = "alienpistol"
-
/obj/item/gun/energy/captain
name = "antique laser gun"
icon_state = "caplaser"
@@ -120,10 +132,9 @@
w_class = WEIGHT_CLASS_NORMAL
projectile_type = /obj/projectile/beam
origin_tech = null
- fire_delay = 10 //Old pistol
charge_cost = 480 //to compensate a bit for self-recharging
cell_type = /obj/item/cell/device/weapon/recharge/captain
- battery_lock = 1
+ legacy_battery_lock = 1
/obj/item/gun/energy/lasercannon
name = "laser cannon"
@@ -133,8 +144,10 @@
origin_tech = list(TECH_COMBAT = 4, TECH_MATERIAL = 3, TECH_POWER = 3)
slot_flags = SLOT_BELT|SLOT_BACK
projectile_type = /obj/projectile/beam/heavylaser/cannon
- battery_lock = 1
- fire_delay = 20
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 2 SECONDS;
+ }
+ legacy_battery_lock = 1
w_class = WEIGHT_CLASS_BULKY
heavy = TRUE
one_handed_penalty = 90 // The thing's heavy and huge.
@@ -150,7 +163,6 @@
one_handed_penalty = 0 // Not sure if two-handing gets checked for mounted weapons, but better safe than sorry.
projectile_type = /obj/projectile/beam/heavylaser
charge_cost = 400
- fire_delay = 20
/obj/item/gun/energy/xray
name = "xray laser gun"
@@ -178,9 +190,11 @@
worth_intrinsic = 750
projectile_type = /obj/projectile/beam/sniper
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 3.5 SECONDS;
+ }
slot_flags = SLOT_BACK
charge_cost = 600
- fire_delay = 35
damage_force = 10
heavy = TRUE
w_class = WEIGHT_CLASS_HUGE // So it can't fit in a backpack.
@@ -203,7 +217,7 @@
pin = /obj/item/firing_pin/explorer
cell_type = /obj/item/cell/device/weapon/recharge/sniper
accuracy = 45 //Modifications include slightly better hip-firing furniture.
- battery_lock = 1 //With the change that the normal DMR can now change the weapon cell, we need to add this here so people can't take out the self-recharging special cell.
+ legacy_battery_lock = 1 //With the change that the normal DMR can now change the weapon cell, we need to add this here so people can't take out the self-recharging special cell.
scoped_accuracy = 100
charge_cost = 600
@@ -217,7 +231,6 @@
projectile_type = /obj/projectile/beam/sniper
slot_flags = SLOT_BACK
charge_cost = 1300
- fire_delay = 20
damage_force = 8
heavy = TRUE
w_class = WEIGHT_CLASS_BULKY
@@ -254,7 +267,7 @@
materials_base = list(MAT_STEEL = 2000)
projectile_type = /obj/projectile/beam/lasertag/blue
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
/obj/item/gun/energy/lasertag/blue
icon_state = "bluetag"
@@ -315,19 +328,20 @@
cell_type = /obj/item/cell/device/weapon
unstable = 1
-/obj/item/gun/energy/zip/consume_next_projectile(mob/user as mob)
+// todo: this is dumb
+/obj/item/gun/energy/zip/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
if(.)
if(unstable)
if(prob(10))
- to_chat(user, "The cell overcooks and ruptures!")
+ // todo: actor support if we keep this shit
+ visible_message("The cell overcooks and ruptures!")
spawn(rand(2 SECONDS,5 SECONDS))
- if(src)
+ if(!QDELETED(src))
visible_message("\The [src] detonates!")
explosion(get_turf(src), -1, 0, 2, 3)
qdel(chambered)
qdel(src)
- return ..()
//NT SpecOps Laser Rifle
/obj/item/gun/energy/combat
@@ -335,7 +349,9 @@
desc = "A sturdy laser rifle fine tuned for Nanotrasen special operations. More reliable than mass production models, this weapon was designed to kill, and nothing else."
icon_state = "clrifle"
item_state = "clrifle"
- fire_delay = 6
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 0.6 SECONDS;
+ }
slot_flags = SLOT_BELT|SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
damage_force = 10
diff --git a/code/modules/projectiles/guns/energy/modular/gunframes.dm b/code/modules/projectiles/guns/energy/modular/gunframes.dm
index 8fb9c34a61f8..43a4f3c66621 100644
--- a/code/modules/projectiles/guns/energy/modular/gunframes.dm
+++ b/code/modules/projectiles/guns/energy/modular/gunframes.dm
@@ -58,7 +58,7 @@
name = "modular energy cannon"
desc = "A huge, semi-modular energy cannon. Can mount three cores, and utilizes a robust power handler and circuitry combined with an integral large cell."
cores = 3
- battery_lock = TRUE
+ legacy_battery_lock = TRUE
cell_type = /obj/item/cell/device/weapon/modcannon
icon_state = "mod_cannon"
w_class = WEIGHT_CLASS_HUGE
@@ -77,7 +77,7 @@
name = "advanced modular energy gun"
desc = "A huge, semi-modular energy weapon. Can mount two cores, and utilizes an advanced power handler coupled with an integral RTG."
cores = 2
- battery_lock = TRUE
+ legacy_battery_lock = TRUE
cell_type = /obj/item/cell/device/weapon/recharge/captain
icon_state = "modnuc"
w_class = WEIGHT_CLASS_HUGE
diff --git a/code/modules/projectiles/guns/energy/modular/modulargun.dm b/code/modules/projectiles/guns/energy/modular/modulargun.dm
index 89e7eb2eeca7..20dba7bd9fd4 100644
--- a/code/modules/projectiles/guns/energy/modular/modulargun.dm
+++ b/code/modules/projectiles/guns/energy/modular/modulargun.dm
@@ -49,7 +49,7 @@
if(!length(firemodes))
return
var/datum/firemode/new_mode = firemodes[1]
- new_mode.apply_to(src)
+ new_mode.apply_legacy_variables(src)
/obj/item/gun/energy/modular/proc/do_generatefiremodes() //Accepts no args. Checks the gun's current components and generates projectile types, firemode costs and max burst. Should be called after changing parts or part values.
if(!circuit)
@@ -69,8 +69,13 @@
var/chargecost = primarycore.beamcost * lasercap.costmod //Cost for primary fire.
chargecost += lasercooler.costadd //Cooler adds a flat amount post capacitor based on firedelay mod. Can be negative.
var/scatter = laserlens.scatter //Does it scatter the beams?
- fire_delay = lasercap.firedelay * lasercooler.delaymod //Firedelay caculated by the capacitor and the laser cooler.
- burst_delay = circuit.burst_delay * lasercooler.delaymod //Ditto but with burst delay.
+ var/fire_delay = lasercap.firedelay * lasercooler.delaymod //Firedelay caculated by the capacitor and the laser cooler.
+ var/burst_delay = circuit.burst_delay * lasercooler.delaymod //Ditto but with burst delay.
+ // shitcode to make old code work; basically wait until the generate.
+ spawn(0)
+ for(var/datum/firemode/firemode in src.firemodes)
+ firemode.cycle_cooldown = fire_delay
+ firemode.burst_delay = burst_delay
accuracy = laserlens.accuracy
var/chargecost_lethal = 120
var/chargecost_special = 120
@@ -188,7 +193,7 @@
if(projectile_type == /obj/projectile)
to_chat(user, "The gun is experiencing a checking error! Open and close the weapon, or try removing all the parts and placing them back in.")
var/datum/firemode/new_mode = firemodes[1]
- new_mode.apply_to(src)
+ new_mode.apply_legacy_variables(src)
return FALSE
/obj/item/gun/energy/modular/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
diff --git a/code/modules/projectiles/guns/modular_guns.dm b/code/modules/projectiles/guns/energy/modular_guns_old.dm
similarity index 99%
rename from code/modules/projectiles/guns/modular_guns.dm
rename to code/modules/projectiles/guns/energy/modular_guns_old.dm
index 7e226f09e813..2835fbf39069 100644
--- a/code/modules/projectiles/guns/modular_guns.dm
+++ b/code/modules/projectiles/guns/energy/modular_guns_old.dm
@@ -129,7 +129,7 @@
/obj/item/gun/energy/modular/load_ammo(var/obj/item/C, mob/user)
if(istype(C, cell_type))
- if(self_recharge || battery_lock)
+ if(self_recharge || legacy_battery_lock)
to_chat(user, "[src] does not have a battery port.")
return
var/obj/item/cell/P = C
diff --git a/code/modules/projectiles/guns/energy/netgun_vr.dm b/code/modules/projectiles/guns/energy/netgun_vr.dm
index f87e7ceeb060..cba92e026c2e 100644
--- a/code/modules/projectiles/guns/energy/netgun_vr.dm
+++ b/code/modules/projectiles/guns/energy/netgun_vr.dm
@@ -1,36 +1,47 @@
+/datum/firemode/energy/netgun
+ cycle_cooldown = 0.5 SECONDS
+
+/datum/firemode/energy/netgun/stun
+ name = "stun"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/stun/blue, fire_sound='sound/weapons/Taser.ogg', charge_cost=240)
+
+/datum/firemode/energy/netgun/capture
+ name = "capture"
+ cycle_cooldown = 5 SECONDS
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/energy_net, fire_sound = 'sound/weapons/eluger.ogg', charge_cost=1200)
+
+
/obj/item/gun/energy/netgun
name = "Hephaestus \'Retiarius\'"
desc = "The Hephaestus Industries 'Retiarius' stuns targets, immobilizing them in an energized net field."
catalogue_data = list()///datum/category_item/catalogue/information/organization/hephaestus)
icon_state = "hunter"
item_state = "gun" // Placeholder
- mode_name = "stun"
fire_sound = 'sound/weapons/eluger.ogg'
origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 5, TECH_MAGNET = 3)
projectile_type = /obj/projectile/beam/stun/blue
charge_cost = 240
- fire_delay = 5
firemodes = list(
- list(mode_name="stun", projectile_type=/obj/projectile/beam/stun/blue, fire_sound='sound/weapons/Taser.ogg', charge_cost=240, fire_delay=5),
- list(mode_name="capture", projectile_type=/obj/projectile/beam/energy_net, fire_sound = 'sound/weapons/eluger.ogg', charge_cost=1200, fire_delay=50)
+ /datum/firemode/energy/netgun/stun,
+ /datum/firemode/energy/netgun/capture,
)
/obj/item/gun/energy/netgun/update_icon()
cut_overlays()
var/list/overlays_to_add = list()
- if(power_supply)
- var/ratio = power_supply.charge / power_supply.maxcharge
+ if(obj_cell_slot.cell)
+ var/ratio = obj_cell_slot.cell.charge / obj_cell_slot.cell.maxcharge
- if(power_supply.charge < charge_cost)
+ if(obj_cell_slot.cell.charge < charge_cost)
ratio = 0
else
ratio = max(round(ratio, 0.25) * 100, 25)
overlays_to_add += "[initial(icon_state)]_cell"
overlays_to_add += "[initial(icon_state)]_[ratio]"
- overlays_to_add += "[initial(icon_state)]_[mode_name]"
+ overlays_to_add += "[initial(icon_state)]_[legacy_get_firemode()?.name]"
add_overlay(overlays_to_add)
diff --git a/code/modules/projectiles/guns/energy/nuclear.dm b/code/modules/projectiles/guns/energy/nuclear.dm
index f901aac01cbb..24b03a444d7f 100644
--- a/code/modules/projectiles/guns/energy/nuclear.dm
+++ b/code/modules/projectiles/guns/energy/nuclear.dm
@@ -1,28 +1,62 @@
+/datum/firemode/energy/energy_gun
+ abstract_type = /datum/firemode/energy/energy_gun
+ cycle_cooldown = 1 SECONDS
+
+/datum/firemode/energy/energy_gun/stun
+ name = "stun"
+ projectile_type = /obj/projectile/beam/stun/med
+ charge_cost = 2400 / 10
+
+/datum/firemode/energy/energy_gun/kill
+ name = "lethal"
+ projectile_type = /obj/projectile/beam
+ charge_cost = 2400 / 5
+
/obj/item/gun/energy/gun
name = "energy gun"
desc = "Another bestseller of Lawson Arms and "+TSC_HEPH+", the LAEP90 Perun is a versatile energy based sidearm, capable of switching between low and high capacity projectile settings. In other words: Stun or Kill."
description_info = "This is an energy weapon. To fire the weapon, ensure your intent is *not* set to 'help', have your gun mode set to 'fire', \
- then click where you want to fire. Most energy weapons can fire through windows harmlessly. To recharge this weapon, use a weapon recharger."
+ then click where you want to fire. Most energy weapons can fire through windows harmlessly. To switch between stun and lethal, click the weapon \
+ in your hand. To recharge this weapon, use a weapon recharger."
icon_state = "energystun100"
item_state = null //so the human update icon uses the icon_state instead.
- fire_delay = 10 // Handguns should be inferior to two-handed weapons.
worth_intrinsic = 250
-
- projectile_type = /obj/projectile/beam/stun/med
origin_tech = list(TECH_COMBAT = 3, TECH_MAGNET = 2)
modifystate = "energystun"
firemodes = list(
- list(mode_name="stun", projectile_type=/obj/projectile/beam/stun/med, modifystate="energystun", charge_cost = 240),
- list(mode_name="lethal", projectile_type=/obj/projectile/beam, modifystate="energykill", charge_cost = 480),
- )
+ /datum/firemode/energy/energy_gun/stun,
+ /datum/firemode/energy/energy_gun/kill,
+ )
/obj/item/gun/energy/gun/mounted
name = "mounted energy gun"
self_recharge = 1
use_external_power = 1
+/datum/firemode/energy/burst_laser
+ abstract_type = /datum/firemode/energy/burst_laser
+ burst_delay = 0.2 SECONDS
+ cycle_cooldown = 0.6 SECONDS
+
+/datum/firemode/energy/burst_laser/stun
+ name = "stun"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/stun/weak, modifystate="fm-2tstun", charge_cost = 100)
+
+/datum/firemode/energy/burst_laser/stun_burst
+ name = "stun burst"
+ burst_amount = 3
+ legacy_direct_varedits = list(burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/stun/weak, modifystate="fm-2tstun")
+
+/datum/firemode/energy/burst_laser/lethal
+ name = "lethal"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/burstlaser, modifystate="fm-2tkill", charge_cost = 200)
+
+/datum/firemode/energy/burst_laser/lethal_burst
+ name = "lethal burst"
+ burst_amount = 3
+ legacy_direct_varedits = list(burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/burstlaser, modifystate="fm-2tkill")
/obj/item/gun/energy/gun/burst
name = "burst laser"
@@ -32,22 +66,42 @@
charge_cost = 100
damage_force = 8
w_class = WEIGHT_CLASS_BULKY //Probably gonna make it a rifle sooner or later
- fire_delay = 6
heavy = TRUE
projectile_type = /obj/projectile/beam/stun/weak
origin_tech = list(TECH_COMBAT = 4, TECH_MAGNET = 2, TECH_ILLEGAL = 3)
modifystate = "fm-2tstun"
-// requires_two_hands = 1
one_handed_penalty = 30
worth_intrinsic = 450
firemodes = list(
- list(mode_name="stun", burst=1, projectile_type=/obj/projectile/beam/stun/weak, modifystate="fm-2tstun", charge_cost = 100),
- list(mode_name="stun burst", burst=3, fire_delay=null, move_delay=4, burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/stun/weak, modifystate="fm-2tstun"),
- list(mode_name="lethal", burst=1, projectile_type=/obj/projectile/beam/burstlaser, modifystate="fm-2tkill", charge_cost = 200),
- list(mode_name="lethal burst", burst=3, fire_delay=null, move_delay=4, burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/burstlaser, modifystate="fm-2tkill"),
- )
+ /datum/firemode/energy/burst_laser/stun,
+ /datum/firemode/energy/burst_laser/stun_burst,
+ /datum/firemode/energy/burst_laser/lethal,
+ /datum/firemode/energy/burst_laser/lethal_burst,
+ )
+
+/datum/firemode/energy/mining_carbine
+ burst_delay = 0.1 SECONDS
+ cycle_cooldown = 0.3 SECONDS
+
+/datum/firemode/energy/mining_carbine/mine
+ name = "mine"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/excavation, modifystate="fm-2tstun", charge_cost = 20)
+
+/datum/firemode/energy/mining_carbine/mine_burst
+ name = "mine burst"
+ burst_amount = 5
+ legacy_direct_varedits = list(burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/excavation, modifystate="fm-2tstun")
+
+/datum/firemode/energy/mining_carbine/scatetr
+ name = "scatter"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/scatter/excavation, modifystate="fm-2tkill", charge_cost = 40)
+
+/datum/firemode/energy/mining_carbine/scatter_burst
+ name = "scatter burst"
+ burst_amount = 5
+ legacy_direct_varedits = list(burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/scatter/excavation, modifystate="fm-2tkill")
/obj/item/gun/energy/gun/miningcarbine
name = "mining carbine"
@@ -57,17 +111,30 @@
charge_cost = 20
damage_force = 8
w_class = WEIGHT_CLASS_BULKY
- fire_delay = 3
projectile_type = /obj/projectile/beam/excavation
origin_tech = list(TECH_COMBAT = 4, TECH_MAGNET = 2, TECH_ILLEGAL = 2)
modifystate = "fm-2tstun"
firemodes = list(
- list(mode_name="mine", burst=1, projectile_type=/obj/projectile/beam/excavation, modifystate="fm-2tstun", charge_cost = 20),
- list(mode_name="mine burst", burst=5, fire_delay=null, move_delay=4, burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/excavation, modifystate="fm-2tstun"),
- list(mode_name="scatter", burst=1, projectile_type=/obj/projectile/scatter/excavation, modifystate="fm-2tkill", charge_cost = 40),
- list(mode_name="scatter burst", burst=5, fire_delay=null, move_delay=4, burst_accuracy=list(65,65,65), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/scatter/excavation, modifystate="fm-2tkill"),
- )
+ /datum/firemode/energy/mining_carbine/mine,
+ /datum/firemode/energy/mining_carbine/mine_burst,
+ /datum/firemode/energy/mining_carbine/scatter,
+ /datum/firemode/energy/mining_carbine/scatter_burst,
+ )
+
+/datum/firemode/energy/advanced_energy_gun
+ abstract_type = /datum/firemode/energy/advanced_energy_gun
+ cycle_cooldown = 0.6 SECONDS
+
+/datum/firemode/energy/advanced_energy_gun/stun
+ name = "stun"
+ projectile_type = /obj/projectile/beam/stun/med
+ charge_cost = 2400 / 10
+
+/datum/firemode/energy/advanced_energy_gun/kill
+ name = "lethal"
+ projectile_type = /obj/projectile/beam
+ charge_cost = 2400 / 5
/obj/item/gun/energy/gun/nuclear
name = "advanced energy gun"
@@ -79,54 +146,40 @@
damage_force = 8 //looks heavier than a pistol
w_class = WEIGHT_CLASS_BULKY //Looks bigger than a pistol, too.
heavy = TRUE
- fire_delay = 6 //This one's not a handgun, it should have the same fire delay as everything else
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
modifystate = null
// requires_two_hands = 1
one_handed_penalty = 30 // It's rather bulky at the fore, so holding it in one hand is harder than with two.
firemodes = list(
- list(mode_name="stun", projectile_type=/obj/projectile/beam/stun, modifystate="nucgunstun", charge_cost = 240), //10 shots
- list(mode_name="lethal", projectile_type=/obj/projectile/beam, modifystate="nucgunkill", charge_cost = 240), //10 shots
- )
-
-/obj/item/gun/energy/gun/multiphase
- name = "\improper X-01 MultiPhase Energy Gun"
- desc = "This is an expensive, modern recreation of an antique laser gun. This gun has several unique firemodes, but lacks the ability to recharge over time."
- icon = 'icons/obj/multiphase.dmi'
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/inhands/guns_left.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/inhands/guns_right.dmi',
- )
- icon_state = "multiphasedis100"
- projectile_type = /obj/projectile/beam/stun/disabler
- origin_tech = list(TECH_COMBAT = 5, TECH_MATERIAL = 3, TECH_POWER = 3)
- slot_flags = SLOT_BELT|SLOT_HOLSTER
- damage_force = 10 //for the HOS to lay down a good beating in desperate situations. Holdover from TG.
- w_class = WEIGHT_CLASS_NORMAL
- fire_delay = 6 //standard rate
- battery_lock = 0
- modifystate = null
+ /datum/firemode/energy/advanced_energy_gun/stun,
+ /datum/firemode/energy/advanced_energy_gun/kill,
+ )
- firemodes = list(
- list(mode_name="disable", burst=3, fire_delay=null, move_delay=4, burst_accuracy=list(0,0,0), dispersion=list(0.0, 0.2, 0.5), projectile_type=/obj/projectile/beam/stun/disabler, modifystate="multiphasedis", charge_cost = 100),
- list(mode_name="stun", burst=1, projectile_type=/obj/projectile/energy/electrode/goldenbolt, modifystate="multiphasestun", charge_cost = 480),
- list(mode_name="lethal", burst=1, projectile_type=/obj/projectile/beam, modifystate="multiphasekill", charge_cost = 240),
- )
+/datum/firemode/energy/legacy_nt_combat_pistol
+ abstract_type = /datum/firemode/energy/advanced_energy_gun
+ cycle_cooldown = 0.6 SECONDS
+
+/datum/firemode/energy/legacy_nt_combat_pistol/stun
+ name = "stun"
+ projectile_type = /obj/projectile/beam/stun/med
+ charge_cost = 2400 / 12
+
+/datum/firemode/energy/legacy_nt_combat_pistol/kill
+ name = "lethal"
+ projectile_type = /obj/projectile/beam
+ charge_cost = 2400 / 6
//NT SpecOps Laser Pistol
/obj/item/gun/energy/gun/combat
name = "NT-ES-2 energy pistol"
desc = "A purpose-built energy weapon designed to function as a sidearm for Nanotrasen special operations. This weapon is ideal for hazardous environments where both lethal and non-lethal responses may be required."
icon_state = "clpistolstun100"
- fire_delay = 8
-
- origin_tech = list(TECH_COMBAT = 5, TECH_MAGNET = 2)
modifystate = "clpistolstun"
firemodes = list(
- list(mode_name="stun", projectile_type=/obj/projectile/beam/stun/med, modifystate="clpistolstun", charge_cost = 200),
- list(mode_name="lethal", projectile_type=/obj/projectile/beam, modifystate="clpistolkill", charge_cost = 400),
- )
+ /datum/firemode/energy/legacy_nt_combat_pistol/stun,
+ /datum/firemode/energy/legacy_nt_combat_pistol/kill,
+ )
diff --git a/code/modules/projectiles/guns/energy/particle.dm b/code/modules/projectiles/guns/energy/particle.dm
index 1a230c11a3d5..f5288b0b5703 100644
--- a/code/modules/projectiles/guns/energy/particle.dm
+++ b/code/modules/projectiles/guns/energy/particle.dm
@@ -18,7 +18,9 @@
w_class = WEIGHT_CLASS_NORMAL
projectile_type = /obj/projectile/bullet/particle
origin_tech = list(TECH_COMBAT = 3, TECH_MAGNET = 2, TECH_MATERIAL = 2)
- fire_delay = 10
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 1 SECONDS;
+ }
charge_cost = 200 //slightly more shots than lasers
var/safetycatch = 0 //if 1, won't let you fire in pressurised environment, rather than malfunctioning
var/obj/item/pressurelock/attached_safety
@@ -34,10 +36,12 @@
w_class = WEIGHT_CLASS_BULKY //bigger than a pistol, too.
heavy = TRUE
origin_tech = list(TECH_COMBAT = 4, TECH_MATERIAL = 5, TECH_POWER = 3, TECH_MAGNET = 3)
- fire_delay = 6 //This one's not a handgun, it should have the same fire delay as everything else
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 0.6 SECONDS;
+ }
self_recharge = 1
modifystate = null
- battery_lock = 1
+ legacy_battery_lock = 1
recharge_time = 6 // every 6 ticks, recharge 2 shots. Slightly slower than AEG.
charge_delay = 10 //Starts recharging faster after firing than an AEG though.
one_handed_penalty = 15
@@ -51,8 +55,10 @@
slot_flags = SLOT_BACK
origin_tech = list(TECH_COMBAT = 5, TECH_MATERIAL = 5, TECH_POWER = 4, TECH_MAGNET = 4)
projectile_type = /obj/projectile/bullet/particle/heavy
- battery_lock = 1
- fire_delay = 15 // fires faster than a laser cannon. c'mon, it's an awesome-but-impractical endgame gun.
+ legacy_battery_lock = 1
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 1.5 SECONDS;
+ }
w_class = WEIGHT_CLASS_HUGE // So it can't fit in a backpack.
damage_force = 10
one_handed_penalty = 60 // The thing's heavy and huge.
@@ -71,7 +77,7 @@
var/datum/gas_mixture/environment = T ? T.return_air() : null
var/pressure = environment ? environment.return_pressure() : 0
- if (!power_supply || power_supply.charge < charge_cost)
+ if (!obj_cell_slot.cell || obj_cell_slot.cell.charge < charge_cost)
user.visible_message("*click*", "*click*")
playsound(src.loc, 'sound/weapons/empty.ogg', 100, 1)
return 0
@@ -93,7 +99,7 @@
playsound(src.loc, 'sound/weapons/empty.ogg', 100, 1)
else if (severity <= 60) //50% chance of fizzling and wasting a shot
user.visible_message("\The [user] fires \the [src], but the shot fizzles in the air!", "You fire \the [src], but the shot fizzles in the air!")
- power_supply.charge -= charge_cost
+ obj_cell_slot.cell.charge -= charge_cost
playsound(src.loc, fire_sound, 100, 1)
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread()
sparks.set_up(2, 1, T)
@@ -101,7 +107,7 @@
update_icon()
else if (severity <= 80) //20% chance of shorting out and emptying the cell
user.visible_message("\The [user] pulls the trigger, but \the [src] shorts out!", "You pull the trigger, but \the [src] shorts out!")
- power_supply.charge = 0
+ obj_cell_slot.cell.charge = 0
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread()
sparks.set_up(2, 1, T)
sparks.start()
@@ -111,11 +117,12 @@
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread()
sparks.set_up(2, 1, T)
sparks.start()
- power_supply.charge = 0
- power_supply.maxcharge = 1 //just to avoid div/0 runtimes
- power_supply.desc += " It seems to be burnt out!"
+ obj_cell_slot.cell.charge = 0
+ obj_cell_slot.cell.maxcharge = 1 //just to avoid div/0 runtimes
+ obj_cell_slot.cell.desc += " It seems to be burnt out!"
desc += " The casing is covered in scorch-marks."
- fire_delay += fire_delay // even if you swap out the cell for a good one, the gun's cluckety-clucked.
+ // todo: transform_cycle_cooldown(datum/firing_cycle/cycle) as num
+ // fire_delay += fire_delay // even if you swap out the cell for a good one, the gun's cluckety-clucked.
charge_cost += charge_cost
update_icon()
else if (severity <= 150) // 10% chance of exploding
diff --git a/code/modules/projectiles/guns/energy/sizegun_vr.dm b/code/modules/projectiles/guns/energy/sizegun_vr.dm
index 7f2a5c9f31b7..30b280ab7706 100644
--- a/code/modules/projectiles/guns/energy/sizegun_vr.dm
+++ b/code/modules/projectiles/guns/energy/sizegun_vr.dm
@@ -14,7 +14,7 @@
origin_tech = list(TECH_BLUESPACE = 4)
modifystate = "sizegun-shrink"
no_pin_required = 1
- battery_lock = 1
+ legacy_battery_lock = 1
var/size_set_to = 1
firemodes = list(
list(mode_name = "select size",
@@ -34,7 +34,7 @@
. = ..()
select_size()
-/obj/item/gun/energy/sizegun/consume_next_projectile()
+/obj/item/gun/energy/sizegun/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
var/obj/projectile/beam/sizelaser/G = .
if(istype(G))
@@ -110,5 +110,5 @@
origin_tech = list(TECH_BLUESPACE = 4)
modifystate = "sizegun-shrink"
no_pin_required = 1
- battery_lock = 1
+ legacy_battery_lock = 1
firemodes = list()
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 14aa3f933823..e9ee0a33725c 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -28,6 +28,9 @@
charge_cost = 480
projectile_type = /obj/projectile/ion/pistol
+/obj/item/gun/energy/ionrifle/weak
+ projectile_type = /obj/projectile/ion/small
+
/obj/item/gun/energy/decloner
name = "biological demolecularisor"
desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements."
@@ -46,7 +49,7 @@
modifystate = "floramut"
cell_type = /obj/item/cell/device/weapon/recharge
no_pin_required = 1
- battery_lock = 1
+ legacy_battery_lock = 1
var/singleton/plantgene/gene = null
firemodes = list(
@@ -77,9 +80,7 @@
to_chat(usr, "You set the [src]'s targeted genetic area to [genemask].")
- return
-
-/obj/item/gun/energy/floragun/consume_next_projectile()
+/obj/item/gun/energy/floragun/consume_next_projectile(datum/gun_firing_cycle/cycle)
. = ..()
var/obj/projectile/energy/floramut/gene/G = .
if(istype(G))
@@ -142,7 +143,7 @@
projectile_type = /obj/projectile/change
origin_tech = null
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
charge_meter = 0
/obj/item/gun/energy/staff/special_check(var/mob/user)
@@ -184,6 +185,24 @@
projectile_type = "/obj/projectile/forcebolt"
*/
+/datum/firemode/energy/dakkalaser
+ burst_delay = 0.1 SECONDS
+
+/datum/firemode/energy/dakkalaser/single
+ name = "1-shot"
+ burst_amount = 1
+ legacy_direct_varedits = list(dispersion = list(0), charge_cost = 24)
+
+/datum/firemode/energy/dakkalaser/five
+ name = "5-burst"
+ burst_amount = 5
+ legacy_direct_varedits = list(burst_accuracy = list(75,75,75,75,75), dispersion = list(1,1,1,1,1))
+
+/datum/firemode/energy/dakkalaser/ten
+ name = "10-burst"
+ burst_amount = 10
+ legacy_direct_varedits = list(burst_accuracy = list(75,75,75,75,75,75,75,75,75,75), dispersion = list(2,2,2,2,2,2,2,2,2,2))
+
/obj/item/gun/energy/dakkalaser
name = "suppression gun"
desc = "Coined 'Sparkers' by Tyrmalin dissidents on Larona upon it's inception, the HI-LLG is an energy-based suppression system, used to overwhelm the opposition in a hail of laser blasts."
@@ -195,17 +214,16 @@
charge_cost = 24 // 100 shots, it's a spray and pray (to RNGesus) weapon.
projectile_type = /obj/projectile/energy/blue_pellet
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
accuracy = 75 // Suppressive weapons don't work too well if there's no risk of being hit.
- burst_delay = 1 // Burst faster than average.
origin_tech = list(TECH_COMBAT = 6, TECH_MAGNET = 6, TECH_ILLEGAL = 6)
one_handed_penalty = 60
firemodes = list(
- list(mode_name="single shot", burst = 1, burst_accuracy = list(75), dispersion = list(0), charge_cost = 24),
- list(mode_name="five shot burst", burst = 5, burst_accuracy = list(75,75,75,75,75), dispersion = list(1,1,1,1,1)),
- list(mode_name="ten shot burst", burst = 10, burst_accuracy = list(75,75,75,75,75,75,75,75,75,75), dispersion = list(2,2,2,2,2,2,2,2,2,2)),
- )
+ /datum/firemode/energy/dakkalaser/one,
+ /datum/firemode/energy/dakkalaser/five,
+ /datum/firemode/energy/dakkalaser/ten,
+ )
/obj/item/gun/energy/maghowitzer
name = "portable MHD howitzer"
@@ -221,7 +239,7 @@
charge_cost = 10000 // Uses large cells, can at max have 3 shots.
projectile_type = /obj/projectile/beam/tungsten
cell_type = /obj/item/cell/high
- accept_cell_type = /obj/item/cell
+ cell_system_legacy_use_device = FALSE
accuracy = 75
charge_meter = 0
@@ -249,7 +267,7 @@
var/beameffect = user.Beam(target_turf,icon_state="sat_beam",icon='icons/effects/beam.dmi',time=31, maxdistance=10,beam_type=/obj/effect/ebeam)
if(beameffect)
user.visible_message("[user] aims \the [src] at \the [A].")
- if(power_supply && power_supply.charge >= charge_cost) //Do a delay for pointblanking too.
+ if(obj_cell_slot.cell && obj_cell_slot.cell.charge >= charge_cost) //Do a delay for pointblanking too.
power_cycle = TRUE
if(do_after(user, 30))
if(A.loc == target_turf)
@@ -292,16 +310,13 @@
else
if(beameffect)
qdel(beameffect)
- handle_click_empty(user)
+ var/datum/gun_firing_cycle/cycle = new
+ cycle.firing_actor = new /datum/event_args/actor(user)
+ post_empty_fire(cycle)
power_cycle = FALSE
else
to_chat(user, "\The [src] is already powering up!")
-//_vr Items:
-
-/obj/item/gun/energy/ionrifle/weak
- projectile_type = /obj/projectile/ion/small
-
/obj/item/gun/energy/medigun //Adminspawn/ERT etc
name = "directed restoration system"
desc = "The BL-3 'Phoenix' is an adaptation on the ML-3 'Medbeam' design that channels the power of the beam into a single healing laser. It is highly energy-inefficient, but its medical power cannot be denied."
@@ -311,101 +326,17 @@
icon = 'icons/obj/gun/energy.dmi'
slot_flags = SLOT_BELT
accuracy = 100
- fire_delay = 12
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 1.2 SECONDS;
+ }
fire_sound = 'sound/weapons/eluger.ogg'
projectile_type = /obj/projectile/beam/medigun
- accept_cell_type = /obj/item/cell
+ cell_system_legacy_use_device = FALSE
cell_type = /obj/item/cell/high
charge_cost = 2500
-/obj/item/gun/energy/service
- name = "service weapon"
- icon_state = "service_grip"
- item_state = "service_grip"
- desc = "An anomalous weapon, long kept secure. It has recently been acquired by Nanotrasen's Paracausal Monitoring Division. How did it get here?"
- damage_force = 5
- slot_flags = SLOT_BELT
- w_class = WEIGHT_CLASS_NORMAL
- projectile_type = /obj/projectile/bullet/pistol/medium/silver
- origin_tech = null
- fire_delay = 10 //Old pistol
- charge_cost = 480 //to compensate a bit for self-recharging
- cell_type = /obj/item/cell/device/weapon/recharge/captain
- battery_lock = 1
- one_handed_penalty = 0
- safety_state = GUN_SAFETY_OFF
-
-/obj/item/gun/energy/service/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- cycle_weapon(user)
-
-/obj/item/gun/energy/service/proc/cycle_weapon(mob/living/L)
- var/obj/item/service_weapon
- var/list/service_weapon_list = subtypesof(/obj/item/gun/energy/service)
- var/list/display_names = list()
- var/list/service_icons = list()
- for(var/V in service_weapon_list)
- var/obj/item/gun/energy/service/weapontype = V
- if (V)
- display_names[initial(weapontype.name)] = weapontype
- service_icons += list(initial(weapontype.name) = image(icon = initial(weapontype.icon), icon_state = initial(weapontype.icon_state)))
-
- service_icons = sortList(service_icons)
-
- var/choice = show_radial_menu(L, src, service_icons)
- if(!choice || !check_menu(L))
- return
-
- var/A = display_names[choice] // This needs to be on a separate var as list member access is not allowed for new
- service_weapon = new A
-
- if(service_weapon)
- qdel(src)
- L.put_in_active_hand(service_weapon)
-
-/obj/item/gun/energy/service/proc/check_menu(mob/user)
- if(!istype(user))
- return FALSE
- if(QDELETED(src))
- return FALSE
- if(user.incapacitated())
- return FALSE
- return TRUE
-
-/obj/item/gun/energy/service/grip
-
-/obj/item/gun/energy/service/shatter
- name = "service weapon (shatter)"
- icon_state = "service_shatter"
- projectile_type = /obj/projectile/bullet/pellet/shotgun/silvershot
- fire_delay = 15 //Increased by 50% for strength.
- charge_cost = 600 //Charge increased due to shotgun round.
-
-/obj/item/gun/energy/service/spin
- name = "service weapon (spin)"
- icon_state = "service_spin"
- projectile_type = /obj/projectile/bullet/pistol/spin
- fire_delay = 0 //High fire rate.
- charge_cost = 80 //Lower cost per shot to encourage rapid fire.
-
-/obj/item/gun/energy/service/pierce
- name = "service weapon (pierce)"
- icon_state = "service_pierce"
- projectile_type = /obj/projectile/bullet/rifle/a762/ap/silver
- fire_delay = 15 //Increased by 50% for strength.
- charge_cost = 600 //Charge increased due to sniper round.
-
-/obj/item/gun/energy/service/charge
- name = "service weapon (charge)"
- icon_state = "service_charge"
- projectile_type = /obj/projectile/bullet/burstbullet/service //Formerly: obj/projectile/bullet/gyro. A little too robust.
- fire_delay = 20
- charge_cost = 800 //Three shots.
-
/obj/item/gun/energy/puzzle_key
name = "Key of Anak-Hun-Tamuun"
desc = "An arcane stave that fires a powerful energy blast. Why was this just left laying around here?"
@@ -415,11 +346,13 @@
item_state = "staffofchaos"
damage_force = 5
charge_meter = 0
- projectile_type = /obj/projectile/beam/emitter
- fire_delay = 10
- charge_cost = 800
+ firemodes = /datum/firemode/energy{
+ projectile_type = /obj/projectile/beam/emitter;
+ cycle_cooldown = 1 SECONDS;
+ charge_cost = 2400 / 3;
+ }
cell_type = /obj/item/cell/device/weapon/recharge/captain
- battery_lock = 1
+ legacy_battery_lock = 1
one_handed_penalty = 0
/obj/item/gun/energy/ermitter
@@ -428,10 +361,12 @@
icon_state = "ermitter_gun"
item_state = "pulse"
projectile_type = /obj/projectile/beam/emitter
- fire_delay = 2 SECONDS
+ firemodes = /datum/firemode/energy{
+ cycle_cooldown = 2 SECONDS;
+ }
charge_cost = 900
cell_type = /obj/item/cell
- accept_cell_type = /obj/item/cell
+ cell_system_legacy_use_device = FALSE
slot_flags = SLOT_BELT|SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
heavy = TRUE
@@ -452,17 +387,31 @@
desc = "Deceptively primitive in appearance, this finely tuned rifle uses an onboard reactor to stimulate the growth of an anomalous crystal. Fragments of this crystal are utilized as ammunition by the weapon."
icon_state = "warplockgun"
item_state = "huntrifle"
- projectile_type = /obj/projectile/bullet/cyanideround/jezzail
- fire_delay = 20
- charge_cost = 600
+ firemodes = /datum/firemode/energy{
+ projectile_type = /obj/projectile/bullet/cyanideround/jezzail;
+ cycle_cooldown = 2 SECONDS;
+ charge_cost = 2400 / 4;
+ }
cell_type = /obj/item/cell/device/weapon
- battery_lock = 1
+ legacy_battery_lock = 1
slot_flags = SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
heavy = TRUE
damage_force = 10
one_handed_penalty = 60
+// todo: nuke plasma weapons from orbit and rework
+/datum/firemode/energy/plasma
+ cycle_cooldown = 2 SECONDS
+
+/datum/firemode/energy/plasma/normal
+ name = "standard"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/plasma, charge_cost = 350)
+
+/datum/firemode/energy/plasma/high
+ name = "high power"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/plasma/hot, charge_cost = 370)
+
//Plasma Guns Plasma Guns!
/obj/item/gun/energy/plasma
name = "\improper Balrog plasma rifle"
@@ -470,7 +419,6 @@
icon_state = "prifle"
item_state = null
projectile_type = /obj/projectile/plasma
- fire_delay = 20
charge_cost = 400
cell_type = /obj/item/cell/device/weapon
slot_flags = SLOT_BELT|SLOT_BACK
@@ -483,34 +431,13 @@
var/overheating = 0
firemodes = list(
- list(mode_name="standard", projectile_type=/obj/projectile/plasma, charge_cost = 350),
- list(mode_name="high power", projectile_type=/obj/projectile/plasma/hot, charge_cost = 370),
- )
-
-/obj/item/gun/energy/plasma/update_icon()
- . = ..()
- if(overheating)
- icon_state = "prifle_overheat"
- update_held_icon()
- else
- return
+ /datum/firemode/energy/plasma/normal,
+ /datum/firemode/energy/plasma/high,
+ )
-/obj/item/gun/energy/plasma/consume_next_projectile(mob/user as mob)
- . = ..()
- if(src.projectile_type == /obj/projectile/plasma/hot)
- switch(rand(1,6))
- if(1)
- to_chat(user, "The containment coil catastrophically overheats!")
- overheating = 1
- spawn(rand(2 SECONDS,5 SECONDS))
- if(src)
- visible_message("\The [src] detonates!")
- explosion(get_turf(src), -1, 0, 2, 3)
- qdel(chambered)
- qdel(src)
- return ..()
- if(2 to 6)
- return ..()
+/obj/item/gun/energy/plasma/update_icon_state()
+ icon_state = "[initial(icon_state)][overheating ? "_overheat" : ""]"
+ return ..()
/obj/item/gun/energy/plasma/pistol
name = "\improper Wyrm plasma pistol"
@@ -523,29 +450,3 @@
origin_tech = list(TECH_COMBAT = 6, TECH_ENGINEERING = 5, TECH_MAGNET = 5)
materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 2000)
one_handed_penalty = 10
-
-/obj/item/gun/energy/plasma/pistol/update_icon()
- . = ..()
- if(overheating)
- icon_state = "ppistol_overheat"
- update_held_icon()
- else
- return
-
-/obj/item/gun/energy/plasma/pistol/consume_next_projectile(mob/user as mob)
- . = ..()
- if(.)
- if(src.projectile_type == /obj/projectile/plasma/hot)
- switch(rand(1,6))
- if(1)
- to_chat(user, "The containment coil catastrophically overheats!")
- overheating = 1
- spawn(rand(2 SECONDS,5 SECONDS))
- if(src)
- visible_message("\The [src] detonates!")
- explosion(get_turf(src), -1, 0, 2, 3)
- qdel(chambered)
- qdel(src)
- return ..()
- if(2 to 6)
- return ..()
diff --git a/code/modules/projectiles/guns/energy/special/hardlight_bow.dm b/code/modules/projectiles/guns/energy/special/hardlight_bow.dm
index 1b6ce64b3a92..496bf3f4b2f8 100644
--- a/code/modules/projectiles/guns/energy/special/hardlight_bow.dm
+++ b/code/modules/projectiles/guns/energy/special/hardlight_bow.dm
@@ -7,7 +7,7 @@
item_state = "bow_pipe"
slot_flags = SLOT_BACK | SLOT_BELT
charge_cost = 1200
- battery_lock = 1
+ legacy_battery_lock = 1
pin = /obj/item/firing_pin/explorer
projectile_type = /obj/projectile/ion
@@ -24,7 +24,7 @@
if(!do_after(user, 10, src))
break
playsound(get_turf(src),'sound/weapons/hardlight_bow_charge.ogg',25,1)
- if(power_supply.give(phase_power) < phase_power)
+ if(obj_cell_slot?.cell?.give(phase_power) < phase_power)
break
recharging = 0
diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm
index 347b32ad1c95..4228596e3adb 100644
--- a/code/modules/projectiles/guns/energy/stun.dm
+++ b/code/modules/projectiles/guns/energy/stun.dm
@@ -1,20 +1,29 @@
+/datum/firemode/energy/taser
+ cycle_cooldown = 0.4 SECONDS
+
+/datum/firemode/energy/taser/stun
+ name = "stun"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/energy/electrode, modifystate="taser", charge_cost = 240)
+
+/datum/firemode/energy/taser/disable
+ name = "disable"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/disabler/weak, modifystate="taserblue", charge_cost = 160)
+
/obj/item/gun/energy/taser
name = "taser gun"
desc = "The NT Mk31 NL is a small gun used for non-lethal takedowns. An NT exclusive iteration of the Mk30 WT design, the Mk31 features a variable output mechanism which draws from a singular power source, allowing for versatile firing solutions without increased weight."
+ description_info = "This is an energy weapon. To fire the weapon, ensure your intent is *not* set to 'help', have your gun mode set to 'fire', \
+ then click where you want to fire. Most energy weapons can fire through windows harmlessly. To recharge this weapon, use a weapon recharger."
icon_state = "taser"
item_state = null //so the human update icon uses the icon_state instead.
- fire_delay = 4
-
worth_intrinsic = 350
-
- projectile_type = /obj/projectile/energy/electrode
modifystate = "taser"
firemodes = list(
- list(mode_name="stun", projectile_type=/obj/projectile/energy/electrode, modifystate="taser", charge_cost = 240),
- list(mode_name="disable", projectile_type=/obj/projectile/beam/disabler/weak, modifystate="taserblue", charge_cost = 160),
- )
+ /datum/firemode/energy/taser/stun,
+ /datum/firemode/energy/taser/disable,
+ )
/obj/item/gun/energy/taser/mounted
name = "mounted taser gun"
@@ -60,7 +69,7 @@
projectile_type = /obj/projectile/energy/bolt
charge_cost = 480
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
charge_meter = 0
/obj/item/gun/energy/crossbow/ninja
@@ -82,9 +91,11 @@
icon_state = "plasma_stun"
item_state = "plasma_stun"
origin_tech = list(TECH_COMBAT = 2, TECH_MATERIAL = 2, TECH_POWER = 3)
- fire_delay = 20
- charge_cost = 600
- projectile_type = /obj/projectile/energy/plasmastun
+ firemodes = /datum/firemode/energy{
+ projectile_type = /obj/projectile/energy/plasmastun;
+ cycle_cooldown = 2 SECONDS;
+ charge_cost = 2400 / 4;
+ }
one_handed_penalty = 5
/obj/item/gun/energy/civtas
@@ -93,7 +104,8 @@
icon_state = "civtas"
item_state = "concealed"
origin_tech = list(TECH_COMBAT = 2, TECH_MATERIAL = 3, TECH_POWER = 3)
- projectile_type = /obj/projectile/energy/electrode/stunshot
- fire_delay = 4
- charge_cost = 1500
- cell_type = /obj/item/cell/device/weapon
+ firemodes = /datum/firemode/energy{
+ projectile_type = /obj/projectile/energy/electrode/stunshot;
+ cycle_cooldown = 0.4 SECONDS;
+ charge_cost = 2400 / 2;
+ }
diff --git a/code/modules/projectiles/guns/firemode.dm b/code/modules/projectiles/guns/firemode.dm
new file mode 100644
index 000000000000..a3f717e2744a
--- /dev/null
+++ b/code/modules/projectiles/guns/firemode.dm
@@ -0,0 +1,81 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/firemode
+ /// The name of the firemode. This is what is shown in VV, **and** to players.
+ var/name = "normal"
+
+ //* firing *//
+ /// number of shots in burst
+ var/burst_amount = 1
+ /// delay between burst shots
+ var/burst_delay = 0.2 SECONDS
+ /// delay **after** the firing cycle which we cannot fire
+ var/cycle_cooldown = 0.4 SECONDS
+
+ //* rendering *//
+ /// state key for rendering, if any
+ var/render_key
+ /// firemode color, used if we're doing colored `-firemode` sprite or colored `-ammo` sprite
+ var/render_color
+ #warn impl
+
+ //* UI *//
+ /// appearance used for radial
+ ///
+ /// supported values:
+ /// * /image
+ /// * /mutable_appearance
+ ///
+ /// this must be created in [make_radial_appearance()] as this cannot be set to image() or similar at compile time
+ var/radial_appearance
+
+ //* LEGACY *//
+ /// direct vv edits to the gun applied when we're selected.
+ ///
+ /// * this is shit, but it is what it is, for now. we're migrating things out of
+ /// it, slowly.
+ /// * passed in variables from direct varedits in New() will be append-overwrite
+ /// for this list.
+ var/list/legacy_direct_varedits
+
+// todo: this shouldn't even exist.
+/datum/firemode/New(obj/item/gun/inherit_from_gun, list/direct_varedits)
+ if(!length(direct_varedits))
+ return
+ for(var/varname in direct_varedits)
+ var/value = direct_varedits[varname]
+ // pull out special crap
+ switch(varname)
+ if("mode_name")
+ src.name = value
+ if("burst")
+ src.burst_amount = value
+ if("fire_delay")
+ src.cycle_cooldown = value
+ if("burst_delay")
+ src.burst_delay = value
+ LAZYSET(legacy_direct_varedits, varname, value || inherit_from_gun.vars[varname])
+
+/datum/firemode/clone(include_contents)
+ var/datum/firemode/creating = new type
+ creating.name = name
+ creating.burst_amount = burst_amount
+ creating.burst_delay = burst_delay
+ creating.cycle_cooldown = cycle_cooldown
+ creating.render_color = render_color
+ creating.render_key = render_key
+ // todo: kill
+ creating.legacy_direct_varedits = deep_copy_list(legacy_direct_varedits)
+ return creating
+
+// todo: annihilate this
+/datum/firemode/proc/apply_legacy_variables(obj/item/gun/gun)
+ for(var/varname in legacy_direct_varedits)
+ gun.vars[varname] = legacy_direct_varedits[varname]
+
+/datum/firemode/proc/fetch_radial_appearance()
+ return radial_appearance || (radial_appearance = make_radial_appearance())
+
+/datum/firemode/proc/make_radial_appearance()
+ return
diff --git a/code/modules/projectiles/firing_pin.dm b/code/modules/projectiles/guns/firing_pin.dm
similarity index 91%
rename from code/modules/projectiles/firing_pin.dm
rename to code/modules/projectiles/guns/firing_pin.dm
index bc63a65de4f1..4d06825e3637 100644
--- a/code/modules/projectiles/firing_pin.dm
+++ b/code/modules/projectiles/guns/firing_pin.dm
@@ -1,3 +1,14 @@
+/**
+ * Firing pins used to pretty much control who can use how many guns.
+ *
+ * The old system was lockboxes; those weren't really fun and there wasn't a good way
+ * to bypass it without an emag.
+ *
+ * Nowadays we just use firing pins and control who can print those.
+ *
+ * In the future, this system may be augmented or replaced, as to make it more
+ * valuable to have a weapon (as opposed to a pin for one).
+ */
/obj/item/firing_pin
name = "electronic firing pin"
desc = "A small authentication device, to be inserted into a firearm receiver to allow operation. NT safety regulations require all new designs to incorporate one."
@@ -84,7 +95,6 @@
return TRUE
return FALSE
-
// Implant pin, checks for implant
/obj/item/firing_pin/implant
name = "implant-keyed firing pin"
@@ -197,20 +207,20 @@
return lock_override
//Allows swiping an armoury access ID on an explorer locked gun to unlock it
-/obj/item/gun/attackby(obj/item/I, mob/user)
+/obj/item/gun/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
if((istype(I, /obj/item/card/id)) && pin)
pin.attackby(I, user)
else
return ..()
-/obj/item/firing_pin/explorer/attackby(obj/item/card/ID, mob/user)
+/obj/item/firing_pin/explorer/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
..()
- if(check_access(ID))
+ if(check_access(I))
locked = !locked
to_chat(user, "You [locked ? "enable" : "disable"] the safety lock on \the [src].")
else
to_chat(user, "Access denied.")
- user.visible_message("[user] swipes \the [ID] against \the [src].")
+ user.visible_message("[user] swipes \the [I] against \the [src].")
/obj/item/firing_pin/emag_act(var/remaining_charges, var/mob/user)
if(emagged)
diff --git a/code/modules/projectiles/guns/gun-firing.dm b/code/modules/projectiles/guns/gun-firing.dm
new file mode 100644
index 000000000000..d65554056749
--- /dev/null
+++ b/code/modules/projectiles/guns/gun-firing.dm
@@ -0,0 +1,190 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Firing Cycle *//
+
+/**
+ * async proc to start a firing cycle
+ *
+ * * firer is where the will actually come out of.
+ * * if firer is a turf, projectile is centered on turf
+ * * if firer is a mob, we use its calculations for that depending on how we're held
+ * * if firer is ourselves, projectile comes out of us. this is implementation defined.
+ *
+ * @return firing cycle datum
+ */
+/obj/item/gun/proc/start_firing_cycle_async(atom/firer, angle, firing_flags, datum/firemode/firemode, atom/target, datum/event_args/actor/actor) as /datum/gun_firing_cycle
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ // invoke async; when it returns, our firing_cycle will still be set
+ INVOKE_ASYNC(PROC_REF(firing_cycle), firer, angle, firing_flags, firemode, target, actor)
+ // check to make sure it's always set
+ ASSERT(firing_cycle)
+ // return it; beware that it can be mutated in the firing cycle.
+ return firing_cycle
+
+/**
+ * starts, and blocks on a firing cycle
+ *
+ * * firer is where the will actually come out of.
+ * * if firer is a turf, projectile is centered on turf
+ * * if firer is a mob, we use its calculations for that depending on how we're held
+ * * if firer is ourselves, projectile comes out of us. this is implementation defined.
+ */
+/obj/item/gun/proc/start_firing_cycle(atom/firer, angle, firing_flags, datum/firemode/firemode, atom/target, datum/event_args/actor/actor) as /datum/gun_firing_cycle
+ SHOULD_CALL_PARENT(TRUE)
+ #warn check next fire time / delays; silently fail if there's a cycle ongoing or right after, and give a message if there isn't
+ // if(world.time < next_fire_time)
+ // if (world.time % 3) //to prevent spam
+ // to_chat(user, "[src] is not ready to fire again!")
+
+ //! LEGACY
+ if(!special_check(actor?.performer))
+ return
+ //! END
+
+ return firing_cycle(firer, angle, firing_flags, firemode, target, actor)
+
+/**
+ * interrupts a given firing cycle ID; if none is provided, we interrupt any active firing cycle.
+ */
+/obj/item/gun/proc/interrupt_firing_cycle(cycle_id)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ if(cycle_id && firing_cycle?.cycle_notch != cycle_id)
+ return
+ firing_cycle = null
+
+/**
+ * Hook for firing cycle start
+ */
+/obj/item/gun/proc/on_firing_cycle_start(datum/gun_firing_cycle/cycle)
+ SHOULD_NOT_SLEEP(TRUE)
+
+/**
+ * Hook for firing cycle end
+ */
+/obj/item/gun/proc/on_firing_cycle_end(datum/gun_firing_cycle/cycle)
+ SHOULD_NOT_SLEEP(TRUE)
+ update_icon()
+
+/**
+ * called exactly once at the start of a firing cycle to start it
+ *
+ * @params
+ * * firer - the thing physically firing us; whether a turret or a person.
+ * this is where the projectile will originate regardles of where the gun actually is!
+ * * angle - the angle to fire in.
+ * * firing_flags - GUN_FIRING_* flags
+ * * firemode - (optional) the /datum/firemode we are firing on
+ * * target - (optional) what we're firing at
+ * * actor - (optional) the person who initiated the firing
+ *
+ * @return the gun firing cycle made and used
+ */
+/obj/item/gun/proc/firing_cycle(atom/firer, angle, firing_flags, datum/firemode/firemode, atom/target, datum/event_args/actor/actor) as /datum/gun_firing_cycle
+ SHOULD_NOT_OVERRIDE(TRUE)
+ PRIVATE_PROC(TRUE) // only base of /start_firing_cycle is allowed to call us
+
+ /**
+ * As a word of warning, any proc called in this proc must be SHOULD_NOT_SLEEP.
+ * If this is ever violated bad things may happen and things may explode.
+ */
+ #warn logging
+ #warn default firemode
+
+ // create cycle
+ var/datum/gun_firing_cycle/our_cycle = new
+ our_cycle.firing_flags = firing_flags
+ our_cycle.original_angle = angle
+ our_cycle.original_target = target
+ our_cycle.firemode = firemode
+ our_cycle.firing_actor = actor
+ our_cycle.firing_atom = firer
+ our_cycle.firing_iterations = firemode.burst_amount
+ our_cycle.firing_delay = firemode.burst_delay
+ // cycle notch
+ our_cycle.cycle_notch = ++firing_cycle_next
+ if(firing_cycle_next >= SHORT_REAL_LIMIT)
+ firing_cycle_next = -(SHORT_REAL_LIMIT - 1)
+ // record start
+ our_cycle.cycle_start_time = world.time
+ // begin
+ firing_cycle = our_cycle
+ // send start hooks
+ on_firing_cycle_start(our_cycle)
+ SEND_SIGNAL(src, COMSIG_GUN_FIRING_CYCLE_START, our_cycle)
+
+ var/safety = 50
+ var/iteration = 0
+ while(iteration < our_cycle.firing_iterations)
+ // loop guard
+ --safety
+ if(safety <= 0)
+ CRASH("safety ran out during firing cycle")
+ // increment iteration; track it locally too, just in case
+ ++iteration
+ our_cycle.cycle_iterations_fired = iteration
+ // fire signal
+ SEND_SIGNAL(src, COMSIG_GUN_FIRING_CYCLE_ITERATION_PREFIRE, our_cycle)
+ // fire
+ our_cycle.last_firing_result = fire(our_cycle)
+ // post-fire
+ if(!post_fire(our_cycle))
+ break
+ // reset variables
+ our_cycle.next_dispersion_adjust = our_cycle.next_angle_adjust = null
+ // continue if needed
+ if(iteration != our_cycle.firing_iterations)
+ sleep(our_cycle.firing_delay)
+ if(firing_cycle != our_cycle)
+ our_cycle.last_interrupted = TRUE
+ break
+
+ // send end hooks
+ on_firing_cycle_end(our_cycle)
+ SEND_SIGNAL(src, COMSIG_GUN_FIRING_CYCLE_END, our_cycle)
+
+ return our_cycle
+
+//* Firing *//
+
+/**
+ * Called to handle post fire
+ *
+ * @return FALSE to abort firing cycle
+ */
+/obj/item/gun/proc/post_fire(datum/gun_firing_cycle/cycle)
+ SHOULD_NOT_SLEEP(TRUE)
+ switch(cycle.last_firing_result)
+ if(GUN_FIRED_SUCCESS)
+ cycle.cycle_iterations_fired++
+ return TRUE
+ if(GUN_FIRED_FAIL_EMPTY, GUN_FIRED_FAIL_INERT)
+ return post_empty_fire(cycle)
+ else
+ return FALSE
+
+//* Firing - Default Handlers (Overridable) *//
+
+/**
+ * Called if someone tries to fire us without live ammo in the chamber (or chamber-equivalent)
+ *
+ * @return FALSE to abort firing cycle.
+ */
+/obj/item/gun/proc/post_empty_fire(datum/gun_firing_cycle/cycle)
+ if(!(cycle.firing_flags & GUN_FIRING_NO_CLICK_EMPTY))
+ // default click empty
+ default_click_empty(cycle)
+ return FALSE
+
+// todo: actor / event_args support
+/obj/item/gun/proc/default_click_empty(datum/gun_firing_cycle/cycle)
+ var/mob/holding_us = worn_mob()
+ if(holding_us)
+ holding_us.visible_message(SPAN_WARNING("*click click*"), SPAN_WARNING("*click*"))
+ else if(isturf(loc))
+ visible_message(SPAN_WARNING("*click click*"), SPAN_WARNING("*click*"))
+ playsound(src, 'sound/weapons/empty.ogg', 75, TRUE)
diff --git a/code/modules/projectiles/guns/gun-modular.dm b/code/modules/projectiles/guns/gun-modular.dm
new file mode 100644
index 000000000000..2681de280c55
--- /dev/null
+++ b/code/modules/projectiles/guns/gun-modular.dm
@@ -0,0 +1,58 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Modular Components - Compatibility *//
+
+/**
+ * hard check
+ */
+/obj/item/gun/proc/can_install_component(obj/item/gun_component/component, datum/event_args/actor/actor, silent, force)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ var/is_full = FALSE
+ #warn slot enforcement
+ return force || component.fits_on_gun(src, fits_modular_component(component), is_full, actor, silent)
+
+/**
+ * checks if we can attach a component; component gets final say
+ */
+/obj/item/gun/proc/fits_modular_component(obj/item/gun_component/component, datum/event_args/actor/actor, silent)
+ return TRUE
+
+//* Modular Components - Add / Remove *//
+
+/**
+ * * moves the component into us if it wasn't already
+ */
+/obj/item/gun/proc/attach_modular_component(obj/item/gun_component/component, datum/event_args/actor/actor, silent, force)
+ #warn impl
+
+/**
+ * * deletes the component if no location is provided to move it to
+ */
+/obj/item/gun/proc/detach_modular_component(obj/item/gun_component/component, datum/event_args/actor/actor, silent, atom/new_loc)
+ #warn impl
+
+#warn hook everything in attackby's
+
+//* Modular Components - API *//
+
+/**
+ * Try to use a certain amount of power.
+ *
+ * @return amount used
+ */
+/obj/item/gun/proc/modular_use_power(obj/item/gun_component/component, joules)
+ return obj_cell_slot?.use(DYNAMIC_J_TO_CELL_UNITS(joules))
+
+/**
+ * Try to use a certain amount of power. Fails if insufficient.
+ *
+ * @params
+ * * component - the component drawing power
+ * * joules - how much power to use, in joules
+ * * reserve - how many joules must be remaining after use, in joules
+ *
+ * @return amount used
+ */
+/obj/item/gun/proc/modular_use_checked_power(obj/item/gun_component/component, joules, reserve)
+ return obj_cell_slot?.checked_use(DYNAMIC_J_TO_CELL_UNITS(joules), reserve)
diff --git a/code/modules/projectiles/guns/gun-projectile-implementation.dm b/code/modules/projectiles/guns/gun-projectile-implementation.dm
new file mode 100644
index 000000000000..4c9a84ccb13a
--- /dev/null
+++ b/code/modules/projectiles/guns/gun-projectile-implementation.dm
@@ -0,0 +1,80 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * tl;dr
+ *
+ * we want eventually /gun/projectile so we don't have to have special behavior on /gun/launcher
+ * and similar 'guns' that aren't actually projectile guns
+ *
+ * this way we have separation between behaviors only needed on guns that shoot
+ * /obj/projectile's. that said, this is a little annoying to do (path length bloat)
+ * so for now we put the projectile procs in their own file.
+ */
+
+/**
+ * called to perform a single firing operation
+ */
+/obj/item/gun/proc/fire(datum/gun_firing_cycle/cycle)
+ SHOULD_NOT_SLEEP(TRUE)
+ #warn impl; check unmount
+
+ // handle legacy systems
+ var/held_twohanded = TRUE
+ if(ismob(cycle.firing_atom))
+ var/mob/mob_firer = cycle.firing_atom
+ // todo: proper twohanding system
+ held_twohanded = mob_firer.can_wield_item(src) && is_held_twohanded(mob_firer)
+ mob_firer.break_cloak()
+
+ // point of no return
+ var/obj/projectile/firing_projectile = consume_next_projectile(cycle)
+ if(!istype(firing_projectile))
+ // it's an error code if it's not real
+ return firing_projectile
+
+ //! LEGACY
+ process_accuracy(firing_projectile, cycle.firing_actor?.performer, cycle.original_target, cycle.cycle_iterations_fired, held_twohanded)
+ // todo: this is ass because if the projectile misses we still get additional damage
+ // todo: Reachability(), not Adjacent().
+ if((cycle.firing_flags & GUN_FIRING_POINT_BLANK) && cycle.original_target && cycle.firing_atom.Adjacent(cycle.original_target))
+ process_point_blank(firing_projectile, cycle.firing_actor?.performer, cycle.original_target)
+ play_fire_sound(cycle.firing_actor?.performer, firing_projectile)
+ //! END
+
+ // record stuff
+ last_fire = world.time
+
+ // todo: do we really need to newtonian move always?
+ if(ismovable(cycle.firing_atom))
+ var/atom/movable/movable_firer = cycle.firing_atom
+ movable_firer.newtonian_move(angle2dir(cycle.original_angle))
+
+ // todo: muzzle flash
+
+/**
+ * Called to actually fire a projectile.
+ */
+/obj/item/gun/proc/launch_projectile(datum/gun_firing_cycle/cycle, obj/projectile/launching)
+ //! LEGACY
+ // this is just stupid lol why are we transcluding name directly into autopsy reports??
+ launching.shot_from = src.name
+ // this shouldn't be a hard-set thing and should be attachment set
+ launching.silenced = src.silenced
+ //! END
+
+/**
+ * Obtains the next projectile to fire.
+ *
+ * Either will return an /obj/projectile,
+ * or return a GUN_FIRED_* define that is not SUCCESS.
+ *
+ * * Things like jams go in here.
+ * * Things like 'the next bullet is empty so we fail' go in here
+ * * This should be called *as* the point of no return. This has side effects.
+ * * Everything is optional here. Things like portable turrets reserve the right to 'pull' from the gun without caring about params.
+ */
+/obj/item/gun/proc/consume_next_projectile(datum/gun_firing_cycle/cycle)
+ . = GUN_FIRED_FAIL_UNKNOWN
+ // todo: on base /gun/projectile?
+ CRASH("attempted to process next projectile on base /gun")
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/guns/gun.dm
similarity index 68%
rename from code/modules/projectiles/gun.dm
rename to code/modules/projectiles/guns/gun.dm
index 20d133ea8abd..e66f9bc340f1 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/guns/gun.dm
@@ -1,36 +1,39 @@
-/*
- Defines a firing mode for a gun.
- A firemode is created from a list of fire mode settings. Each setting modifies the value of the gun var with the same name.
- If the fire mode value for a setting is null, it will be replaced with the initial value of that gun's variable when the firemode is created.
- Obviously not compatible with variables that take a null value. If a setting is not present, then the corresponding var will not be modified.
-*/
-/datum/firemode
- var/name = "default"
- var/list/settings = list()
-
- /// state key for rendering, if any
- var/render_key
-
-/datum/firemode/New(obj/item/gun/gun, list/properties = null)
- ..()
- if(!properties) return
-
- for(var/propname in properties)
- var/propvalue = properties[propname]
-
- if(propname == "mode_name")
- name = propvalue
- if(isnull(propvalue))
- settings[propname] = gun.vars[propname] //better than initial() as it handles list vars like burst_accuracy
- else
- settings[propname] = propvalue
-
-/datum/firemode/proc/apply_to(obj/item/gun/gun)
- for(var/propname in settings)
- gun.vars[propname] = settings[propname]
-
-//Parent gun type. Guns are weapons that can be aimed at mobs and act over a distance
+/**
+ * # Guns
+ *
+ * A gun is a weapon that can be aimed and fired at someone or something over a distance.
+ *
+ * todo: /obj/item/gun/projectile vs /obj/item/gun/launcher,
+ * instead of have projectile be on /obj/item/gun
+ *
+ * ## Hotkey Priority
+ *
+ * The usable semantic hotkeys for guns are: Z, Spacebar, F, G.
+ * F, G are avoided as 'unique defensives' and something components
+ * need to be able to register to.
+ *
+ * todo: At some point, we'll need proper hotkey priority handling for items
+ * for the 'primary semantic keys' like active key/spacebar,
+ * F and G. For now, it's kind of a wild west where items define
+ * Z and Spacebar and F/G are usually component-hooked.
+ *
+ * The problem comes in that guns have **three** self-actions instead of two:
+ * - Wielding
+ * - Racking / chamber charging
+ * - Firemode switch
+ *
+ * This is annoying because semantically, the Z key should always have wielding,
+ * Spacebar should have racking behaviors if they exist, which means we don't
+ * have a spot for firemode switching.
+ *
+ * As of right now, wielding is not on all guns but that will change very soon.
+ * todo: Change that very soon.
+ * This means that Z key will never be available to guns for firemode switches.
+ *
+ * For now, we're winging it. This is just design notes for when we cross
+ * this hellish bridge.
+ */
/obj/item/gun
name = "gun"
desc = "Its a gun. It's pretty terrible, though."
@@ -62,12 +65,40 @@
/// * this is a default value; set to null by default to have the projectile's say.
var/accuracy_disabled = null
- // legacy below //
+ //* Firemode *//
+ /**
+ * The list of our possible firemodes.
+ *
+ * Firemodes may be;
+ *
+ * * an instance: this will be kept around per gun
+ * * an anonymous type (byond 'pop' object with /typepath{varedit = "abc";} syntax):
+ * this will be kept around per gun
+ * * a typepath: this will be globally cached
+ *
+ * This variable may either be a list, of the above, or a singular of the above.
+ */
+ var/list/firemodes = /datum/firemode
+ /// use radial for firemode
+ var/firemodes_use_radial = FALSE
+ #warn impl
+
+ //* Firing *//
+
+ /// the current firing cycle
+ ///
+ /// * to interrupt a firing cycle, just change it.
+ var/tmp/datum/gun_firing_cycle/firing_cycle
+ /// the next firing cycle
+ ///
+ /// * static var; technically can collide. realistically, won't.
+ var/static/firing_cycle_next = 0
+ /// last world.time we fired a shot
+ var/last_fire = 0
+ /// next world.time we can start a firing cycle
+ var/next_fire_cycle = 0
- var/burst = 1
- var/fire_delay = 6 //delay after shooting before the gun can be used again
- var/burst_delay = 2 //delay between shots, if firing in bursts
- var/move_delay = 1
+ // legacy below //
var/fire_sound = null // This is handled by projectile.dm's fire_sound var now, but you can override the projectile's fire_sound with this one if you want to.
var/fire_sound_text = "gunshot"
var/fire_anim = null
@@ -80,21 +111,16 @@
var/scoped_accuracy = null
var/list/burst_accuracy = list(0) //allows for different accuracies for each shot in a burst. Applied on top of accuracy
var/list/dispersion = list(0)
- var/mode_name = null
// todo: purge with fire
var/projectile_type = /obj/projectile //On ballistics, only used to check for the cham gun
- var/holy = FALSE //For Divinely blessed guns
// todo: this should be on /ballistic, and be `internal_chambered`.
var/obj/item/ammo_casing/chambered = null
var/wielded_item_state
var/one_handed_penalty = 0 // Penalty applied if someone fires a two-handed gun with one hand.
var/atom/movable/screen/auto_target/auto_target
- var/shooting = 0
- var/next_fire_time = 0
var/sel_mode = 1 //index of the currently selected mode
- var/list/firemodes = list()
var/selector_sound = 'sound/weapons/guns/selector.ogg'
//aiming system stuff
@@ -102,25 +128,15 @@
//0 for one bullet after tarrget moves and aim is lowered
var/multi_aim = 0 //Used to determine if you can target multiple people.
var/tmp/list/mob/living/aim_targets //List of who yer targeting.
- var/tmp/mob/living/last_moved_mob //Used to fire faster at more than one person.
- var/tmp/told_cant_shoot = 0 //So that it doesn't spam them with the fact they cannot hit them.
var/tmp/lock_time = -100
/// whether or not we have safeties and if safeties are on
var/safety_state = GUN_SAFETY_ON
- var/last_shot = 0 //records the last shot fired
-
var/charge_sections = 4
var/shaded_charge = FALSE
var/ammo_x_offset = 2
var/ammo_y_offset = 0
- var/can_flashlight = FALSE
- var/gun_light = FALSE
- var/light_state = "flight"
- var/light_brightness = 4
- var/flight_x_offset = 0
- var/flight_y_offset = 0
var/obj/item/firing_pin/pin = /obj/item/firing_pin
var/no_pin_required = 0
@@ -129,9 +145,46 @@
//Gun Malfunction variables
var/unstable = 0
var/destroyed = 0
+ var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
- //* Rendering *//
+ //* THIS IS A WIP SYSTEM!! *//
+ // todo: well, finish this.
+ //* Modular Components *//
+ //* Generalized, and efficient modular component support at base /gun *//
+ //* level. *//
+ /// System flag for using modular component system
+ ///
+ /// * Firing cycles are more expensive when modular components are invoked.
+ /// * This is because modular components use signal and API hooks that are not necessary for most guns.
+ /// * Thus, keep this off if it's not a modular weapon. It won't break it, but it's needless overhead.
+ var/modular_system = FALSE
+ /// currently installed components.
+ ///
+ /// * This is a lazy list.
+ var/list/obj/item/gun_component/modular_components
+ /// lazy way to set internal slots, because this is modified so often
+ ///
+ /// * literally not checked past init, it's used to generate the typelist
+ /// * if it's specified in the list, the list's copy is used instead.
+ var/modular_component_slots_internal = INFINITY
+ /// allowed component slots, associated to amount
+ ///
+ /// * this is typelist()'d; if you want to change it later, make a copy!
+ var/list/modular_component_slots
+
+ //* Power *//
+ //* Because the use of power is such a common case on /gun, it's been *//
+ //* hoisted to the base /obj/item/gun level for handling. *//
+
+ /// do we use a cell slot?
+ var/cell_system = FALSE
+ /// cell type to start with
+ var/cell_type = /obj/item/cell/device/weapon
+ /// -_-
+ var/cell_system_legacy_use_device = TRUE
+
+ //* Rendering *//
/// Used instead of base_icon_state for the mob renderer, if this exists.
var/base_mob_state
@@ -180,7 +233,7 @@
/obj/item/gun/Initialize(mapload)
. = ..()
- // instantiate & dedupe renderers
+ // instantiate & dedupe renderers //
var/requires_icon_update
if(item_renderer)
if(ispath(item_renderer) || IS_ANONYMOUS_TYPEPATH(item_renderer))
@@ -197,13 +250,17 @@
if(requires_icon_update)
update_icon()
- //! LEGACY: if neither of these are here, we are using legacy render.
+ //! LEGACY BELOW !//
+
+ // if neither of these are here, we are using legacy render. //
if(!item_renderer && !mob_renderer && render_use_legacy_by_default)
item_icons = list(
SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_guns.dmi',
SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_guns.dmi',
)
+ if(!islist(firemodes))
+ firemodes = list(firemodes)
for(var/i in 1 to firemodes.len)
var/key = firemodes[i]
if(islist(key))
@@ -222,22 +279,46 @@
if(pin)
pin = new pin(src)
-/obj/item/gun/CtrlClick(mob/user)
- if(can_flashlight && ishuman(user) && src.loc == usr && !user.incapacitated(INCAPACITATION_ALL))
- toggle_flashlight()
- else
- return ..()
+ //! LEGACY ABOVE !//
-/obj/item/gun/proc/toggle_flashlight()
- if(gun_light)
- set_light(0)
- gun_light = FALSE
- else
- set_light(light_brightness)
- gun_light = TRUE
+ // cell system //
+ if(cell_system)
+ var/datum/object_system/cell_slot/slot = init_cell_slot(cell_type)
+ slot.legacy_use_device_cells = cell_system_legacy_use_device
+ slot.remove_yank_offhand = TRUE
+ slot.remove_yank_context = TRUE
- playsound(src, 'sound/machines/button.ogg', 25)
- update_icon()
+ // modular components //
+ if(islist(modular_component_slots))
+ var/list/existing_typelist = get_typelist(NAMEOF(src, modular_component_slots))
+ if(existing_typelist)
+ modular_component_slots = existing_typelist
+ else
+ // if it's 1. a list and 2. we can't grab a typelist for it,
+ // we make it, patching internal modules lazily
+ var/internal_modules_patch = modular_component_slots[GUN_COMPONENT_INTERNAL_MODULE]
+ if(isnull(internal_modules_patch))
+ modular_component_slots[GUN_COMPONENT_INTERNAL_MODULE] = modular_component_slots_internal
+ modular_component_slots = typelist(NAMEOF(src, modular_component_slots), modular_component_slots)
+
+ #warn firemode action if needed
+
+/obj/item/gun/examine(mob/user, dist)
+ . = ..()
+ if(!no_pin_required)
+ if(pin)
+ . += "It has \a [pin] installed."
+ else
+ . += "It doesn't have a firing pin installed, and won't fire."
+ if(firemodes.len > 1)
+ var/datum/firemode/current_mode = firemodes[sel_mode]
+ . += "The fire selector is set to [current_mode.name]."
+ if(safety_state != GUN_NO_SAFETY)
+ to_chat(user, SPAN_NOTICE("The safety is [check_safety() ? "on" : "off"]."))
+ #warn component examine
+
+/obj/item/gun/CanItemAutoclick(object, location, params)
+ . = automatic
/obj/item/gun/update_twohanding()
if(one_handed_penalty)
@@ -265,7 +346,6 @@
item_state_slots[SLOT_ID_RIGHT_HAND] = initial(item_state)
..()
-
//Checks whether a given mob can use the gun
//Any checks that shouldn't result in handle_click_empty() being called if they fail should go here.
//Otherwise, if you want handle_click_empty() to be called, check in consume_next_projectile() and return null there.
@@ -281,31 +361,8 @@
return 0
if(!handle_pins(user))
return 0
-
- var/mob/living/M = user
- if(MUTATION_HULK in M.mutations)
- to_chat(M, "Your fingers are much too large for the trigger guard!")
- return 0
- if((MUTATION_CLUMSY in M.mutations) && prob(40)) //Clumsy handling
- var/obj/P = consume_next_projectile()
- if(P)
- if(process_projectile(P, user, user, pick("l_foot", "r_foot")))
- handle_post_fire(user, user)
- var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
- user.visible_message(
- "\The [user] shoots [TU.himself] in the foot with \the [src]!",
- "You shoot yourself in the foot with \the [src]!"
- )
- M.drop_item_to_ground(src)
- else
- handle_click_empty(user)
- return 0
return 1
-/obj/item/gun/emp_act(severity)
- for(var/obj/O in contents)
- O.emp_act(severity)
-
/obj/item/gun/dropped(mob/user, flags, atom/newLoc)
. = ..()
update_appearance()
@@ -324,6 +381,19 @@
if(!user.aiming)
user.aiming = new(user)
+ if(check_safety())
+ //If we are on harm intent (intending to injure someone) but forgot to flick the safety off, there is a 50% chance we
+ //will reflexively do it anyway
+ if(user.a_intent == INTENT_HARM && prob(50))
+ toggle_safety(user)
+ else
+ handle_click_safety(user)
+ return
+
+ if(!user?.client?.get_preference_toggle(/datum/game_preference_toggle/game/help_intent_firing) && user.a_intent == INTENT_HELP)
+ to_chat(user, SPAN_WARNING("You refrain from firing [src] because your intent is set to help!"))
+ return
+
if(user && user.client && user.aiming && user.aiming.active && user.aiming.aiming_at != target)
PreFire(target,user,shitty_legacy_params) //They're using the new gun system, locate what they're aiming at.
return
@@ -400,77 +470,11 @@
pin.emag_act(remaining_charges, user)
/obj/item/gun/proc/Fire(atom/target, mob/living/user, clickparams, pointblank=0, reflex=0)
- if(!user || !target) return
- if(target.z != user.z) return
-
- add_fingerprint(user)
-
- user.break_cloak()
-
- if(!special_check(user))
- return
-
- if(world.time < next_fire_time)
- if (world.time % 3) //to prevent spam
- to_chat(user, "[src] is not ready to fire again!")
- return
-
- if(check_safety())
- //If we are on harm intent (intending to injure someone) but forgot to flick the safety off, there is a 50% chance we
- //will reflexively do it anyway
- if(user.a_intent == INTENT_HARM && prob(50))
- toggle_safety(user)
- else
- handle_click_safety(user)
- return
-
- if(!user?.client?.get_preference_toggle(/datum/game_preference_toggle/game/help_intent_firing) && user.a_intent == INTENT_HELP)
- to_chat(user, SPAN_WARNING("You refrain from firing [src] because your intent is set to help!"))
- return
-
- var/shoot_time = (burst - 1)* burst_delay
-
- //These should apparently be disabled to allow for the automatic system to function without causing near-permanant paralysis. Re-enabling them while we sort that out.
- user.setClickCooldown(shoot_time) //no clicking on things while shooting
-
- next_fire_time = world.time + shoot_time
-
- var/held_twohanded = (user.can_wield_item(src) && src.is_held_twohanded(user))
-
- //actually attempt to shoot
- var/turf/targloc = get_turf(target) //cache this in case target gets deleted during shooting, e.g. if it was a securitron that got destroyed.
+ SHOULD_NOT_OVERRIDE(TRUE)
for(var/i in 1 to burst)
- var/obj/projectile = consume_next_projectile(user)
- if(!projectile)
- handle_click_empty(user)
- break
-
- user.newtonian_move(get_dir(target, user)) // Recoil
-
- process_accuracy(projectile, user, target, i, held_twohanded)
-
- if(pointblank)
- process_point_blank(projectile, user, target)
-
+ #warn this
if(process_projectile(projectile, user, target, user.zone_sel.selecting, clickparams))
- handle_post_fire(user, target, pointblank, reflex)
- update_icon()
-
- if(i < burst)
- sleep(burst_delay)
-
- if(!(target && target.loc))
- target = targloc
- pointblank = 0
-
- last_shot = world.time
-
-
- // We do this down here, so we don't get the message if we fire an empty gun.
- if(user.is_holding(src) && user.hands_full())
- if(one_handed_penalty >= 20)
- to_chat(user, "You struggle to keep \the [src] pointed at the correct position with just one hand!")
var/target_for_log
if(ismob(target))
@@ -480,109 +484,12 @@
add_attack_logs(user,target_for_log,"Fired gun [src.name] ([reflex ? "REFLEX" : "MANUAL"])")
- //update timing
- user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
-
- next_fire_time = world.time + fire_delay
-
- accuracy = initial(accuracy) //Reset the gun's accuracyw
-
- if(muzzle_flash)
- if(gun_light)
- set_light(light_brightness)
- else
- set_light(0)
-
-// Similar to the above proc, but does not require a user, which is ideal for things like turrets.
-/obj/item/gun/proc/Fire_userless(atom/target)
- if(!target)
- return
-
- if(world.time < next_fire_time)
- return
-
- var/shoot_time = (burst - 1)* burst_delay
- next_fire_time = world.time + shoot_time
-
- var/turf/targloc = get_turf(target) //cache this in case target gets deleted during shooting, e.g. if it was a securitron that got destroyed.
- for(var/i in 1 to burst)
- var/obj/projectile = consume_next_projectile()
- if(!projectile)
- handle_click_empty()
- break
-
- if(istype(projectile, /obj/projectile))
- var/obj/projectile/P = projectile
-
- var/acc = burst_accuracy[min(i, burst_accuracy.len)]
- var/disp = dispersion[min(i, dispersion.len)]
-
- P.accuracy_overall_modify *= 1 + acc / 100
- P.dispersion = disp
-
- P.shot_from = src.name
- P.silenced = silenced
-
- P.old_style_target(target)
- play_fire_sound(P = projectile)
- P.fire()
-
- last_shot = world.time
-
- if(muzzle_flash)
- set_light(muzzle_flash)
- update_icon()
-
- //process_accuracy(projectile, user, target, acc, disp)
-
- // if(pointblank)
- // process_point_blank(projectile, user, target)
-
- // if(process_projectile(projectile, null, target, user.zone_sel.selecting, clickparams))
- // handle_post_fire(null, target, pointblank, reflex)
-
- // update_icon()
-
- if(i < burst)
- sleep(burst_delay)
-
- if(!(target && target.loc))
- target = targloc
- //pointblank = 0
-
- var/target_for_log
- if(ismob(target))
- target_for_log = target
- else
- target_for_log = "[target.name]"
-
- add_attack_logs("Unmanned",target_for_log,"Fired [src.name]")
-
- //update timing
- next_fire_time = world.time + fire_delay
-
- accuracy = initial(accuracy) //Reset the gun's accuracy
-
- if(muzzle_flash)
- set_light(0)
-
-//obtains the next projectile to fire
-/obj/item/gun/proc/consume_next_projectile()
- return null
-
-//called if there was no projectile to shoot
-/obj/item/gun/proc/handle_click_empty(mob/user)
- if (user)
- user.visible_message("*click click*", "*click*")
- else
- visible_message("*click click*")
- playsound(src, 'sound/weapons/empty.ogg', 100, 1)
-
/obj/item/gun/proc/handle_click_safety(mob/user)
user.visible_message(SPAN_WARNING("[user] squeezes the trigger of \the [src] but it doesn't move!"), SPAN_WARNING("You squeeze the trigger but it doesn't move!"), range = MESSAGE_RANGE_COMBAT_SILENCED)
//called after successfully firing
/obj/item/gun/proc/handle_post_fire(mob/user, atom/target, var/pointblank=0, var/reflex=0)
+ SHOULD_NOT_OVERRIDE(TRUE)
if(fire_anim)
flick(fire_anim, src)
@@ -690,29 +597,6 @@
if(!isnull(M.accuracy_dispersion))
P.dispersion = max(P.dispersion + M.accuracy_dispersion, 0)
-//does the actual launching of the projectile
-/obj/item/gun/proc/process_projectile(obj/projectile, mob/user, atom/target, var/target_zone, var/params=null)
- var/obj/projectile/P = projectile
- if(!istype(P))
- return FALSE //default behaviour only applies to true projectiles
-
- //shooting while in shock
- var/forcespread
- if(istype(user, /mob/living/carbon))
- var/mob/living/carbon/mob = user
- if(mob.shock_stage > 120)
- forcespread = rand(50, 50)
- else if(mob.shock_stage > 70)
- forcespread = rand(-25, 25)
- else if(IS_PRONE(mob))
- forcespread = rand(-15, 15)
- var/launched = !P.launch_from_gun(target, target_zone, user, params, null, forcespread, src)
-
- if(launched)
- play_fire_sound(user, P)
-
- return launched
-
/obj/item/gun/proc/play_fire_sound(var/mob/user, var/obj/projectile/P)
var/shot_sound = fire_sound
@@ -726,46 +610,6 @@
else
playsound(src, shot_sound, 50, 1)
-// todo: rework all this this is fucking dumb
-//Suicide handling.
-// /obj/item/gun/var/mouthshoot = 0 //To stop people from suiciding twice... >.>
-
-// /obj/item/gun/proc/handle_suicide(mob/living/user)
-// if(!ishuman(user))
-// return
-// var/mob/living/carbon/human/M = user
-
-// mouthshoot = 1
-// M.visible_message("[user] sticks their gun in their mouth, ready to pull the trigger...")
-// if(!do_after(user, 40))
-// M.visible_message("[user] decided life was worth living")
-// mouthshoot = 0
-// return
-// var/obj/projectile/in_chamber = consume_next_projectile()
-// if (istype(in_chamber))
-// user.visible_message("[user] pulls the trigger.")
-// play_fire_sound(M, in_chamber)
-// if(istype(in_chamber, /obj/projectile/beam/lasertag))
-// user.show_message("You feel rather silly, trying to commit suicide with a toy.")
-// mouthshoot = 0
-// return
-
-// in_chamber.on_hit(M)
-// if(in_chamber.damage_type != DAMAGE_TYPE_HALLOSS && !in_chamber.nodamage)
-// log_and_message_admins("[key_name(user)] commited suicide using \a [src]")
-// user.apply_damage(in_chamber.damage_force*2.5, in_chamber.damage_type, "head", used_weapon = "Point blank shot in the mouth with \a [in_chamber]", sharp=1)
-// user.death()
-// else if(in_chamber.damage_type == DAMAGE_TYPE_HALLOSS)
-// to_chat(user, "Ow...")
-// user.apply_effect(110,AGONY,0)
-// qdel(in_chamber)
-// mouthshoot = 0
-// return
-// else
-// handle_click_empty(user)
-// mouthshoot = 0
-// return
-
/obj/item/gun/proc/toggle_scope(var/zoom_amount=2.0)
//looking through a scope limits your periphereal vision
//still, increase the view size by a tiny amount so that sniping isn't too restricted to NSEW
@@ -786,19 +630,6 @@
accuracy = initial(accuracy)
recoil = initial(recoil)
-/obj/item/gun/examine(mob/user, dist)
- . = ..()
- if(!no_pin_required)
- if(pin)
- . += "It has \a [pin] installed."
- else
- . += "It doesn't have a firing pin installed, and won't fire."
- if(firemodes.len > 1)
- var/datum/firemode/current_mode = firemodes[sel_mode]
- . += "The fire selector is set to [current_mode.name]."
- if(safety_state != GUN_NO_SAFETY)
- to_chat(user, SPAN_NOTICE("The safety is [check_safety() ? "on" : "off"]."))
-
/obj/item/gun/proc/switch_firemodes(mob/user)
if(firemodes.len <= 1)
return null
@@ -807,7 +638,7 @@
if(sel_mode > firemodes.len)
sel_mode = 1
var/datum/firemode/new_mode = firemodes[sel_mode]
- new_mode.apply_to(src)
+ new_mode.apply_legacy_variables(src)
if(user)
to_chat(user, "\The [src] is now set to [new_mode.name].")
playsound(loc, selector_sound, 50, 1)
@@ -883,7 +714,7 @@
return (safety_state == GUN_SAFETY_ON)
// PENDING FIREMODE REWORK
-/obj/item/gun/proc/legacy_get_firemode()
+/obj/item/gun/proc/legacy_get_firemode() as /datum/firemode
if(!length(firemodes) || (sel_mode > length(firemodes)))
return
return firemodes[sel_mode]
@@ -900,6 +731,19 @@
/obj/item/gun/proc/get_ammo_ratio()
return 0
+//* Firemodes *//
+
+/**
+ * Ensures our firemodes list is not a cached copy.
+ *
+ * * This absolutely must be called before **any** mutating writes to
+ * `firemodes` or its contents.
+ */
+/obj/item/gun/proc/ensure_firemodes_owned()
+ if(!is_typelist(NAMEOF(src, firemodes), firemodes))
+ return
+ firemodes = deep_clone_list(firemodes)
+
//* Rendering *//
/obj/item/gun/update_icon(updates)
diff --git a/code/modules/projectiles/guns/gun_component.dm b/code/modules/projectiles/guns/gun_component.dm
new file mode 100644
index 000000000000..6ff3e394c3fc
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_component.dm
@@ -0,0 +1,79 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * A component used in guns with modular parts.
+ *
+ * * This is **not** an attachment system. This is for things integral to gun operation.
+ */
+/obj/item/gun_component
+ name = "gun component"
+ desc = "A thing, that probably goes in a gun. Why are you seeing this?"
+ icon = 'icons/modules/projectiles/gun_components.dmi'
+ icon_state = "" // empty state
+
+ /// component slot
+ ///
+ /// * This is just a suggestion.
+ /// * The actual APIs used are agnostic of this value.
+ var/component_slot
+ /// Conflict flags
+ var/component_conflict = NONE
+ #warn impl
+
+ /// should we be hidden from examine?
+ var/show_on_examine = TRUE
+ /// automatically hook firing iteration pre-fire? will call on_firing_cycle_iteration(cycle) if hooked.
+ var/hook_iteration_pre_fire = FALSE
+ #warn impl
+
+ /// currently attached gun
+ var/obj/item/gun/attached
+
+//* Attach / Detach *//
+
+/**
+ * returns if we should fit on a gun
+ *
+ * * we get the final say
+ * * this includes if the gun is already overcrowded! be careful with this
+ *
+ * @params
+ * * gun - the gun we tried to attach to
+ * * gun_opinion - what the gun had to say about it
+ * * gun_is_full - is the gun out of slots for us? we can still override but this is to separate it from gun_opinion.
+ * * actor - person initiating it; this is mostly for message feedback
+ * * silent - do not emit message to user on fail
+ */
+/obj/item/gun_component/proc/fits_on_gun(obj/item/gun/gun, gun_opinion, gun_is_full, datum/event_args/actor/actor, silent)
+ return TRUE
+
+/**
+ * called on attach
+ */
+/obj/item/gun_component/proc/on_attach(obj/item/gun/gun, datum/event_args/actor/actor, silent)
+ SHOULD_CALL_PARENT(TRUE)
+
+/**
+ * called on detach
+ */
+/obj/item/gun_component/proc/on_detach(obj/item/gun/gun, datum/event_args/actor/actor, silent)
+ SHOULD_CALL_PARENT(TRUE)
+
+//* Gun API *//
+
+/**
+ * Called right before fire() is invoked, if [hook_iteration_pre_fire] is set.
+ */
+/obj/item/gun_component/proc/on_firing_cycle_iteration(datum/gun_firing_cycle/cycle)
+ return
+
+//* Information *//
+
+/**
+ * Called to query the stat bullet points of this component
+ *
+ * @return a list of data about us to put in bullet points, in raw HTML
+ */
+/obj/item/gun_component/proc/summarize_bullet_points(datum/event_args/actor/actor)
+ return list()
diff --git a/code/modules/projectiles/guns/gun_component/acceleration_coil.dm b/code/modules/projectiles/guns/gun_component/acceleration_coil.dm
new file mode 100644
index 000000000000..800418edc0c4
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_component/acceleration_coil.dm
@@ -0,0 +1,18 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/gun_component/acceleration_coil
+ name = "weapon acceleration coil"
+ desc = "A basic acceleration coil used in magnetic weapons."
+ component_slot = GUN_COMPONENT_ACCELERATION_COIL
+
+/obj/item/gun_component/acceleration_coil/heater
+ name = "weapon acceleration coil (heater)"
+ desc = {"
+ A magnetic acceleration coil designed to superheat a passing projectile, resulting
+ in subtly raised penetration performance and a searing property to the resulting impact.
+ "}
+
+#warn impl all
+
+// TODO: This file is mostly stubs and WIPs.
diff --git a/code/modules/projectiles/guns/gun_component/active_cooler.dm b/code/modules/projectiles/guns/gun_component/active_cooler.dm
new file mode 100644
index 000000000000..94af9b9cf937
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_component/active_cooler.dm
@@ -0,0 +1,30 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/gun_component/active_cooler
+ name = "weapon cooler"
+ component_slot = GUN_COMPONENT_ACTIVE_COOLER
+
+/obj/item/gun_component/active_cooler/recovery
+ name = "weapon cooler (recovery)"
+ desc = {"
+ A cooler that passes residual heat through a series of peltier cells to recover some of
+ the energy used in firing. Very slow.
+ "}
+
+/obj/item/gun_component/active_cooler/powered
+ name = "weapon cooler (powered)"
+ desc = {"
+ A cooler that pumps heat out of the gun using provided power.
+ "}
+
+/obj/item/gun_component/active_cooler/active_reload
+ name = "weapon cooler (slide charging)"
+ desc = {"
+ A cooler that pumps heat out of the gun when a slide charging energy handler
+ is racked. Has mediocre cooling performance otherwise.
+ "}
+
+#warn impl all
+
+// TODO: This file is mostly stubs and WIPs.
diff --git a/code/modules/projectiles/guns/gun_component/energy_handler.dm b/code/modules/projectiles/guns/gun_component/energy_handler.dm
new file mode 100644
index 000000000000..779920af2893
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_component/energy_handler.dm
@@ -0,0 +1,47 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/gun_component/energy_handler
+ name = "weapon energy handler"
+ component_slot = GUN_COMPONENT_ENERGY_HANDLER
+
+/obj/item/gun_component/energy_handler/active_reload
+ name = "weapon energy handler (slide charging)"
+ desc = {"
+ An uncommon energy handler. Requires the user to rack the weapon to recharge
+ a linked supercapacitor array between shots for fast operation. In return,
+ the power provided to a given shot is improved by a decent margin.
+ "}
+
+/obj/item/gun_component/energy_handler/active_reload/summarize_bullet_points(datum/event_args/actor/actor, range)
+ . = list()
+ #warn hotkey hook
+ . += "Requires racking the weapon via Unique Action ([]) between shots. This will initiate an 'active reload', with a constant reload interval where you can finish the action early."
+ . += "Pressing Unique action ([]) again will attempt to finish the active reload early. This will abort the reload if it is done at the wrong time."
+ . += "Slowly recharges without a slide rack."
+ . += "Increases the available power on a fired shot."
+ . += "Suffers decreased efficiency on burst shots."
+
+/obj/item/gun_component/energy_handler/active_reload/perfect
+ name = "weapon energy handler (synchronous slide charging)"
+ desc = {"
+ An uncommon energy handler. Requires the user to rack the weapon to recharge
+ a linked supercapacitor array between shots for fast operation. In return,
+ the power provided to a given shot is improved by a decent margin. This one is
+ even more unwieldly to use, requiring the slide action to coincide with the chaotic peak
+ of an initiated recharging cycle for optimal performance.
+ "}
+
+/obj/item/gun_component/energy_handler/active_reload/perfect/summarize_bullet_points(datum/event_args/actor/actor, range)
+ . = list()
+ #warn hotkey hook
+ . += "Requires racking the weapon via Unique Action ([]) between shots. This will initiate an 'active reload', with a ranndomized reload interval where you can finish the action early."
+ . += "Pressing Unique action ([]) again will attempt to finish the active reload early. This will abort the reload if it is done at the wrong time."
+ . += "Slowly recharges without a slide rack."
+ . += "Increases the available power on a fired shot."
+ . += "On a successful active reload (early finish), this will further increase the available power on a fired shot."
+ . += "Suffers decreased efficiency on burst shots."
+
+#warn impl all
+
+// TODO: This file is mostly stubs and WIPs.
diff --git a/code/modules/projectiles/guns/gun_component/internal_module.dm b/code/modules/projectiles/guns/gun_component/internal_module.dm
new file mode 100644
index 000000000000..4dae7fdaa5f7
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_component/internal_module.dm
@@ -0,0 +1,45 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/gun_component/internal_module
+ name = "weapon module"
+ desc = "An internal module for a modular gun."
+ component_slot = GUN_COMPONENT_INTERNAL_MODULE
+
+#warn impl all
+
+// TODO: This file is mostly stubs and WIPs.
+
+/**
+ * mostly a test module;
+ *
+ * * makes the gun fire a second round on every fire
+ * * conflicts with any other burst modifiers
+ */
+/obj/item/gun_component/internal_module/double_shot
+ name = "AN-94 Fire Controller"
+ desc = /obj/item/gun_component/internal_module::desc + " This will cause the gun to fire one additional round per burst, at the cost of reduced accuracy."
+ component_conflict = GUN_COMPONENT_CONFLICT_BURST_MODIFICATION
+
+ /// angular dispersion to impose on the last round in the burst, and the round we add
+ var/dispersion_amount = 5
+
+/obj/item/gun_component/internal_module/double_shot/on_firing_cycle_iteration(datum/gun_firing_cycle/cycle)
+ // only invoke on last iteration
+ if(cycle.cycle_iterations_fired != cycle.firing_iterations)
+ return
+ // do not invoke multiple times
+ switch(LAZYACCESS(cycle.blackboard, "an-94-refire-triggered"))
+ if(0, null)
+ // set re-invoke flag
+ LAZYSET(cycle.blackboard, "an-94-refire-triggered", 1)
+ // add one iteration
+ cycle.firing_iterations++
+ // force current shot dispersion
+ cycle.next_dispersion += dispersion_amount
+ if(1)
+ // add dispersion
+ LAZYSET(cycle.blackboard, "an-94-refire-triggered", 2)
+ cycle.next_dispersion_adjust += dispersion_amount
+
+// todo: integrated electronics framework
diff --git a/code/modules/projectiles/guns/gun_component/power_unit.dm b/code/modules/projectiles/guns/gun_component/power_unit.dm
new file mode 100644
index 000000000000..39f2c560301d
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_component/power_unit.dm
@@ -0,0 +1,10 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/obj/item/gun_component/power_unit
+ name = "weapon power unit"
+ component_slot = GUN_COMPONENT_POWER_UNIT
+
+#warn impl all
+
+// TODO: This file is mostly stubs and WIPs.
diff --git a/code/modules/projectiles/guns/gun_firing_cycle.dm b/code/modules/projectiles/guns/gun_firing_cycle.dm
new file mode 100644
index 000000000000..62be391f914d
--- /dev/null
+++ b/code/modules/projectiles/guns/gun_firing_cycle.dm
@@ -0,0 +1,61 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/gun_firing_cycle
+ //* cycle *//
+ /// our firing cycle id - integer
+ var/cycle_notch
+ /// start world.time
+ var/cycle_start_time
+ /// iterations so far fired
+ ///
+ /// * this is set before the fire() call, which means fire() and post_fire()
+ /// can access this for current iteration.
+ var/cycle_iterations_fired = 0
+
+ //* targeting *//
+ /// original target
+ var/atom/original_target
+ /// original angle
+ var/original_angle
+
+ //* firemode *//
+ /// firemode: the original /datum/firemode we're firing on
+ var/datum/firemode/firemode
+
+ //* firing *//
+ /// firing flags
+ var/firing_flags
+ /// firing atom
+ ///
+ /// * this is not the same as actor event args; most things that care about this
+ /// do not care about the actor tuple.
+ var/atom/firing_atom
+ /// actor tuple, if it exists.
+ var/datum/event_args/actor/firing_actor
+ /// how many iterations to fire
+ ///
+ /// * defaulted to firemode settings
+ var/firing_iterations = 1
+ /// delay between firing iterations
+ ///
+ /// * defaulted to firemode settings
+ var/firing_delay = 0.2 SECONDS
+
+ //* fired processing args *//
+ //* these are vars set in a given iteration of firing. *//
+ /// last GUN_FIRED_* result
+ var/last_firing_result
+ /// were we interrupted?
+ var/last_interrupted = FALSE
+ /// on this iteration, have this much dispersion added
+ var/next_dispersion_adjust
+ #warn hook
+ /// on this iteration, force adjust the angle by this much (pos = cw, neg = ccw)
+ var/next_angle_adjust
+ #warn hook
+
+ //* firing modifier args *//
+ //* this is where things like modular gun components will inject into. *//
+ /// blackboard for modular gun components to use
+ var/list/blackboard
diff --git a/code/modules/projectiles/gun_item_renderer.dm b/code/modules/projectiles/guns/gun_item_renderer.dm
similarity index 100%
rename from code/modules/projectiles/gun_item_renderer.dm
rename to code/modules/projectiles/guns/gun_item_renderer.dm
diff --git a/code/modules/projectiles/gun_mob_renderer.dm b/code/modules/projectiles/guns/gun_mob_renderer.dm
similarity index 100%
rename from code/modules/projectiles/gun_mob_renderer.dm
rename to code/modules/projectiles/guns/gun_mob_renderer.dm
diff --git a/code/modules/projectiles/guns/launcher.dm b/code/modules/projectiles/guns/launcher.dm
index aa1db93267ba..ff7a8e4baa31 100644
--- a/code/modules/projectiles/guns/launcher.dm
+++ b/code/modules/projectiles/guns/launcher.dm
@@ -1,3 +1,6 @@
+/**
+ * A gun that throws things instead of, well, firing them.
+ */
/obj/item/gun/launcher
name = "launcher"
desc = "A device that launches things."
@@ -18,3 +21,12 @@
projectile.forceMove(get_turf(user))
projectile.throw_at_old(target, throw_distance, release_force, user)
return 1
+
+/**
+ * Returns the next /atom/movable to throw, or a GUN_FIRED_* for fail satus.
+ *
+ * * This should clear the throwable from our references.
+ */
+/obj/item/gun/launcher/proc/consume_next_throwable(iteration, firing_flags, datum/firemode/firemode, datum/event_args/actor/actor, atom/firer)
+ . = GUN_FIRED_FAIL_UNKNOWN
+ CRASH("attempted to consume_next_throwable on base /gun/launcher")
diff --git a/code/modules/projectiles/guns/launcher/crossbow.dm b/code/modules/projectiles/guns/launcher/crossbow.dm
index bfe7d6f03232..5f7077cd1815 100644
--- a/code/modules/projectiles/guns/launcher/crossbow.dm
+++ b/code/modules/projectiles/guns/launcher/crossbow.dm
@@ -55,7 +55,9 @@
item_state = "crossbow-solid"
fire_sound = 'sound/weapons/punchmiss.ogg' // TODO: Decent THWOK noise.
fire_sound_text = "a solid thunk"
- fire_delay = 25
+ firemodes = /datum/firemode{
+ cycle_cooldown = 2.5 SECONDS;
+ }
slot_flags = SLOT_BACK
safety_state = GUN_NO_SAFETY
one_handed_penalty = 10
@@ -70,17 +72,22 @@
/obj/item/gun/launcher/crossbow/update_release_force()
release_force = tension*release_speed
-/obj/item/gun/launcher/crossbow/consume_next_projectile(mob/user=null)
+/obj/item/gun/launcher/crossbow/start_firing_cycle(atom/firer, angle, firing_flags, datum/firemode/firemode, atom/target, datum/event_args/actor/actor)
if(tension <= 0)
- to_chat(user, "\The [src] is not drawn back!")
- return null
- return bolt
-
-/obj/item/gun/launcher/crossbow/handle_post_fire(mob/user, atom/target)
+ actor?.chat_feedback(
+ SPAN_WARNING("The bolt on [src] isn't drawn back!"),
+ target = src,
+ )
+ return FALSE
+ return ..()
+
+/obj/item/gun/launcher/crossbow/consume_next_throwable(iteration, firing_flags, datum/firemode/firemode, datum/event_args/actor/actor, atom/firer)
+ . = bolt
bolt = null
+
+/obj/item/gun/launcher/crossbow/post_fire(datum/gun_firing_cycle/cycle)
+ . = ..()
tension = 0
- update_icon()
- ..()
/obj/item/gun/launcher/crossbow/attack_self(mob/user, datum/event_args/actor/actor)
. = ..()
diff --git a/code/modules/projectiles/guns/launcher/grenade_launcher.dm b/code/modules/projectiles/guns/launcher/grenade_launcher.dm
index 3a4f539a4288..5f6021227c37 100644
--- a/code/modules/projectiles/guns/launcher/grenade_launcher.dm
+++ b/code/modules/projectiles/guns/launcher/grenade_launcher.dm
@@ -83,19 +83,6 @@
else
..()
-/*//This broke for no reason. Look into it.
-/obj/item/gun/launcher/grenade/consume_next_projectile()
- if(chambered)
- chambered.det_time = 10
- chambered.activate(null)
- return chambered
-*/
-
-/obj/item/gun/launcher/grenade/handle_post_fire(mob/user)
- message_admins("[key_name_admin(user)] fired a grenade ([chambered.name]) from a grenade launcher ([src.name]).")
- log_game("[key_name_admin(user)] used a grenade ([chambered.name]).")
- chambered = null
-
//Underslung grenade launcher to be used with the Z8
/obj/item/gun/launcher/grenade/underslung
name = "underslung grenade launcher"
diff --git a/code/modules/projectiles/guns/launcher/pneumatic.dm b/code/modules/projectiles/guns/launcher/pneumatic.dm
index f990a8d6935c..ada6f1319577 100644
--- a/code/modules/projectiles/guns/launcher/pneumatic.dm
+++ b/code/modules/projectiles/guns/launcher/pneumatic.dm
@@ -7,7 +7,9 @@
w_class = WEIGHT_CLASS_HUGE
heavy = TRUE
fire_sound_text = "a loud whoosh of moving air"
- fire_delay = 50
+ firemodes = /datum/firemode{
+ cycle_cooldown = 5 SECONDS;
+ }
fire_sound = 'sound/weapons/grenade_launcher.ogg' // Formerly tablehit1.ogg but I like this better -Ace
one_handed_penalty = 10
@@ -83,12 +85,12 @@
return
eject_tank(user)
-/obj/item/gun/launcher/pneumatic/consume_next_projectile(mob/user=null)
+/obj/item/gun/launcher/pneumatic/consume_next_throwable(iteration, firing_flags, datum/firemode/firemode, datum/event_args/actor/actor, atom/firer)
if(!item_storage.contents.len)
- return null
+ return GUN_FIRED_FAIL_EMPTY
if (!tank)
- to_chat(user, "There is no gas tank in [src]!")
- return null
+ actor?.chat_feedback(SPAN_WARNING("There's no gas tank in [src]!"), src)
+ return GUN_FIRED_FAIL_INERT
var/environment_pressure = 10
var/turf/T = get_turf(src)
@@ -99,8 +101,9 @@
fire_pressure = (tank.air_contents.return_pressure() - environment_pressure)*pressure_setting/100
if(fire_pressure < 10)
- to_chat(user, "There isn't enough gas in the tank to fire [src].")
- return null
+ // todo: ughhhh this should misfire not do this
+ actor?.chat_feedback(SPAN_WARNING("There's not enough gas in the tank to fire [src]!"), src)
+ return GUN_FIRED_FAIL_INERT
var/obj/item/launched = item_storage.contents[1]
item_storage.obj_storage.remove(launched, src)
@@ -121,14 +124,14 @@
else
release_force = 0
-/obj/item/gun/launcher/pneumatic/handle_post_fire()
+/obj/item/gun/launcher/pneumatic/post_fire(datum/gun_firing_cycle/cycle)
+ . = ..()
if(tank)
var/lost_gas_amount = tank.air_contents.total_moles*(pressure_setting/100)
var/datum/gas_mixture/removed = tank.air_contents.remove(lost_gas_amount)
var/turf/T = get_turf(src.loc)
- if(T) T.assume_air(removed)
- ..()
+ T?.assume_air(removed)
/obj/item/gun/launcher/pneumatic/update_icon()
. = ..()
diff --git a/code/modules/projectiles/guns/launcher/syringe_gun.dm b/code/modules/projectiles/guns/launcher/syringe_gun.dm
index aeff2f7e9bef..f4be4ac40cb9 100644
--- a/code/modules/projectiles/guns/launcher/syringe_gun.dm
+++ b/code/modules/projectiles/guns/launcher/syringe_gun.dm
@@ -84,16 +84,12 @@
var/max_darts = 1
var/obj/item/syringe_cartridge/next
-/obj/item/gun/launcher/syringe/consume_next_projectile()
+/obj/item/gun/launcher/syringe/consume_next_throwable(iteration, firing_flags, datum/firemode/firemode, datum/event_args/actor/actor, atom/firer)
if(next)
next.prime()
- return next
- return null
-
-/obj/item/gun/launcher/syringe/handle_post_fire()
- ..()
- darts -= next
- next = null
+ . = next
+ darts -= next
+ next = null
/obj/item/gun/launcher/syringe/attack_self(mob/user, datum/event_args/actor/actor)
. = ..()
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm b/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm
index 77a05acafa81..2054dbb9651c 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm
@@ -109,20 +109,30 @@
icon_state = (ammo_magazine)? "stg60" : "stg60-e"
item_state = (ammo_magazine)? "arifle" : "arifle-e"
+/datum/firemode/energy/eluger
+ cycle_cooldown = 0.4 SECONDS
+
+/datum/firemode/energy/eluger/stun
+ name = "stun"
+ legacy_direct_varedits = list(charge_cost=120,projectile_type=/obj/projectile/beam/stun, modifystate="elugerstun", fire_sound='sound/weapons/Taser.ogg')
+
+/datum/firemode/energy/eluger/lethal
+ name = "lethal"
+ legacy_direct_varedits = list(charge_cost=240,projectile_type=/obj/projectile/beam/eluger, modifystate="elugerkill", fire_sound='sound/weapons/eluger.ogg')
+
// ------------ Energy Luger ------------
/obj/item/gun/energy/gun/eluger
name = "energy Luger"
desc = "The finest sidearm produced by RauMauser. Although its battery cannot be removed, its ergonomic design makes it easy to shoot, allowing for rapid follow-up shots. It also has the ability to toggle between stun and kill."
icon_state = "elugerstun100"
item_state = "gun"
- fire_delay = null // Lugers are quite comfortable to shoot, thus allowing for more controlled follow-up shots. Rate of fire similar to a laser carbine.
- battery_lock = 1 // In exchange for balance, you cannot remove the battery. Also there's no sprite for that and I fucking suck at sprites. -Ace
+ legacy_battery_lock = 1 // In exchange for balance, you cannot remove the battery. Also there's no sprite for that and I fucking suck at sprites. -Ace
origin_tech = list(TECH_COMBAT = 3, TECH_MAGNET = 2, TECH_ILLEGAL = 2)
modifystate = "elugerstun"
fire_sound = 'sound/weapons/Taser.ogg'
firemodes = list(
- list(mode_name="stun", charge_cost=120,projectile_type=/obj/projectile/beam/stun, modifystate="elugerstun", fire_sound='sound/weapons/Taser.ogg'),
- list(mode_name="lethal", charge_cost=240,projectile_type=/obj/projectile/beam/eluger, modifystate="elugerkill", fire_sound='sound/weapons/eluger.ogg'),
+ /datum/firemode/energy/eluger/stun,
+ /datum/firemode/energy/eluger/lethal,
)
//Civilian gun
@@ -180,6 +190,18 @@
else
. += "inspector_on"
+/datum/firemode/sol_smg
+ burst_delay = 0.2 SECONDS
+
+/datum/firemode/sol_smg/one
+ name = "semi-automatic"
+ burst_amount = 1
+ legacy_direct_varedits = list()
+
+/datum/firemode/sol_smg/three
+ name = "3-round bursts"
+ burst_amount = 3
+
// No idea what this is for.
/obj/item/gun/ballistic/automatic/sol
name = "\improper \"Sol\" SMG"
@@ -193,12 +215,13 @@
allowed_magazines = list(/obj/item/ammo_magazine/a9mm)
load_method = MAGAZINE
multi_aim = 1
- burst_delay = 2
origin_tech = list(TECH_COMBAT = 4, TECH_MATERIAL = 2)
firemodes = list(
- list(mode_name="semiauto", burst=1, fire_delay=0, move_delay=null, burst_accuracy=null, dispersion=null),
- list(mode_name="3-round bursts", burst=3, fire_delay=null, move_delay=4, burst_accuracy=list(0,-15,-15), dispersion=list(0.0, 0.6, 1.0)),
- )
+ /datum/firemode/sol_smg/one,
+ /datum/firemode/sol_smg/three,
+ )
+ burst_accuracy=list(0,-15,-15)
+ dispersion=list(0.0, 0.6, 1.0)
/obj/item/gun/ballistic/automatic/sol/proc/update_charge()
if(!ammo_magazine)
@@ -225,7 +248,7 @@
charge_cost = 1200
charge_meter = 0
modifystate = null
- battery_lock = 1
+ legacy_battery_lock = 1
fire_sound = 'sound/weapons/Taser.ogg'
origin_tech = list(TECH_COMBAT = 3, TECH_MAGNET = 2)
firemodes = list(
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/dominator.dm b/code/modules/projectiles/guns/legacy_vr_guns/dominator.dm
index 84173c51c78e..f86f32861976 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/dominator.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/dominator.dm
@@ -25,7 +25,7 @@
/obj/item/gun/energy/gun/fluff/dominator/special_check(mob/user)
- if(!emagged && mode_name == "lethal" && get_security_level() == "green")
+ if(!emagged && legacy_get_firemode()?.name == "lethal" && get_security_level() == "green")
to_chat(user,"The trigger refuses to depress while on the lethal setting under security level green!")
return FALSE
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/protector.dm b/code/modules/projectiles/guns/legacy_vr_guns/protector.dm
index ac559ad91380..2eaaa49f30d2 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/protector.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/protector.dm
@@ -24,20 +24,18 @@
charge_sections = 3 //For the icon
ammo_x_offset = 2
ammo_y_offset = 0
- can_flashlight = TRUE
- light_state = "prot_light"
- flight_x_offset = 0
- flight_y_offset = 0
+
+ // todo: add flashlight attachment support
firemodes = list(
- list(mode_name="stun", projectile_type=/obj/projectile/beam/stun/protector, modifystate="stun", fire_sound='sound/weapons/Taser.ogg'),
+ list(mode_name="stun", projectile_type=/obj/projectile/beam/stun/protector, modifystate="stun", fire_sound='sound/weapons/Taser.ogg'),
list(mode_name="lethal", projectile_type=/obj/projectile/beam, modifystate="kill", fire_sound='sound/weapons/gauss_shoot.ogg'),
)
var/emagged = FALSE
/obj/item/gun/energy/protector/special_check(mob/user)
- if(!emagged && mode_name == "lethal" && get_security_level() == "green")
+ if(!emagged && legacy_get_firemode()?.name == "lethal" && get_security_level() == "green")
to_chat(user,"The trigger refuses to depress while on the lethal setting under security level green!")
return FALSE
@@ -72,10 +70,10 @@
if(itemState)
itemState += "[modifystate]"
*/
- if(power_supply)
- ratio = CEILING(((power_supply.charge / power_supply.maxcharge) * charge_sections), 1)
+ if(obj_cell_slot.cell)
+ ratio = CEILING(((obj_cell_slot.cell.charge / obj_cell_slot.cell.maxcharge) * charge_sections), 1)
- if(power_supply.charge < charge_cost)
+ if(obj_cell_slot.cell.charge < charge_cost)
overlays_to_add += "[icon_state]_empty"
else
if(!shaded_charge)
@@ -86,12 +84,6 @@
else
overlays_to_add += "[icon_state]_[modifystate][ratio]"
- if(can_flashlight & gun_light)
- var/mutable_appearance/flashlight_overlay = mutable_appearance(icon, light_state)
- flashlight_overlay.pixel_x = flight_x_offset
- flashlight_overlay.pixel_y = flight_y_offset
- overlays_to_add += flashlight_overlay
-
/* Don't have one for this gun
if(itemState)
itemState += "[ratio]"
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/secutor.dm b/code/modules/projectiles/guns/legacy_vr_guns/secutor.dm
index c986ae23379b..31e490494bee 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/secutor.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/secutor.dm
@@ -1,3 +1,18 @@
+/datum/firemode/energy/secutor
+ cycle_cooldown = 0.8 SECONDS
+
+/datum/firemode/energy/secutor/stun
+ name = "stun"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/energy/electrode/secutor, modifystate="secutorstun", charge_cost = 240)
+
+/datum/firemode/energy/secutor/phase
+ name = "phase"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/energy/phase/secutor, modifystate="secutorphaser", charge_cost = 200)
+
+/datum/firemode/energy/secutor/lethal
+ name = "lethal"
+ legacy_direct_varedits = list(projectile_type=/obj/projectile/beam/secutor, modifystate="secutorkill", charge_cost = 300)
+
// -------------- Secutor -------------
/obj/item/gun/energy/secutor
name = "\improper Secutor sidearm"
@@ -11,15 +26,12 @@
worn_render_flags = WORN_RENDER_SLOT_NO_RENDER
projectile_type = /obj/projectile/energy/electrode/secutor
- fire_delay = 8
-
- modifystate = "secutorstun"
-
firemodes = list(
- list(mode_name="stun", fire_delay=8, projectile_type=/obj/projectile/energy/electrode/secutor, modifystate="secutorstun", charge_cost = 240),
- list(mode_name="phaser", fire_delay=8, projectile_type=/obj/projectile/energy/phase/secutor, modifystate="secutorphaser", charge_cost = 200),
- list(mode_name="low-power-lethal", fire_delay=10, projectile_type=/obj/projectile/beam/secutor, modifystate="secutorkill", charge_cost = 300),
+ /datum/firemode/energy/secutor/stun,
+ /datum/firemode/energy/secutor/phase,
+ /datum/firemode/energy/secutor/lethal,
)
+ modifystate = "secutorstun"
var/emagged = FALSE
@@ -30,7 +42,7 @@
cut_overlays()
/obj/item/gun/energy/secutor/special_check(mob/user)
- if(!emagged && mode_name == "low-power-lethal" && get_security_level() == "green")
+ if(!emagged && legacy_get_firemode()?.name == "lethal" && get_security_level() == "green")
to_chat(user,"The trigger refuses to depress while on the lethal setting and while under security level blue!")
return FALSE
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index 5e6cd06d99a5..17cae1b6b70f 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -1,3 +1,9 @@
+/**
+ * Magic "Guns"
+ *
+ * These quite literally just use magic. They contain default handling for charges,
+ * self charging, etc, but that's it; they don't support cells or casings or anything like that.
+ */
/obj/item/gun/magic
name = "magic staff"
desc = "This staff is boring to watch because even though it came first you've seen everything it can do in other staves for years."
@@ -50,7 +56,7 @@
charge_tick = 0
charges++
-/obj/item/gun/magic/consume_next_projectile()
+/obj/item/gun/magic/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(charges <= 0)
return null
return chambered?.get_projectile()
diff --git a/code/modules/projectiles/guns/magnetic/magnetic.dm b/code/modules/projectiles/guns/magnetic.dm
similarity index 89%
rename from code/modules/projectiles/guns/magnetic/magnetic.dm
rename to code/modules/projectiles/guns/magnetic.dm
index 8dffab58d8e4..be49ad8405f5 100644
--- a/code/modules/projectiles/guns/magnetic/magnetic.dm
+++ b/code/modules/projectiles/guns/magnetic.dm
@@ -1,3 +1,11 @@
+/**
+ * Magnetic Guns
+ *
+ * Not to be confused with /obj/item/gun/ballistic/magnetic,
+ * these guns are generally special and use special ammo,
+ * like fuel rods and RCDs. They also **optionally** consume energy to fire,
+ * and have an inbuilt capacitor charge system.
+ */
/obj/item/gun/magnetic
name = "improvised coilgun"
desc = "A coilgun hastily thrown together out of a basic frame and advanced power storage components. Is it safe for it to be duct-taped together like that?"
@@ -8,7 +16,6 @@
origin_tech = list(TECH_COMBAT = 5, TECH_MATERIAL = 4, TECH_ILLEGAL = 2, TECH_MAGNET = 4)
w_class = WEIGHT_CLASS_BULKY
- var/obj/item/cell/cell // Currently installed powercell.
var/obj/item/stock_parts/capacitor/capacitor // Installed capacitor. Higher rating == faster charge between shots.
var/obj/item/stock_parts/manipulator/manipulator // Installed manipulator. Mostly for Phoron Bore, higher rating == less mats consumed upon firing
var/removable_components = TRUE // Whether or not the gun can be dismantled.
@@ -26,22 +33,19 @@
if(capacitor)
power_per_tick = (power_cost*0.15) * capacitor.rating
update_icon()
- return ..()
+ . = ..()
+ obj_cell_slot.legacy_use_device_cells = FALSE
/obj/item/gun/magnetic/Destroy()
STOP_PROCESSING(SSobj, src)
- QDEL_NULL(cell)
QDEL_NULL(loaded)
QDEL_NULL(capacitor)
. = ..()
-/obj/item/gun/magnetic/get_cell(inducer)
- return cell
-
/obj/item/gun/magnetic/process(delta_time)
if(capacitor)
- if(cell)
- if(capacitor.charge < capacitor.max_charge && cell.checked_use(power_per_tick))
+ if(obj_cell_slot.cell)
+ if(capacitor.charge < capacitor.max_charge && obj_cell_slot.cell.checked_use(power_per_tick))
capacitor.charge(power_per_tick)
else
capacitor.use(capacitor.charge * 0.05)
@@ -51,11 +55,11 @@
var/list/overlays_to_add = list()
cut_overlays()
if(removable_components)
- if(cell)
+ if(obj_cell_slot.cell)
overlays_to_add += image(icon, "[icon_state]_cell")
if(capacitor)
overlays_to_add += image(icon, "[icon_state]_capacitor")
- if(!cell || !capacitor)
+ if(!obj_cell_slot.cell || !capacitor)
overlays_to_add += image(icon, "[icon_state]_red")
else if(capacitor.charge < power_cost)
overlays_to_add += image(icon, "[icon_state]_amber")
@@ -75,12 +79,12 @@
. = ..()
show_ammo(user)
- if(cell)
- . += "The installed [cell.name] has a charge level of [round((cell.charge/cell.maxcharge)*100)]%."
+ if(obj_cell_slot.cell)
+ . += "The installed [obj_cell_slot.cell.name] has a charge level of [round((obj_cell_slot.cell.charge/obj_cell_slot.cell.maxcharge)*100)]%."
if(capacitor)
. += "The installed [capacitor.name] has a charge level of [round((capacitor.charge/capacitor.max_charge)*100)]%."
- if(!cell || !capacitor)
+ if(!obj_cell_slot.cell || !capacitor)
. += "The capacitor charge indicator is blinking red. Maybe you should check the cell or capacitor."
else
if(capacitor.charge < power_cost)
@@ -89,20 +93,7 @@
. += "The capacitor charge indicator is green."
/obj/item/gun/magnetic/attackby(var/obj/item/thing, var/mob/user)
-
if(removable_components)
- if(istype(thing, /obj/item/cell))
- if(cell)
- to_chat(user, "\The [src] already has \a [cell] installed.")
- return
- if(!user.attempt_insert_item_for_installation(thing, src))
- return
- cell = thing
- playsound(src, 'sound/machines/click.ogg', 10, 1)
- user.visible_message("\The [user] slots \the [cell] into \the [src].")
- update_icon()
- return
-
if(thing.is_screwdriver())
if(!capacitor)
to_chat(user, "\The [src] has no capacitor installed.")
@@ -157,9 +148,6 @@
if(loaded)
removing = loaded
loaded = null
- else if(cell && removable_components)
- removing = cell
- cell = null
if(removing)
removing.forceMove(get_turf(src))
@@ -177,8 +165,7 @@
qdel(loaded)
loaded = null
-/obj/item/gun/magnetic/consume_next_projectile()
-
+/obj/item/gun/magnetic/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(!check_ammo() || !capacitor || capacitor.charge < power_cost)
return
@@ -213,7 +200,13 @@
power_cost = 500
-/obj/item/gun/magnetic/fuelrod/consume_next_projectile()
+ cell_type = /obj/item/cell/high
+
+/obj/item/gun/magnetic/fuelrod/Initialize(mapload)
+ capacitor = new /obj/item/stock_parts/capacitor
+ return ..()
+
+/obj/item/gun/magnetic/fuelrod/consume_next_projectile(datum/gun_firing_cycle/cycle)
if(!check_ammo() || !capacitor || capacitor.charge < power_cost)
return
@@ -252,7 +245,7 @@
return new projectile_type(src)
-/obj/item/gun/magnetic/fuelrod/Initialize(mapload)
- cell = new /obj/item/cell/high
- capacitor = new /obj/item/stock_parts/capacitor
- return ..()
+//* Object System - Cell *//
+
+/obj/item/gun/magnetic/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot)
+ return removable_components && ..()
diff --git a/code/modules/projectiles/guns/magnetic/bore.dm b/code/modules/projectiles/guns/magnetic/bore.dm
index f6bf34c5d9d1..529a7d7b8635 100644
--- a/code/modules/projectiles/guns/magnetic/bore.dm
+++ b/code/modules/projectiles/guns/magnetic/bore.dm
@@ -35,11 +35,11 @@
/obj/item/gun/magnetic/matfed/update_overlays()
. = ..()
if(removable_components)
- if(cell)
+ if(obj_cell_slot.cell)
. += image(icon, "[icon_state]_cell")
if(capacitor)
. += image(icon, "[icon_state]_capacitor")
- if(!cell || !capacitor)
+ if(!obj_cell_slot.cell || !capacitor)
. += image(icon, "[icon_state]_red")
else if(capacitor.charge < power_cost)
. += image(icon, "[icon_state]_amber")
@@ -48,23 +48,6 @@
if(mat_storage)
. += image(icon, "[icon_state]_loaded")
-/obj/item/gun/magnetic/matfed/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
- if(user.get_inactive_held_item() == src)
- var/obj/item/removing
-
- if(cell && removable_components)
- removing = cell
- cell = null
-
- if(removing)
- removing.forceMove(get_turf(src))
- user.put_in_hands(removing)
- user.visible_message("\The [user] removes \the [removing] from \the [src].")
- playsound(src, 'sound/machines/click.ogg', 10, 1)
- update_icon()
- return
- . = ..()
-
/obj/item/gun/magnetic/matfed/check_ammo()
if(mat_storage - mat_cost >= 0)
return TRUE
@@ -75,17 +58,6 @@
/obj/item/gun/magnetic/matfed/attackby(var/obj/item/thing, var/mob/user)
if(removable_components)
- if(istype(thing, /obj/item/cell))
- if(cell)
- to_chat(user, "\The [src] already has \a [cell] installed.")
- return
- if(!user.attempt_insert_item_for_installation(thing, src))
- return
- cell = thing
- playsound(src, 'sound/machines/click.ogg', 10, 1)
- user.visible_message("\The [user] slots \the [cell] into \the [src].")
- update_icon()
- return
if(thing.is_crowbar())
if(!manipulator)
to_chat(user, "\The [src] has no manipulator installed.")
diff --git a/code/modules/projectiles/guns/magnetic/magnetic_railgun.dm b/code/modules/projectiles/guns/magnetic/magnetic_railgun.dm
index edbd60c68f51..5343ad80cbea 100644
--- a/code/modules/projectiles/guns/magnetic/magnetic_railgun.dm
+++ b/code/modules/projectiles/guns/magnetic/magnetic_railgun.dm
@@ -14,9 +14,8 @@
loaded = /obj/item/rcd_ammo/large
weight = ITEM_WEIGHT_GUN_BULKY
encumbrance = ITEM_ENCUMBRANCE_GUN_BULKY
- fire_delay = 1
+ cell_type = /obj/item/cell/hyper
- var/initial_cell_type = /obj/item/cell/hyper
var/initial_capacitor_type = /obj/item/stock_parts/capacitor/adv
var/empty_sound = 'sound/machines/twobeep.ogg'
@@ -24,7 +23,6 @@
capacitor = new initial_capacitor_type(src)
capacitor.charge = capacitor.max_charge
- cell = new initial_cell_type(src)
if (ispath(loaded))
loaded = new loaded
return ..()
@@ -59,9 +57,8 @@
desc = "The Mars Military Industries MI-227 Meteor. Originally a vehicle-mounted turret weapon for heavy anti-vehicular and anti-structural fire, the fact that it was made man-portable is mindboggling in itself."
icon_state = "heavy_railgun"
- initial_cell_type = /obj/item/cell/infinite
+ cell_type = /obj/item/cell/infinite
initial_capacitor_type = /obj/item/stock_parts/capacitor/super
- fire_delay = 0
weight = ITEM_WEIGHT_GUN_RIDICULOUS
encumbrance = ITEM_ENCUMBRANCE_GUN_RIDICULOUS
@@ -86,11 +83,9 @@
icon_state = "flechette_gun"
item_state = "z8carbine"
- initial_cell_type = /obj/item/cell/hyper
+ cell_type = /obj/item/cell/hyper
initial_capacitor_type = /obj/item/stock_parts/capacitor/adv
- fire_delay = 0
-
slot_flags = SLOT_BACK
weight = ITEM_WEIGHT_GUN_LIGHT
@@ -117,11 +112,8 @@
removable_components = TRUE
- initial_cell_type = /obj/item/cell/high
+ cell_type = /obj/item/cell/high
initial_capacitor_type = /obj/item/stock_parts/capacitor
-
- fire_delay = 8
-
slot_flags = SLOT_BACK
weight = ITEM_WEIGHT_GUN_LIGHT
@@ -149,7 +141,7 @@
w_class = WEIGHT_CLASS_NORMAL
- initial_cell_type = /obj/item/cell/high
+ cell_type = /obj/item/cell/high
initial_capacitor_type = /obj/item/stock_parts/capacitor
slot_flags = SLOT_BELT|SLOT_HOLSTER
@@ -178,7 +170,7 @@
icon_state = "railgun_sifguard"
item_state = "z8carbine"
- initial_cell_type = /obj/item/cell/high
+ cell_type = /obj/item/cell/high
initial_capacitor_type = /obj/item/stock_parts/capacitor/adv
slot_flags = SLOT_BACK
diff --git a/code/modules/projectiles/guns/vox.dm b/code/modules/projectiles/guns/vox.dm
index 3ef5d6f19358..a73c7293be30 100644
--- a/code/modules/projectiles/guns/vox.dm
+++ b/code/modules/projectiles/guns/vox.dm
@@ -1,3 +1,5 @@
+// todo: why is all this crap in the root level?
+
/*
* Vox Spike Thrower
* Alien pinning weapon.
@@ -44,7 +46,7 @@
/obj/item/gun/launcher/spikethrower/update_release_force()
return
-/obj/item/gun/launcher/spikethrower/consume_next_projectile()
+/obj/item/gun/launcher/spikethrower/consume_next_throwable(iteration, firing_flags, datum/firemode/firemode, datum/event_args/actor/actor, atom/firer)
if(spikes < 1) return null
spikes--
return new /obj/item/spike(src)
@@ -62,7 +64,7 @@
charge_cost = 300
projectile_type = /obj/projectile/beam/stun/darkmatter
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
accuracy = 30
firemodes = list(
@@ -124,7 +126,7 @@
w_class = WEIGHT_CLASS_HUGE
heavy = TRUE
cell_type = /obj/item/cell/device/weapon/recharge
- battery_lock = 1
+ legacy_battery_lock = 1
charge_cost = 400
projectile_type=/obj/projectile/sonic/weak
diff --git a/code/modules/projectiles/projectile/projectile.dm b/code/modules/projectiles/projectile/projectile.dm
index 489ec2c9828c..ca8da8f3bce2 100644
--- a/code/modules/projectiles/projectile/projectile.dm
+++ b/code/modules/projectiles/projectile/projectile.dm
@@ -288,9 +288,9 @@
//? Damage - default handling
/// damage amount
- var/damage_force = 10
- /// damage tier - goes hand in hand with [damage_armor]
- var/damage_tier = BULLET_TIER_DEFAULT
+ var/damage_force = 0
+ /// damage tier - goes hand in hand with [damage_mode]
+ var/damage_tier = ARMOR_TIER_DEFAULT
/// damage type - DAMAGE_TYPE_* define
var/damage_type = DAMAGE_TYPE_BRUTE
/// armor flag for damage - goes hand in hand with [damage_tier]
@@ -599,14 +599,6 @@
launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread)
return fire(angle_override, direct_target)
-//called to launch a projectile from a gun
-/obj/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/gun/launcher)
-
- shot_from = launcher.name
- silenced = launcher.silenced
-
- return launch_projectile(target, target_zone, user, params, angle_override, forced_spread)
-
/obj/projectile/proc/launch_projectile_from_turf(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
var/direct_target
if(get_turf(target) == get_turf(src))
diff --git a/code/modules/projectiles/projectile/subtypes/beam/beams.dm b/code/modules/projectiles/projectile/subtypes/beam/beams.dm
index d83d27afc40f..c85d30c01a75 100644
--- a/code/modules/projectiles/projectile/subtypes/beam/beams.dm
+++ b/code/modules/projectiles/projectile/subtypes/beam/beams.dm
@@ -89,6 +89,7 @@
icon_state = "cyan"
fire_sound = 'sound/weapons/weaponsounds_alienlaser.ogg'
damage_force = 40
+ damage_tier = LASER_TIER_HIGH
light_color = "#00C6FF"
muzzle_type = /obj/effect/projectile/muzzle/laser_omni
@@ -221,12 +222,12 @@
/obj/projectile/beam/stun/weak
name = "weak stun beam"
icon_state = "stun"
- agony = 25
+ agony = 27.5
/obj/projectile/beam/stun/med
name = "stun beam"
icon_state = "stun"
- agony = 30
+ agony = 40
//Disabler Beams - It didn't feel right just to recolor Stun beams. We have uses for them still.
/obj/projectile/beam/disabler
diff --git a/code/modules/projectiles/projectile/subtypes/energy/energy.dm b/code/modules/projectiles/projectile/subtypes/energy/energy.dm
index 4d9acec111be..a32077cddf71 100644
--- a/code/modules/projectiles/projectile/subtypes/energy/energy.dm
+++ b/code/modules/projectiles/projectile/subtypes/energy/energy.dm
@@ -181,12 +181,12 @@
var/ear_safety = 0
ear_safety = M.get_ear_protection()
if(ear_safety == 1)
- M.Confuse(150)
+ M.Confuse(6)
else if (ear_safety > 1)
- M.Confuse(30)
+ M.Confuse(3)
else if (!ear_safety)
- M.afflict_stun(20 * 10)
- M.afflict_paralyze(20 * 2)
+ M.afflict_stun(2 SECONDS)
+ M.afflict_paralyze(0.5 SECONDS)
M.ear_damage += rand(1, 10)
M.ear_deaf = max(M.ear_deaf,15)
if (M.ear_damage >= 15)
diff --git a/code/modules/projectiles/projectile/subtypes/unsorted.dm b/code/modules/projectiles/projectile/subtypes/unsorted.dm
index d9b5882ed3e2..8778cfe589ba 100644
--- a/code/modules/projectiles/projectile/subtypes/unsorted.dm
+++ b/code/modules/projectiles/projectile/subtypes/unsorted.dm
@@ -349,31 +349,18 @@
//Plasma Burst
/obj/projectile/plasma
- name ="plasma bolt"
+ name = "plasma bolt"
icon_state= "fuel-tritium"
- damage_force = 50
+ damage_force = 30
damage_type = DAMAGE_TYPE_BURN
damage_flag = ARMOR_ENERGY
light_range = 4
light_power = 3
light_color = "#00ccff"
- var/heavy = FALSE
-
-/obj/projectile/plasma/on_impact(atom/target, impact_flags, def_zone, efficiency)
- . = ..()
- if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
- return
-
- var/blast_dir = src.dir
- target.visible_message("\The [target] is engulfed in roiling plasma!")
- var/blastloc = get_step(target, blast_dir)
- if(blastloc)
- explosion(blastloc, -1, 0, heavy? 2 : 1, heavy? 3 : 2)
/obj/projectile/plasma/hot
- name ="heavy plasma bolt"
- damage_force = 75
+ name = "heavy plasma bolt"
+ damage_force = 50
light_range = 5
light_power = 4
light_color = "#00ccff"
- heavy = TRUE
diff --git a/code/modules/tension/tension.dm b/code/modules/tension/tension.dm
index 584c0e458fae..64a8da1310f3 100644
--- a/code/modules/tension/tension.dm
+++ b/code/modules/tension/tension.dm
@@ -195,7 +195,7 @@
weapon_damage = P.damage_force
if(will_point_blank && a_intent == INTENT_HARM)
weapon_damage *= 1.5
- weapon_attack_speed = G.fire_delay / (1 SECOND)
+ weapon_attack_speed = (G.legacy_get_firemode()?.cycle_cooldown || (0.4 SECONDS)) / (1 SECONDS)
qdel(P)
var/average_damage = weapon_damage / weapon_attack_speed
diff --git a/code/modules/xenoarcheaology/finds/find_spawning.dm b/code/modules/xenoarcheaology/finds/find_spawning.dm
index 1da2aa25e49e..7436e345c2ac 100644
--- a/code/modules/xenoarcheaology/finds/find_spawning.dm
+++ b/code/modules/xenoarcheaology/finds/find_spawning.dm
@@ -303,15 +303,15 @@
//10% chance to have an unchargeable cell
//15% chance to gain a random amount of starting energy, otherwise start with an empty cell
if(prob(5))
- new_gun.power_supply.rigged = 1
+ new_gun.obj_cell_slot.cell.rigged = 1
if(prob(10))
- new_gun.power_supply.maxcharge = 0
+ new_gun.obj_cell_slot.cell.maxcharge = 0
LAZYSET(new_gun.origin_tech, TECH_ARCANE, rand(0, 1))
if(prob(15))
- new_gun.power_supply.charge = rand(0, new_gun.power_supply.maxcharge)
+ new_gun.obj_cell_slot.cell.charge = rand(0, new_gun.obj_cell_slot.cell.maxcharge)
LAZYSET(new_gun.origin_tech, TECH_ARCANE, 1)
else
- new_gun.power_supply.charge = 0
+ new_gun.obj_cell_slot.cell.charge = 0
item_type = "gun"
if(27)
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/isd/carbine.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/isd/carbine.dmi
new file mode 100644
index 000000000000..9942b8b75587
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/isd/carbine.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/isd/lance.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/isd/lance.dmi
new file mode 100644
index 000000000000..b10ce2be1335
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/isd/lance.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/isd/multiphase.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/isd/multiphase.dmi
new file mode 100644
index 000000000000..82d9c12f6d41
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/isd/multiphase.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/isd/sidearm.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/isd/sidearm.dmi
new file mode 100644
index 000000000000..82d5185888ee
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/isd/sidearm.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/protomag/ammo.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/ammo.dmi
new file mode 100644
index 000000000000..113839045f81
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/ammo.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/protomag/magazines.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/magazines.dmi
new file mode 100644
index 000000000000..511e86c793e3
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/magazines.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/protomag/pistol.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/pistol.dmi
new file mode 100644
index 000000000000..dd1876fce290
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/pistol.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/protomag/projectile.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/projectile.dmi
new file mode 100644
index 000000000000..ecaf2742e5fa
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/projectile.dmi differ
diff --git a/icons/content/factions/corporations/nanotrasen/items/guns/protomag/rifle.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/rifle.dmi
new file mode 100644
index 000000000000..9f115516cf75
Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/rifle.dmi differ
diff --git a/icons/modules/projectiles/gun_components.dmi b/icons/modules/projectiles/gun_components.dmi
new file mode 100644
index 000000000000..1ae4da67932b
Binary files /dev/null and b/icons/modules/projectiles/gun_components.dmi differ
diff --git a/icons/obj/multiphase.dmi b/icons/obj/multiphase.dmi
deleted file mode 100644
index 493b8fba1202..000000000000
Binary files a/icons/obj/multiphase.dmi and /dev/null differ
diff --git a/maps/templates/admin/ert_base.dmm b/maps/templates/admin/ert_base.dmm
index b82c3d08903c..118c5807975c 100644
--- a/maps/templates/admin/ert_base.dmm
+++ b/maps/templates/admin/ert_base.dmm
@@ -432,16 +432,16 @@
/obj/item/gun/energy/netgun,
/obj/item/gun/energy/sniperrifle,
/obj/item/gun/energy/gun/martin{
- battery_lock = 0
+ legacy_battery_lock = 0
},
/obj/item/gun/energy/gun/martin{
- battery_lock = 0
+ legacy_battery_lock = 0
},
/obj/item/gun/energy/gun/martin{
- battery_lock = 0
+ legacy_battery_lock = 0
},
/obj/item/gun/energy/gun/martin{
- battery_lock = 0
+ legacy_battery_lock = 0
},
/obj/item/cell/device/weapon,
/obj/item/cell/device/weapon,