diff --git a/citadel.dme b/citadel.dme index a0160f3e7a30..84df114059b1 100644 --- a/citadel.dme +++ b/citadel.dme @@ -188,6 +188,7 @@ #include "code\__DEFINES\dcs\signals\datums\signals_inventory.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" @@ -259,6 +260,7 @@ #include "code\__DEFINES\languages\translation.dm" #include "code\__DEFINES\machines\airlock_states.dm" #include "code\__DEFINES\machines\door.dm" +#include "code\__DEFINES\machines\lathe.dm" #include "code\__DEFINES\machines\turrets.dm" #include "code\__DEFINES\mapping\levels.dm" #include "code\__DEFINES\mapping\maploader.dm" @@ -318,7 +320,8 @@ #include "code\__DEFINES\projectiles\ammo_magazine.dm" #include "code\__DEFINES\projectiles\gun.dm" #include "code\__DEFINES\projectiles\gun_attachment.dm" -#include "code\__DEFINES\projectiles\guns-legacy.dm" +#include "code\__DEFINES\projectiles\gun_component.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" @@ -438,6 +441,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" @@ -476,6 +480,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" @@ -953,6 +959,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" @@ -1110,13 +1117,20 @@ #include "code\game\content\factions\corporations\hephaestus\hephaestus-faction.dm" #include "code\game\content\factions\corporations\nanotrasen\nanotrasen-faction.dm" #include "code\game\content\factions\corporations\nanotrasen\nanotrasen-supply.dm" -#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_expeditionary-antimaterial.dm" -#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_expeditionary-heavy_rifle.dm" -#include "code\game\content\factions\corporations\nanotrasen\items\guns\nt_expeditionary-heavy_sidearm.dm" -#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_pulse.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition-antimaterial.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition-heavy_rifle.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition-heavy_sidearm.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition-light_rifle.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition-light_sidearm.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_isd.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_pmd.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_protomag-ammo.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_protomag-caliber.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_protomag-magazine.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_protomag-projectile.dm" +#include "code\game\content\factions\corporations\nanotrasen\guns\nt_protomag.dm" +#include "code\game\content\factions\corporations\nanotrasen\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" #include "code\game\content\factions\corporations\nanotrasen\nanotrasen-supply\contraband.dm" @@ -4513,12 +4527,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" @@ -4548,14 +4559,43 @@ #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-attachment.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-projectile-legacy.dm" +#include "code\modules\projectiles\guns\gun.dm" #include "code\modules\projectiles\guns\gun_attachment.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\attachments\bayonet.dm" #include "code\modules\projectiles\guns\attachments\flashlight.dm" #include "code\modules\projectiles\guns\attachments\harness.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" @@ -4563,6 +4603,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" @@ -4574,14 +4615,14 @@ #include "code\modules\projectiles\guns\energy\special.dm" #include "code\modules\projectiles\guns\energy\stun.dm" #include "code\modules\projectiles\guns\energy\temperature.dm" -#include "code\modules\projectiles\guns\energy\modular\gunframes.dm" -#include "code\modules\projectiles\guns\energy\modular\lasermediums.dm" -#include "code\modules\projectiles\guns\energy\modular\modularcooling.dm" -#include "code\modules\projectiles\guns\energy\modular\modularfcu.dm" -#include "code\modules\projectiles\guns\energy\modular\modulargun.dm" -#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\focusing_lens.dm" +#include "code\modules\projectiles\guns\gun_component\internal_module.dm" +#include "code\modules\projectiles\guns\gun_component\particle_array.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" @@ -4598,31 +4639,19 @@ #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-alter.dm" #include "code\modules\projectiles\projectile\projectile-hitscan_visuals.dm" +#include "code\modules\projectiles\projectile\projectile-lazy_helpers.dm" #include "code\modules\projectiles\projectile\projectile-physics.dm" #include "code\modules\projectiles\projectile\projectile-tracing.dm" #include "code\modules\projectiles\projectile\projectile.dm" #include "code\modules\projectiles\projectile\projectile_effect.dm" #include "code\modules\projectiles\projectile\projectile_effect\detonation.dm" +#include "code\modules\projectiles\projectile\projectile_effect\electrical_probe.dm" +#include "code\modules\projectiles\projectile\projectile_effect\electrical_stun.dm" #include "code\modules\projectiles\projectile\projectile_effect\detonation\legacy_emp.dm" #include "code\modules\projectiles\projectile\projectile_effect\detonation\legacy_explosion.dm" #include "code\modules\projectiles\projectile\subtypes\arc.dm" diff --git a/code/__DEFINES/_flags/item_flags.dm b/code/__DEFINES/_flags/item_flags.dm index 3be7599323d1..fd682933d13a 100644 --- a/code/__DEFINES/_flags/item_flags.dm +++ b/code/__DEFINES/_flags/item_flags.dm @@ -14,7 +14,7 @@ #define ITEM_THROW_UNCATCHABLE (1<<5) /// we cannot be used a tool on click, no matter what #define ITEM_NO_TOOL_ATTACK (1<<6) -/// we're dual wielded - multi-wielding coming later tm +/// we're wielded, usually via /datum/component/wielding #define ITEM_MULTIHAND_WIELDED (1<<7) /// don't allow help intent attacking #define ITEM_CAREFUL_BLUDGEON (1<<8) 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/datums/design.dm b/code/__DEFINES/datums/design.dm index 58069c624bef..ff85485fa554 100644 --- a/code/__DEFINES/datums/design.dm +++ b/code/__DEFINES/datums/design.dm @@ -1,21 +1,3 @@ -//? lathe_type bitfield - -#define LATHE_TYPE_AUTOLATHE (1<<0) -#define LATHE_TYPE_PROTOLATHE (1<<1) -#define LATHE_TYPE_CIRCUIT (1<<2) -#define LATHE_TYPE_PROSTHETICS (1<<3) -#define LATHE_TYPE_MECHA (1<<4) -#define LATHE_TYPE_BIOPRINTER (1<<5) - -DEFINE_BITFIELD(lathe_type, list( - BITFIELD(LATHE_TYPE_AUTOLATHE), - BITFIELD(LATHE_TYPE_PROTOLATHE), - BITFIELD(LATHE_TYPE_CIRCUIT), - BITFIELD(LATHE_TYPE_PROSTHETICS), - BITFIELD(LATHE_TYPE_MECHA), - BITFIELD(LATHE_TYPE_BIOPRINTER), -)) - //? design_unlock bitfield /// any lathe that can print us should have us always @@ -103,3 +85,56 @@ DEFINE_BITFIELD(design_flags, list( //science subcategories #define DESIGN_SUBCATEGORY_XENOBIOLOGY "Xenobiology" #define DESIGN_SUBCATEGORY_XENOARCHEOLOGY "Xenoarcheology" + +//* Design Helpers - Generic *// + +/** + * Generate a design for an entity. + * + * * Design path is appended to 1/datum/prototype/design/generated`. + */ +#define GENERATE_DESIGN(ENTITY_PATH, DESIGN_PATH, DESIGN_ID) \ +/datum/prototype/design/generated##DESIGN_PATH { \ + id = DESIGN_ID; \ + build_path = ENTITY_PATH; \ +}; \ +/datum/prototype/design/generated##DESIGN_PATH + +//* Design Helpers - For a specific lathe *// + +/** + * Generates for all lathes. + */ +#define GENERATE_DESIGN_FOR_AUTOLATHE(ENTITY_PATH, DESIGN_PATH, DESIGN_ID) \ +GENERATE_DESIGN(ENTITY_PATH, DESIGN_PATH, DESIGN_ID); \ +/datum/prototype/design/generated##DESIGN_PATH { \ + lathe_type = LATHE_TYPE_AUTOLATHE; \ +}; \ +/datum/prototype/design/generated##DESIGN_PATH + +/** + * Generates for Nanotrasen-standard autolathes. In the future, we might have flags + * for what factions get it automatically. + */ +#define GENERATE_DESIGN_FOR_PROTOLATHE(ENTITY_PATH, DESIGN_PATH, DESIGN_ID) \ +GENERATE_DESIGN(ENTITY_PATH, DESIGN_PATH, DESIGN_ID); \ +/datum/prototype/design/generated##DESIGN_PATH { \ + lathe_type = LATHE_TYPE_PROTOLATHE; \ +}; \ +/datum/prototype/design/generated##DESIGN_PATH + +//* Design Helpers - For a specific lathe & faction *// + +/** + * Generates for Nanotrasen-standard autolathes. In the future, we might have flags + * for what factions get it automatically. + */ +#define GENERATE_DESIGN_FOR_NT_AUTOLATHE(ENTITY_PATH) \ +GENERATE_DESIGN_FOR_AUTOLATHE(ENTITY_PATH, DESIGN_PATH, DESIGN_ID) + +/** + * Generates for Nanotrasen-standard autolathes. In the future, we might have flags + * for what factions get it automatically. + */ +#define GENERATE_DESIGN_FOR_NT_PROTOLATHE(ENTITY_PATH) \ +GENERATE_DESIGN_FOR_PROTOLATHE(ENTITY_PATH, DESIGN_PATH, DESIGN_ID) 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..302f96921de8 --- /dev/null +++ b/code/__DEFINES/dcs/signals/items/signals_gun.dm @@ -0,0 +1,37 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Firing Cycles *// + +/** + * 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" + +//* Fire() Injections *// + +/** + * Signature: (datum/gun_firing_cycle/cycle) + * * Raised before every fire() call. + */ +#define COMSIG_GUN_FIRING_PREFIRE "gun-firing-prefire" + +/** + * Signature: (datum/gun_firing_cycle/cycle, obj/projectile/proj) + * * Raised before every fire() call, after consume_next_projectile(). + * * Only valid on /obj/item/gun/projectile + * * This happens after the nominal PNR, so anything like energy drains and whatnot would have already + * been done by now. Keep that in mind. + */ +#define COMSIG_GUN_FIRING_PROJECTILE_INJECTION "gun-firing-projectile" + +/** + * Signature: (datum/gun_firing_cycle/cycle) + * * Raised after every fire() call, before post_fire(). + */ +#define COMSIG_GUN_FIRING_POSTFIRE "gun-firing-postfire" 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/machines/lathe.dm b/code/__DEFINES/machines/lathe.dm new file mode 100644 index 000000000000..0bfe4c25977c --- /dev/null +++ b/code/__DEFINES/machines/lathe.dm @@ -0,0 +1,20 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//? lathe_type bitfield + +#define LATHE_TYPE_AUTOLATHE (1<<0) +#define LATHE_TYPE_PROTOLATHE (1<<1) +#define LATHE_TYPE_CIRCUIT (1<<2) +#define LATHE_TYPE_PROSTHETICS (1<<3) +#define LATHE_TYPE_MECHA (1<<4) +#define LATHE_TYPE_BIOPRINTER (1<<5) + +DEFINE_BITFIELD(lathe_type, list( + BITFIELD(LATHE_TYPE_AUTOLATHE), + BITFIELD(LATHE_TYPE_PROTOLATHE), + BITFIELD(LATHE_TYPE_CIRCUIT), + BITFIELD(LATHE_TYPE_PROSTHETICS), + BITFIELD(LATHE_TYPE_MECHA), + BITFIELD(LATHE_TYPE_BIOPRINTER), +)) diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm index 5d8c19a0288f..ba8f9949ebb1 100644 --- a/code/__DEFINES/movespeed_modification.dm +++ b/code/__DEFINES/movespeed_modification.dm @@ -33,7 +33,7 @@ DEFINE_ENUM(movespeed_modifier_calculation_type, list( //* params for add_or_update_variable_movespeed_modifier /// multiplicative_slowdown -#define MOVESPEED_PARAM_DELAY_MOD "delay" +#define MOVESPEED_PARAM_HYPERBOLIC_SLOWDOWN "delay" /// multiply_speed #define MOVESPEED_PARAM_MULTIPLY_SPEED "multiply" /// absolute_max_tiles_per_second diff --git a/code/__DEFINES/power/balancing.dm b/code/__DEFINES/power/balancing.dm index d966ee58b549..7cf8428e1162 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,9 +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 - affects cell use_scaled - higher = things use less energy. handheld devices usually use this. -GLOBAL_VAR_INIT(cellefficiency, 1) + +/// 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) //* Computers diff --git a/code/__DEFINES/projectiles/gun.dm b/code/__DEFINES/projectiles/gun.dm index 445ac24e243c..e58bfbca3fb3 100644 --- a/code/__DEFINES/projectiles/gun.dm +++ b/code/__DEFINES/projectiles/gun.dm @@ -1,6 +1,39 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2024 Citadel Station Developers *// +//* firing_flags on gun firing procs *// + +/// 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. this must be false-y. +#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) + //* rendering enums - /obj/item/gun/ballistic *// //? render_bolt_overlay @@ -26,3 +59,17 @@ /// * render `[base state]-break-open` if open /// * render `[base state]-break-close` if closed #define BALLISTIC_RENDER_BREAK_BOTH 3 + +//* rendering enums - /obj/item/gun/ballistic/magnetic, /obj/item/gun/magnetic *// + +//? render_battery_overlay + +/// do not render battery state +#define MAGNETIC_RENDER_BATTERY_NEVER 0 +/// render `[base state]-battery` if in +#define MAGNETIC_RENDER_BATTERY_IN 1 +/// render `[base state]-battery` if out +#define MAGNETIC_RENDER_BATTERY_OUT 2 +/// * render `[base state]-battery-in` if in +/// * render `[base state]-battery-out` if out +#define MAGNETIC_RENDER_BATTERY_BOTH 3 diff --git a/code/__DEFINES/projectiles/gun_component.dm b/code/__DEFINES/projectiles/gun_component.dm new file mode 100644 index 000000000000..ff9bbf5c8f7d --- /dev/null +++ b/code/__DEFINES/projectiles/gun_component.dm @@ -0,0 +1,59 @@ +//* 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 ****// + +// None yet. diff --git a/code/__DEFINES/projectiles/guns-legacy.dm b/code/__DEFINES/projectiles/guns_legacy.dm similarity index 100% rename from code/__DEFINES/projectiles/guns-legacy.dm rename to code/__DEFINES/projectiles/guns_legacy.dm diff --git a/code/__DEFINES/projectiles/system.dm b/code/__DEFINES/projectiles/system.dm index 7d177322ac9a..c7c4e2808777 100644 --- a/code/__DEFINES/projectiles/system.dm +++ b/code/__DEFINES/projectiles/system.dm @@ -1,9 +1,9 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2024 Citadel Station Developers *// -//* rendering system -//* this is currently only used on ammo magazines, as guns use composition of datums -//* to determine their renderers instead. +//* rendering system *// +//* this is currently only used on ammo magazines, as guns use composition of datums *// +//* to determine their renderers instead. *// /// no automatic rendering #define GUN_RENDERING_DISABLED 0 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..ab2d74dd586c 100644 --- a/code/__HELPERS/math/angle.dm +++ b/code/__HELPERS/math/angle.dm @@ -52,3 +52,14 @@ . += 180 else if(x < 0) . += 360 + +/** + * get angle from center of bounding box of entity A to entity B + * + * * entity A and entity B may be atoms or movables or both + */ +/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 f2496f77efad..0e4ffe9fd96c 100644 --- a/code/__HELPERS/type_processing.dm +++ b/code/__HELPERS/type_processing.dm @@ -52,7 +52,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..06e0ae1e3d58 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] @@ -76,8 +76,8 @@ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_dropped)) if(!hooked) var/obj/item/item = parent - if(item.worn_mob()) - on_equipped(item, item.worn_mob(), item.worn_slot) + if(item.get_worn_mob()) + on_equipped(item, item.get_worn_mob(), item.worn_slot) /datum/component/passive_parry/UnregisterFromParent() . = ..() 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/items/wielding.dm b/code/datums/components/items/wielding.dm index de96fcbb4c37..605f28faa0f3 100644 --- a/code/datums/components/items/wielding.dm +++ b/code/datums/components/items/wielding.dm @@ -3,7 +3,7 @@ registered_type = /datum/component/wielding /// hands needed - var/hands + var/hands = 2 /// lazylist var/list/obj/item/offhand/wielding/offhands /// wielded user @@ -18,9 +18,12 @@ return COMPONENT_INCOMPATIBLE if((. = ..()) == COMPONENT_INCOMPATIBLE) return - src.hands = hands - src.on_wield = on_wield - src.on_unwield = on_unwield + if(hands) + src.hands = hands + if(on_wield) + src.on_wield = on_wield + if(on_unwield) + src.on_unwield = on_unwield /datum/component/wielding/RegisterWithParent() . = ..() @@ -37,11 +40,25 @@ /datum/component/wielding/proc/signal_examine(datum/source, mob/user, list/examine_list) SIGNAL_HANDLER - examine_list += SPAN_NOTICE("[parent] seems to be able to be used with [hands] hands. Press your \"Wield Item\" keybind to toggle wielding.") + examine_list += SPAN_NOTICE("[parent] seems to be able to be used with [hands] hands. Press your \"Wield Item\" keybind [user?.client?.print_keys_for_keybind_with_prefs_link(/datum/keybinding/mob/multihand_wield, " ")]to toggle wielding.") /datum/component/wielding/proc/signal_dropped(datum/source, mob/user, flags, atom/newloc) unwield() +/** + * todo: event_args/actor + */ +/datum/component/wielding/proc/auto_wield() + var/obj/item/our_item = parent + var/mob/holding = our_item.is_being_held() + if(!holding || wielder) + unwield() + else + wield(holding) + +/** + * todo: event_args/actor + */ /datum/component/wielding/proc/wield(mob/wielder) if(src.wielder) return @@ -62,12 +79,18 @@ to_chat(src.wielder, SPAN_WARNING("You start wielding [parent] with [hands == 2? "both" : "[hands]"] hands.")) post_wield(src.wielder, hands) +/** + * todo: event_args/actor + */ /datum/component/wielding/proc/post_wield(mob/user, hands) if(on_wield) on_wield.Invoke(user, hands) var/obj/item/I = parent I.on_wield(user, hands) +/** + * todo: event_args/actor + */ /datum/component/wielding/proc/unwield(gcing) if(!wielder) return @@ -83,6 +106,9 @@ wielder = null post_unwield(unwielding, hands) +/** + * todo: event_args/actor + */ /datum/component/wielding/proc/post_unwield(mob/user, hands) if(on_unwield) on_unwield.Invoke(user, hands) @@ -108,9 +134,20 @@ //* Item Hooks *// +/** + * Call to attempt to wield/unwield with wield component. + */ +/obj/item/proc/auto_wield(datum/event_args/actor/actor) + var/datum/component/wielding/wielding_component = GetComponent(/datum/component/wielding) + if(!wielding_component) + return + wielding_component.auto_wield(actor.performer) + /** * Called when wielded via wielding component. * + * todo: /datum/event_args/actor + * * * This is a default hook that's always executed, even if there's a callback provided to the component. */ /obj/item/proc/on_wield(mob/user, hands) @@ -119,7 +156,11 @@ /** * Called when wielded via wielding component. * + * todo: /datum/event_args/actor + * * * This is a default hook that's always executed, even if there's a callback provided to the component. */ /obj/item/proc/on_unwield(mob/user, hands) return + + 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..9b8c87ee7fd3 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 @@ -316,7 +316,7 @@ // item processing if(isitem(attack_source_descriptor)) var/obj/item/item_source_descriptor = attack_source_descriptor - var/mob/mob_holding_item = item_source_descriptor.worn_mob() + var/mob/mob_holding_item = item_source_descriptor.get_worn_mob() if(mob_holding_item) attack_descriptor = "[mob_holding_item]'s [item_source_descriptor]" @@ -437,8 +437,8 @@ /datum/parry_frame/proc/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, efficiency, obj/item/weapon, datum/event_args/actor/clickchain/e_args, tool_text) . = shieldcall_returns // todo: doesn't take into account any damage randomization - var/estimated_severity = clamp(weapon.damage_force * e_args.damage_multiplier / 20 * 75, 0, 100) - e_args.damage_multiplier *= clamp(1 - efficiency, 0, 1) + var/estimated_severity = clamp(weapon.damage_force * e_args.melee_damage_multiplier / 20 * 75, 0, 100) + e_args.melee_damage_multiplier *= clamp(1 - efficiency, 0, 1) . = perform_aftereffects(defending, ATTACK_TYPE_MELEE, efficiency, weapon, ., e_args) perform_audiovisuals(defending, ATTACK_TYPE_MELEE, efficiency, weapon, ., estimated_severity, weapon, tool_text) if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) @@ -457,8 +457,8 @@ /datum/parry_frame/proc/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, efficiency, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args, tool_text) . = shieldcall_returns // todo: doesn't take into account any damage randomization - var/estimated_severity = clamp(style.damage * e_args.damage_multiplier / 20 * 75, 0, 100) - e_args.damage_multiplier *= clamp(1 - efficiency, 0, 1) + var/estimated_severity = clamp(style.damage * e_args.melee_damage_multiplier / 20 * 75, 0, 100) + e_args.melee_damage_multiplier *= clamp(1 - efficiency, 0, 1) . = perform_aftereffects(defending, ATTACK_TYPE_UNARMED, efficiency, style, ., e_args) perform_audiovisuals(defending, ATTACK_TYPE_UNARMED, efficiency, style, ., estimated_severity, style, tool_text) if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) @@ -478,7 +478,7 @@ . = shieldcall_returns // todo: doesn't take into account any damage randomization var/estimated_severity = 50 - e_args.damage_multiplier *= clamp(1 - efficiency, 0, 1) + e_args.melee_damage_multiplier *= clamp(1 - efficiency, 0, 1) . = perform_aftereffects(defending, ATTACK_TYPE_TOUCH, efficiency, null, ., e_args) perform_audiovisuals(defending, ATTACK_TYPE_TOUCH, efficiency, null, ., estimated_severity, e_args.performer, tool_text) if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) diff --git a/code/datums/design/design.dm b/code/datums/design/design.dm index 7c2f1feda134..a5da2138bb3c 100644 --- a/code/datums/design/design.dm +++ b/code/datums/design/design.dm @@ -38,8 +38,11 @@ /// desc of item before any desc-generation is done. also shown in ui. if null, it'll be auto-detected from the build_path if possible. var/build_desc /// type of what we build + /// + /// * Autodetection only works on /obj's. var/build_path - /// types of lathes that can print us + /// Types of lathes that can print us. + /// * Type: /bitfield/lathe_type var/lathe_type = NONE /// time needed in deciseconds - for stacks, this is time *PER SHEET*. var/work = 5 SECONDS @@ -85,10 +88,13 @@ is_stack = TRUE var/obj/item/stack/stack_path = build_path max_stack = initial(stack_path.max_amount) - var/obj/item/instance = SSatoms.instance_atom_immediate(build_path) + var/obj/instance = SSatoms.instance_atom_immediate(build_path) // lathe designs shouldn't be qdeleting, but incase someone puts in a random.. if(QDELETED(instance)) return + if(!isobj(instance)) + qdel(instance) + return if(isnull(materials_base)) var/list/fetched = instance.detect_material_base_costs() if(length(fetched)) diff --git a/code/datums/elements/clothing/dynamic_recolor.dm b/code/datums/elements/clothing/dynamic_recolor.dm index 23968124d2ac..0ad6ee664014 100644 --- a/code/datums/elements/clothing/dynamic_recolor.dm +++ b/code/datums/elements/clothing/dynamic_recolor.dm @@ -22,7 +22,7 @@ if(isnull(queried)) return - if(check_possession && gear.worn_mob() != user) + if(check_possession && gear.get_worn_mob() != user) return if(check_mobility && !CHECK_MOBILITY(usr, MOBILITY_CAN_USE)) diff --git a/code/datums/event_args/actor.dm b/code/datums/event_args/actor.dm index fa99f22f136f..2911a370716e 100644 --- a/code/datums/event_args/actor.dm +++ b/code/datums/event_args/actor.dm @@ -1,7 +1,16 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +// todo: mob.generate_simulated_actor(...) + /** * used to hold semantic data about an action being done by an actor vs initiator (controller) */ /datum/event_args/actor + /// Is this a simulated event? + /// * This is used for logging. + /// * This should be set to TRUE if this didn't originate from a player's client. + var/simulated = FALSE /// the mob performing the action var/mob/performer /// the mob actually initiating the action, e.g. a remote controller. @@ -12,7 +21,10 @@ src.initiator = initiator || performer /datum/event_args/actor/clone(include_contents) - return new /datum/event_args/actor(performer, initiator) + var/datum/event_args/actor/cloning = new + cloning.performer = performer + cloning.initiator = initiator + return cloning //* Logging *// diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm index 50c24f7ed3ef..c09ff0632710 100644 --- a/code/datums/event_args/clickchain.dm +++ b/code/datums/event_args/clickchain.dm @@ -1,20 +1,44 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +// todo: mob.generate_simulated_clickchain(...) to generate a simulated click + /** * used to hold data about a click (melee/ranged/other) action * - * the click may be real or fake. - * - * * clickchain flags are deliberately not stored in here; you're supposed to modify and return them a ton, so it's inefficient to put it in here. + * * The click may be real or fake. + * * Clickchain flags are deliberately not stored in here; you're supposed to modify and return them a ton, so it's inefficient to put it in here. * * This is required for item swings / interaction, usually, not just base /event_args/actor. + * + * todo: helpers for true clickchain event generation / simulation */ /datum/event_args/actor/clickchain + //* Using Data *// + /// optional: attack intent - var/intent - /// optional: click params - var/list/params + var/using_intent /// optional: hand index, if any - var/hand_index + var/using_hand_index /// with item, if any - var/obj/item/using + var/obj/item/using_item + + //* Click Data *// + + /// optional: click params + var/list/click_params + /// did we unpack click params? + var/tmp/click_params_unpacked = FALSE + /// pixel x on tile clicked + var/tmp/click_params_tile_px + /// pixel y on tile clicked + var/tmp/click_params_tile_py + /// tile x from bottom left of screen, starting at 1 + var/tmp/click_params_screen_tx + /// tile y from bottom left of screen, starting at 1 + var/tmp/click_params_screen_ty + + //* Target Data *// + /// optional: target atom var/atom/target @@ -25,14 +49,78 @@ /// todo: implement; needs slight clickchain/melee overhaul /// /// * Allowed to be changed by shieldcalls and other intercepts - var/damage_multiplier = 1 + var/melee_damage_multiplier = 1 + + //* Resolved Data *// + + /// Resolved angle from performer + /// * This is in degrees clockwise of north. + var/tmp/resolved_angle_from_performer /datum/event_args/actor/clickchain/New(mob/performer, mob/initiator, atom/target, list/params, intent) ..() src.target = target - src.params = params || list() - src.intent = intent + src.click_params = params || list() + src.using_intent = intent /datum/event_args/actor/clickchain/clone() - var/datum/event_args/actor/clickchain/cloned = new(performer, initiator, target, params, intent) - return cloned + var/datum/event_args/actor/clickchain/cloning = ..() + cloning.using_intent = using_intent + cloning.using_hand_index = using_hand_index + cloning.using_item = using_item + cloning.click_params = click_params + cloning.target = target + cloning.melee_damage_multiplier = melee_damage_multiplier + return cloning + +/** + * Resolves the angle of the clicked pixel from a given entity. + * + * * This defaults to the clickchain's performer. + * * This means that this proc should correctly offset the effective angle if the entity is not the center of the + * initiator's screen! + * * Calling this with no arguments or with the clickchain's performer will also resolve the pixel x/y + * of the click + * + * @return degrees clockwise from north, or null if failed to resolve + */ +/datum/event_args/actor/clickchain/proc/resolve_click_angle(atom/from_entity = performer) + if((from_entity == performer) && !isnull(resolved_angle_from_performer)) + return resolved_angle_from_performer + // todo: this relies on a client existing. this shouldn't be necessary for a true click simulation! + if(!initiator?.client) + return + if(!click_params) + return + unpack_click_params() + // target x/y from bottom left of screen + var/target_x = click_params_screen_tx * WORLD_ICON_SIZE + click_params_tile_px - WORLD_ICON_SIZE + var/target_y = click_params_screen_ty * WORLD_ICON_SIZE + click_params_tile_py - WORLD_ICON_SIZE + // origin x/y of the performing mob on the screen + // todo: this doesn't take into account if the performer isn't the eye of the initiator's client! + // that'll need to be added for remote control to work. + var/origin_x = (initiator.client.current_viewport_width * WORLD_ICON_SIZE * 0.5) - initiator.client.pixel_x + var/origin_y = (initiator.client.current_viewport_height * WORLD_ICON_SIZE * 0.5) - initiator.client.pixel_y + // atan args are reversed for clockwise from north instead of counterclockwise from east + . = arctan(target_y - origin_y, target_x - origin_x) + if(from_entity == performer) + resolved_angle_from_performer = . + +/datum/event_args/actor/clickchain/proc/unpack_click_params() + if(click_params_unpacked) + return + click_params_unpacked = TRUE + if(!click_params) + return + // Handle `screen-loc`, specified as "tile_x:pixel_x,tile_y:pixel_y" + var/list/x_y_split = splittext(click_params["screen-loc"], ",") + if(length(x_y_split)) + var/list/x_split = splittext(x_y_split[1], ":") + var/list/y_split = splittext(x_y_split[2], ":") + click_params_screen_tx = text2num(x_split[1]) + click_params_screen_ty = text2num(y_split[1]) + click_params_tile_px = text2num(x_split[2]) + click_params_tile_py = text2num(y_split[2]) + +/datum/event_args/actor/clickchain/proc/legacy_get_target_zone() + return initiator?.zone_sel?.selecting 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/datums/status_effects/grouped/staggered.dm b/code/datums/status_effects/grouped/staggered.dm index 0d082c8b393f..481aa04e2df8 100644 --- a/code/datums/status_effects/grouped/staggered.dm +++ b/code/datums/status_effects/grouped/staggered.dm @@ -16,7 +16,7 @@ return applied_highest = highest owner.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_staggered, params = list( - MOVESPEED_PARAM_DELAY_MOD = highest + MOVESPEED_PARAM_HYPERBOLIC_SLOWDOWN = highest )) /datum/status_effect/grouped/staggered/on_remove() 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..a894c8da7945 100644 --- a/code/game/click/drag_drop.dm +++ b/code/game/click/drag_drop.dm @@ -45,6 +45,7 @@ over_object.MouseDroppedOn(src, user, proximity, params) // todo: less shit naming convenions for these +// todo: just completely rework everything god damn mousedown / mousedrag / mousemove handling is ass /** * user dropped us onto an atom mouse-drag-drop @@ -92,8 +93,6 @@ return /client - var/list/atom/selected_target[2] - var/obj/item/active_mousedown_item = null var/mouseParams = "" var/mouseLocation = null var/mouseObject = null @@ -102,76 +101,8 @@ var/atom/middragatom /client/MouseDown(object, location, control, params) - /* - if (mouse_down_icon) - mouse_pointer_icon = mouse_down_icon - */ - var/delay = mob.CanMobAutoclick(object, location, params) - if(delay) - selected_target[1] = object - selected_target[2] = params - while(selected_target[1]) - Click(selected_target[1], location, control, selected_target[2]) - sleep(delay) - active_mousedown_item = mob.canMobMousedown(object, location, params) - if(active_mousedown_item) - active_mousedown_item.onMouseDown(object, location, params, mob) /client/MouseUp(object, location, control, params) - /* - if (mouse_up_icon) - mouse_pointer_icon = mouse_up_icon - */ - selected_target[1] = null - if(active_mousedown_item) - active_mousedown_item.onMouseUp(object, location, params, mob) - active_mousedown_item = null - -/mob/proc/CanMobAutoclick(object, location, params) - -/mob/living/carbon/CanMobAutoclick(atom/object, location, params) - if(!object.IsAutoclickable()) - return - var/obj/item/h = get_active_held_item() - if(h) - . = h.CanItemAutoclick(object, location, params) - -/mob/proc/canMobMousedown(atom/object, location, params) - -/mob/living/carbon/canMobMousedown(atom/object, location, params) - var/obj/item/H = get_active_held_item() - if(H) - . = H.canItemMouseDown(object, location, params) - -/obj/item/proc/CanItemAutoclick(object, location, params) - -/obj/item/proc/canItemMouseDown(object, location, params) - if(canMouseDown) - return src - -/obj/item/proc/onMouseDown(object, location, params, mob) - return - -/obj/item/proc/onMouseUp(object, location, params, mob) - return - -/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 - -/atom/movable/screen/IsAutoclickable() - . = 0 - -/atom/movable/screen/click_catcher/IsAutoclickable() - . = 1 //Please don't roast me too hard /client/MouseMove(object,location,control,params) @@ -179,18 +110,8 @@ mouseLocation = location mouseObject = object mouseControlObject = control - /* - if(mob && LAZYLEN(mob.mousemove_intercept_objects)) - for(var/datum/D in mob.mousemove_intercept_objects) - D.onMouseMove(object, location, control, params) - */ - if(!show_popup_menus && mob) //CIT CHANGE - passes onmousemove() to mobs - mob.onMouseMove(object, location, control, params) //CIT CHANGE - ditto ..() -/datum/proc/onMouseMove(object, location, control, params) - return - /client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) var/list/L = params2list(params) if (L["middle"]) @@ -204,15 +125,6 @@ mouseLocation = over_location mouseObject = over_object mouseControlObject = over_control - if(selected_target[1] && over_object && over_object.IsAutoclickable()) - selected_target[1] = over_object - selected_target[2] = params - if(active_mousedown_item) - active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - - -/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - return /client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params) if (middragatom == src_object) diff --git a/code/game/click/item_attack.dm b/code/game/click/item_attack.dm index f64c8bb68fa4..f9ea265694a5 100644 --- a/code/game/click/item_attack.dm +++ b/code/game/click/item_attack.dm @@ -29,10 +29,10 @@ avoid code duplication. This includes items that may sometimes act as a standard return A.attackby(src, user, params, clickchain_flags, attack_modifier) // No comment -/atom/proc/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) +/atom/proc/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier = 1) return I.standard_melee_attack(src, user, clickchain_flags, params, damage_multiplier, user.zone_sel?.selecting, user.a_intent) -/mob/living/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) +/mob/living/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier = 1) if(can_operate(src) && user.a_intent != INTENT_HARM && I.do_surgery(src,user)) return NONE if(attempt_vr(src,"vore_attackby",args)) diff --git a/code/game/click/items.dm b/code/game/click/items.dm index 598a10344357..3300f9d0dd6b 100644 --- a/code/game/click/items.dm +++ b/code/game/click/items.dm @@ -5,7 +5,8 @@ //? Click-Chain system - using an item in hand to "attack", whether in melee or ranged. -// todo: refactor attack object/mob to just melee attack or something +// todo: refactor attack object/mob to just melee_attack_chain and a single melee attack system or something +// todo: yeah most of this file needs re-evaluated again, especially for event_args/actor/clickchain support & right clicks /** * Called when trying to click something that the user can Reachability() to. @@ -28,6 +29,7 @@ // todo: inject something here for 'used as item' much like /tg/, to get rid of attackby pattern var/datum/event_args/actor/clickchain/e_args = new(user) + e_args.click_params = params if((. |= item_attack_chain(target, e_args, ., params)) & CLICKCHAIN_DO_NOT_PROPAGATE) return @@ -346,13 +348,13 @@ // no targeting return NONE // check intent - if((item_flags & ITEM_CAREFUL_BLUDGEON) && clickchain.intent == INTENT_HELP) + if((item_flags & ITEM_CAREFUL_BLUDGEON) && clickchain.using_intent == INTENT_HELP) clickchain.initiator.action_feedback(SPAN_WARNING("You refrain from hitting [target] because your intent is set to help."), src) return CLICKCHAIN_DO_NOT_PROPAGATE //? legacy: decloak clickchain.performer.break_cloak() // set mult - clickchain.damage_multiplier *= mult + clickchain.melee_damage_multiplier *= mult // click cooldown // todo: clickcd rework clickchain.performer.setClickCooldown(clickchain.performer.get_attack_speed(src)) diff --git a/code/game/click/mobs.dm b/code/game/click/mobs.dm index 656b62c6baf1..f2a10007c366 100644 --- a/code/game/click/mobs.dm +++ b/code/game/click/mobs.dm @@ -134,9 +134,9 @@ constructed.initiator = src constructed.performer = src constructed.target = target - constructed.params = list() - constructed.intent = a_intent - constructed.hand_index = active_hand + constructed.click_params = list() + constructed.using_intent = a_intent + constructed.using_hand_index = active_hand if(!unarmed) - constructed.using = get_active_held_item() + constructed.using_item = get_active_held_item() return constructed diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-antimaterial.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-antimaterial.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-antimaterial.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-antimaterial.dm diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-heavy_rifle.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-heavy_rifle.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-heavy_rifle.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-heavy_rifle.dm diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-heavy_sidearm.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-heavy_sidearm.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-heavy_sidearm.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-heavy_sidearm.dm diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-light_rifle.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-light_rifle.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-light_rifle.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-light_rifle.dm diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-light_sidearm.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-light_sidearm.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-light_sidearm.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-light_sidearm.dm diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-shotgun.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-shotgun.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary-shotgun.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition-shotgun.dm diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_expedition.dm similarity index 100% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_expeditionary.dm rename to code/game/content/factions/corporations/nanotrasen/guns/nt_expedition.dm diff --git a/code/game/content/factions/corporations/nanotrasen/guns/nt_isd.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_isd.dm new file mode 100644 index 000000000000..49025af67ba7 --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/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 fell short. + "} + 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/guns/nt_pmd.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_pmd.dm new file mode 100644 index 000000000000..2f534aadcbf2 --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/guns/nt_pmd.dm @@ -0,0 +1,72 @@ +/** + * 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, add icon states to firemodes + 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/guns/nt_protomag-ammo.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-ammo.dm new file mode 100644 index 000000000000..0e76a3c6bf52 --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-ammo.dm @@ -0,0 +1,141 @@ +//* 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 + effective_mass_multiplier = /obj/item/ammo_casing/nt_protomag::effective_mass_multiplier * 0.5 + +#warn make these lose performance if shot without magnetic priming + +/obj/item/ammo_casing/nt_protomag/magboosted/standard + projectile_type = /obj/projectile/nt_protomag/standard + stripe_color = /obj/projectile/nt_protomag/standard::color + materials_base = list( + /datum/prototype/material/steel::id = 75, + /datum/prototype/material/glass::id = 25, + ) + +/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 + stripe_color = /obj/projectile/nt_protomag/sabot::color + materials_base = list( + /datum/prototype/material/steel::id = 125, + /datum/prototype/material/glass::id = 25, + ) + +// 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 +// stripe_color = /obj/projectile/nt_protomag/shredder::color + +/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 + stripe_color = /obj/projectile/nt_protomag/impact::color + materials_base = list( + /datum/prototype/material/steel::id = 75, + /datum/prototype/material/glass::id = 75, + ) + +/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 + stripe_color = /obj/projectile/nt_protomag/practice::color + materials_base = list( + /datum/prototype/material/steel::id = 25, + /datum/prototype/material/glass::id = 12.5, + ) + +#warn materials for below +/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 + stripe_color = /obj/projectile/nt_protomag/smoke::color + +/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 + stripe_color = /obj/projectile/nt_protomag/emp::color + +// 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 +// stripe_color = /obj/projectile/nt_protomag/concussive::color + +/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 + stripe_color = /obj/projectile/nt_protomag/penetrator::color + +/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 + stripe_color = /obj/projectile/nt_protomag/shock::color + +/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 + stripe_color = /obj/projectile/nt_protomag/flare::color + +// 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 +// stripe_color = /obj/projectile/nt_protomag/incendiary::color + +// 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 +// stripe_color = /obj/projectile/nt_protomag/reagent::color diff --git a/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-caliber.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-caliber.dm new file mode 100644 index 000000000000..8b5b11ff2f1d --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-caliber.dm @@ -0,0 +1,17 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * NT Protomag calibers. + * + * These share specifications with NT Expeditionary calibers, and are cross compatible. + */ +/datum/ammo_caliber/nt_protomag + caliber = "nt-protomag" + diameter = /datum/ammo_caliber/nt_expedition/heavy_rifle::diameter + length = /datum/ammo_caliber/nt_expedition/heavy_rifle::length + +/datum/ammo_caliber/nt_protomag/antimaterial + caliber = "nt-protomag-antimaterial" + diameter = /datum/ammo_caliber/nt_expedition/antimaterial::diameter + length = /datum/ammo_caliber/nt_expedition/antimaterial::length diff --git a/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-magazine.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-magazine.dm new file mode 100644 index 000000000000..270bca5cf75e --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-magazine.dm @@ -0,0 +1,98 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/datum/prototype/design/generated/nt_protomag_ammo + category = DESIGN_CATEGORY_MUNITIONS + subcategory = DESIGN_SUBCATEGORY_AMMO + +// todo: make this fit in webbing +/obj/item/ammo_magazine/nt_protomag + abstract_type = /obj/item/ammo_magazine/nt_protomag + desc = "A magazine for a magnetic weapon of some kind." + icon = 'icons/content/factions/corporations/nanotrasen/items/guns/protomag/magazines.dmi' + ammo_caliber = /datum/caliber/nt_protomag + +//* Sidearm Magazines *// + +/obj/item/ammo_magazine/nt_protomag/sidearm + name = "protomag sidearm magazine" + ammo_max = 8 + icon_state = "pistol-1" + base_icon_state = "pistol" + rendering_static_overlay = "pistol-stripe" + rendering_system = GUN_RENDERING_STATES + rendering_count = 1 + + w_class = WEIGHT_CLASS_NORMAL // no boxes + weight_volume = WEIGHT_VOLUME_TINY + slot_flags = SLOT_POCKET + + magazine_restrict = /obj/item/gun/ballistic/magnetic/modular/nt_protomag/sidearm + +//* Rifle Magazines *// + +/obj/item/ammo_magazine/nt_protomag/rifle + name = "protomag rifle magazine" + ammo_max = 16 + icon_state = "rifle-map" + base_icon_state = "rifle" + rendering_static_overlay = "rifle-stripe" + + rendering_system = GUN_RENDERING_STATES + rendering_count = 6 + rendering_segment_x_offset = -2 + + w_class = WEIGHT_CLASS_NORMAL // no boxes + weight_volume = WEIGHT_VOLUME_SMALL + slot_flags = SLOT_POCKET + + magazine_restrict = /obj/item/gun/ballistic/magnetic/modular/nt_protomag/rifle + +//* Typegen *// + +/** + * Generates magazines and designs for normal protomag ammo. + */ +#define NT_PROTOMAG_MAG_TYPEGEN(ID, SUFFIX, NAME, AMMO) \ +/obj/item/ammo_magazine/nt_protomag/pistol##SUFFIX { \ + name = "protomag sidearm magazine (" + NAME + ")"; \ + ammo_preload = /obj/item/ammo_casing/nt_protomag##AMMO; \ + rendering_static_overlay_color = /obj/item/ammo_casing/nt_protomag##AMMO::stripe_color; \ +} \ +/obj/item/ammo_magazine/nt_protomag/rifle##SUFFIX { \ + name = "protomag rifle magazine (" + NAME + ")"; \ + ammo_preload = /obj/item/ammo_casing/nt_protomag##AMMO; \ + rendering_static_overlay_color = /obj/item/ammo_casing/nt_protomag##AMMO::stripe_color; \ +} \ +GENERATE_DESIGN_FOR_AUTOLATHE(/obj/item/ammo_magazine/nt_protomag/pistol##SUFFIX, /nt_protomag_ammo/pistol/##SUFFIX, "NTMagPistolAmmo" + ##ID); \ +GENERATE_DESIGN_FOR_AUTOLATHE(/obj/item/ammo_magazine/nt_protomag/rifle##SUFFIX, /nt_protomag_ammo/rifle/##SUFFIX, "NTMagRifleAmmo" + ##ID); + +/** + * Generates magazines and designs for special protomag ammo. + */ +#define NT_PROTOMAG_MAG_TYPEGEN_SPECIAL(ID, SUFFIX, NAME, AMMO) \ +NT_PROTOMAG_MAG_TYPEGEN(ID, SUFFIX, NAME, AMMO); \ +/datum/prototype/design/generated/nt_protomag_ammo/pistol/##SUFFIX { \ + lathe_type = LATHE_TYPE_AUTOLATHE | LATHE_TYPE_PROTOLATHE; \ +} \ +/datum/prototype/design/generated/nt_protomag_ammo/rifle/##SUFFIX { \ + lathe_type = LATHE_TYPE_AUTOLATHE | LATHE_TYPE_PROTOLATHE; \ +} + +NT_PROTOMAG_MAG_TYPEGEN("Standard", /standard, "standard", /magboosted/standard) +NT_PROTOMAG_MAG_TYPEGEN("Sabot", /sabot, "sabot", /magboosted/sabot) +// NT_PROTOMAG_MAG_TYPEGEN("Shredder", /shredder, "shredder", /magboosted/shredder) +NT_PROTOMAG_MAG_TYPEGEN("Impact", /impact, "impact", /magboosted/impact) +NT_PROTOMAG_MAG_TYPEGEN("Practice", /practice, "practice", /magboosted/practice) + +NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Smoke", /smoke, "smoke", /magnetic/smoke) +NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Emp", /emp, "emp", /magnetic/emp) +// NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Concussive", /concussive, "concussive", /magnetic/concussive) +NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Penetrator", /penetrator, "penetrator", /magnetic/penetrator) +NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Shock", /shock, "shock", /magnetic/shock) +NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Flare", /flare, "flare", /magnetic/flare) +// NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Incendiary", /incendiary, "incendiary", /magnetic/incendiary) +// NT_PROTOMAG_MAG_TYPEGEN_SPECIAL("Reagent", /reagent, "reagent", /magnetic/reagent) + +#undef NT_PROTOMAG_MAG_TYPEGEN +#undef NT_PROTOMAG_MAG_TYPEGEN_SPECIAL diff --git a/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-projectile.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-projectile.dm new file mode 100644 index 000000000000..b451a7970a7b --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag-projectile.dm @@ -0,0 +1,85 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/image/projectile/nt_protomag_emissive + 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 + abstract_type = /obj/projectile/nt_protomag + icon = 'icons/content/factions/corporations/nanotrasen/items/guns/protomag/projectile.dmi' + icon_state = "kinetic" + speed = /obj/projectile::speed * 1.1 + +/obj/projectile/nt_protomag/Initialize(mapload) + . = ..() + add_overlay(/image/projectile/nt_protomag_emissive) + +/obj/projectile/nt_protomag/standard + name = "magnetic slug" + color = "#ccaa55" + +/obj/projectile/nt_protomag/sabot + name = "dense slug" + color = "#ff7700" + speed = /obj/projectile/nt_protomag::speed * 1.2 + +// 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" + speed = /obj/projectile/nt_protomag::speed * 0.9 + +/obj/projectile/nt_protomag/practice + name = "lightweight slug" + color = "#ffffff" + damage_force = 5 + damage_tier = BULLET_TIER_LOW + +/obj/projectile/nt_protomag/smoke + name = "disintegrating slug" + color = "#888888" + speed = /obj/projectile/nt_protomag::speed * 0.6 + +/obj/projectile/nt_protomag/emp + name = "ion slug" + color = "#aaaaff" + base_projectile_effects = list( + /datum/projectile_effect/detonation/legacy_emp{ + sev_3 = 2; + } + ) + speed = /obj/projectile/nt_protomag::speed * 0.8 + +// 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" + speed = /obj/projectile/nt_protomag::speed * 1.25 + +/obj/projectile/nt_protomag/shock + name = "piezo slug" + color = "#cccc55" + speed = /obj/projectile/nt_protomag::speed * 0.8 + +/obj/projectile/nt_protomag/flare + name = "tracer shot" + color = "#aa3333" + speed = /obj/projectile/nt_protomag::speed * 0.6 + +// 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/guns/nt_protomag.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag.dm new file mode 100644 index 000000000000..a635d0f94e8c --- /dev/null +++ b/code/game/content/factions/corporations/nanotrasen/guns/nt_protomag.dm @@ -0,0 +1,52 @@ +//* 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 + icon = 'icons/content/factions/corporations/nanotrasen/items/guns/protomag/gun.dmi' + 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. + "} + +//* Sidearm *// + +#warn impl all + +/obj/item/gun/ballistic/magnetic/modular/nt_protomag/sidearm + name = "protomag sidearm" + item_renderer = /datum/gun_item_renderer/overlays{ + count = 4; + use_empty = TRUE; + use_single = TRUE; + } + render_magazine_overlay = MAGAZINE_CLASS_GENERIC + render_battery_overlay = MAGNETIC_RENDER_BATTERY_IN + fire_sound = 'sound/factions/corporations/nanotrasen/protomag-pistol.ogg' + base_shot_power = /obj/item/cell/device/weapon::maxcharge * (1 / (/obj/item/ammo_magazine/nt_protomag/sidearm::ammo_max * 4)) + +//* Rifle *// + +#warn impl all + +/obj/item/gun/ballistic/magnetic/modular/nt_protomag/rifle + name = "protomag rifle" + item_renderer = /datum/gun_item_renderer/overlays{ + count = 4; + use_empty = TRUE; + use_single = TRUE; + } + render_magazine_overlay = MAGAZINE_CLASS_GENERIC + render_battery_overlay = MAGNETIC_RENDER_BATTERY_IN + base_shot_power = /obj/item/cell/device/weapon::maxcharge * (1 / (/obj/item/ammo_magazine/nt_protomag/rifle::ammo_max * 4)) + fire_sound = 'sound/factions/corporations/nanotrasen/protomag-rifle.ogg' + +#warn materials / rnd designs diff --git a/code/game/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dm b/code/game/content/factions/corporations/nanotrasen/guns/nt_pulse.dm similarity index 72% rename from code/game/content/factions/corporations/nanotrasen/items/guns/nt_pulse.dm rename to code/game/content/factions/corporations/nanotrasen/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/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/gamemodes/technomancer/devices/gloves_of_regen.dm b/code/game/gamemodes/technomancer/devices/gloves_of_regen.dm index 00630d77e3c9..d3034f6a9d07 100644 --- a/code/game/gamemodes/technomancer/devices/gloves_of_regen.dm +++ b/code/game/gamemodes/technomancer/devices/gloves_of_regen.dm @@ -49,7 +49,7 @@ return ..() /obj/item/clothing/gloves/regen/process(delta_time) - var/mob/living/wearer = worn_mob() + var/mob/living/wearer = get_worn_mob() if(!wearer || wearer.isSynthetic() || wearer.stat == DEAD || wearer.nutrition <= 10) return // Robots and dead people don't have a metabolism. diff --git a/code/game/machinery/doors/airlock/airlock.dm b/code/game/machinery/doors/airlock/airlock.dm index b18db9b23ca5..a9ded8fe5a79 100644 --- a/code/game/machinery/doors/airlock/airlock.dm +++ b/code/game/machinery/doors/airlock/airlock.dm @@ -841,11 +841,6 @@ About the new airlock wires panel: if(locked) to_chat(user, "The airlock's bolts prevent it from being forced.") else if( !welded && !operating ) - if(istype(C, /obj/item/material/twohanded/fireaxe)) // If this is a fireaxe, make sure it's held in two hands. - var/obj/item/material/twohanded/fireaxe/F = C - if(!F.wielded) - to_chat(user, "You need to be wielding \the [F] to do that.") - return // At this point, it's an armblade or a fireaxe that passed the wielded test, let's try to open it. if(density) spawn(0) @@ -857,7 +852,6 @@ About the new airlock wires panel: ..() else ..() - return /obj/machinery/door/airlock/phoron/attackby(C as obj, mob/user as mob) if(C) diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm index 9df2f78a99fd..6a3539057dcf 100644 --- a/code/game/machinery/doors/blast_door.dm +++ b/code/game/machinery/doors/blast_door.dm @@ -135,12 +135,6 @@ src.add_fingerprint(user, 0, I) if(istype(I, /obj/item)) // For reasons unknown, sometimes C is actually not what it is advertised as, like a mob. if(I.pry == 1 && (user.a_intent != INTENT_HARM || (machine_stat & BROKEN))) // Can we pry it open with something, like a crowbar/fireaxe/lingblade? - if(istype(I,/obj/item/material/twohanded/fireaxe)) // Fireaxes need to be in both hands to pry. - var/obj/item/material/twohanded/fireaxe/F = I - if(!F.wielded) - to_chat(user, "You need to be wielding \the [F] to do that.") - return - // If we're at this point, it's a fireaxe in both hands or something else that doesn't care for twohanding. if(((machine_stat & NOPOWER) || (machine_stat & BROKEN)) && !( src.operating )) force_toggle(1, user) diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 423e176d4d09..2da5544818ae 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -345,12 +345,6 @@ GLOBAL_LIST_INIT(firelock_align_types, typecacheof(list( "You try to pry \the [src] [density ? "open" : "closed"], but it is welded in place!",\ "You hear someone struggle and metal straining.") return - - if(istype(C,/obj/item/material/twohanded/fireaxe)) - var/obj/item/material/twohanded/fireaxe/F = C - if(!F.wielded) - return - if(prying) to_chat(user, "Someone's already prying that [density ? "open" : "closed"].") return 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-carry_weight.dm b/code/game/objects/items-carry_weight.dm index 4cdcdebb2999..dc242d4d836e 100644 --- a/code/game/objects/items-carry_weight.dm +++ b/code/game/objects/items-carry_weight.dm @@ -23,7 +23,7 @@ return 0 . -= weight_registered weight_registered += . - var/mob/living/wearer = worn_mob() + var/mob/living/wearer = get_worn_mob() if(istype(wearer)) wearer.adjust_current_carry_weight(.) @@ -35,12 +35,12 @@ return 0 . -= encumbrance_registered encumbrance_registered += . - var/mob/living/wearer = worn_mob() + var/mob/living/wearer = get_worn_mob() if(istype(wearer)) wearer.adjust_current_carry_encumbrance(.) /obj/item/proc/update_flat_encumbrance() - var/mob/living/wearer = worn_mob() + var/mob/living/wearer = get_worn_mob() if(istype(wearer)) wearer.recalculate_carry() @@ -68,7 +68,7 @@ if(amount == slowdown) return slowdown = amount - worn_mob()?.update_item_slowdown() + get_worn_mob()?.update_item_slowdown() /obj/item/proc/propagate_weight(old_weight, new_weight) loc?.on_contents_weight_change(src, old_weight, new_weight) diff --git a/code/game/objects/items-interaction.dm b/code/game/objects/items-interaction.dm index 61dde4f3304a..9ede6213df48 100644 --- a/code/game/objects/items-interaction.dm +++ b/code/game/objects/items-interaction.dm @@ -189,7 +189,9 @@ // SHOULD_NOT_OVERRIDE(TRUE) // may be re-evaluated later if(isnull(actor)) actor = new /datum/event_args/actor(user) - 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) @@ -236,7 +238,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 @@ -262,7 +266,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 @@ -288,7 +294,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/items-inventory-hooks.dm b/code/game/objects/items-inventory-hooks.dm index c8ff5b4d889e..ca5884f1df72 100644 --- a/code/game/objects/items-inventory-hooks.dm +++ b/code/game/objects/items-inventory-hooks.dm @@ -7,7 +7,7 @@ // inventory handling if(destination == worn_inside) return ..() - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() if(!ismob(M)) worn_slot = null worn_hook_suppressed = FALSE @@ -19,7 +19,7 @@ /obj/item/Move(atom/newloc, direct, glide_size_override) if(!worn_slot) return ..() - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() if(istype(M)) M.temporarily_remove_from_inventory(src, INV_OP_FORCE) else diff --git a/code/game/objects/items-inventory-old.dm b/code/game/objects/items-inventory-old.dm index 2e272b5e03bb..ac6fc33b5881 100644 --- a/code/game/objects/items-inventory-old.dm +++ b/code/game/objects/items-inventory-old.dm @@ -165,9 +165,9 @@ /** * get the mob we're equipped on */ -/obj/item/proc/worn_mob() as /mob +/obj/item/proc/get_worn_mob() as /mob RETURN_TYPE(/mob) - return worn_inside?.worn_mob() || (worn_slot? loc : null) + return worn_inside?.get_worn_mob() || (worn_slot? loc : null) //* Stripping *// @@ -196,14 +196,14 @@ var/slot = worn_slot if(!slot) CRASH("no worn slot") - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() if(!M) CRASH("no worn mob") if(!M.strip_interaction_prechecks(user)) return if(!do_after(user, delay, M, DO_AFTER_IGNORE_ACTIVE_ITEM)) return - if(slot != worn_slot || M != worn_mob()) + if(slot != worn_slot || M != get_worn_mob()) return return TRUE diff --git a/code/game/objects/items-inventory-rendering.dm b/code/game/objects/items-inventory-rendering.dm index d28e70515cc5..303ef0c1cfad 100644 --- a/code/game/objects/items-inventory-rendering.dm +++ b/code/game/objects/items-inventory-rendering.dm @@ -251,7 +251,7 @@ /obj/item/proc/update_worn_icon() if(!worn_slot) return // acceptable - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() ASSERT(M) // not acceptable if(held_index) M.update_inv_hand(held_index) @@ -468,7 +468,7 @@ return data -/obj/item/proc/debug_worn_assets(slot_or_id, mob/M = worn_mob(), bodytype) +/obj/item/proc/debug_worn_assets(slot_or_id, mob/M = get_worn_mob(), bodytype) var/mob/living/carbon/human/H = ishuman(M)? M : null var/datum/inventory_slot/slot_meta if(isnull(slot_or_id)) diff --git a/code/game/objects/items-inventory.dm b/code/game/objects/items-inventory.dm index 7a431de35f1c..7968fc05ae44 100644 --- a/code/game/objects/items-inventory.dm +++ b/code/game/objects/items-inventory.dm @@ -68,8 +68,8 @@ /** * checks if we're in inventory. if so, returns mob we're in */ -/obj/item/proc/is_in_inventory() - return worn_slot && worn_mob() +/obj/item/proc/is_in_inventory() as /mob + return inv_inside?.owner /** * checks if we're held in hand @@ -77,7 +77,7 @@ * if so, returns mob we're in */ /obj/item/proc/is_being_held() as /mob - return (worn_slot == SLOT_ID_HANDS)? worn_mob() : null + return isnum(inv_slot_or_index) ? inv_inside?.owner : null /** * checks if we're worn. if so, return mob we're in @@ -95,9 +95,9 @@ /obj/item/register_shieldcall(datum/shieldcall/delegate) . = ..() if(delegate.shields_in_inventory) - worn_mob()?.register_shieldcall(delegate) + get_worn_mob()?.register_shieldcall(delegate) /obj/item/unregister_shieldcall(datum/shieldcall/delegate) . = ..() if(delegate.shields_in_inventory) - worn_mob()?.unregister_shieldcall(delegate) + get_worn_mob()?.unregister_shieldcall(delegate) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 77c2eb3ea3a6..db95790d7884 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -91,6 +91,7 @@ /// The inventory datum we're in. /// /// * This also doubles as an 'is in inventory' check, as this will always be set if we are in inventory. + /// * This also doubles as 'get worn mob' by doing `inv_inside?.owner`. var/datum/inventory/inv_inside /// currently equipped slot id /// @@ -229,7 +230,7 @@ /obj/item/Destroy() // run inventory hooks if(worn_slot && !worn_hook_suppressed) - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() if(!ismob(M)) stack_trace("invalid current equipped slot [worn_slot] on an item not on a mob.") return ..() @@ -243,16 +244,6 @@ else return TRUE -/obj/item/proc/update_twohanding() - update_worn_icon() - -/obj/item/proc/is_held_twohanded(mob/living/M) - for(var/i in M.get_usable_hand_indices()) - if(!isnull(M.inventory?.held_items[i])) - continue - return TRUE - return FALSE - /obj/item/legacy_ex_act(severity) switch(severity) if(1.0) @@ -738,10 +729,10 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. * * null */ /obj/item/proc/set_actions_to(descriptor) - var/mob/worn_mob = worn_mob() + var/mob/get_worn_mob = get_worn_mob() - if(worn_mob) - unregister_item_actions(worn_mob) + if(get_worn_mob) + unregister_item_actions(get_worn_mob) if(ispath(descriptor, /datum/action)) descriptor = new descriptor(src) @@ -755,8 +746,8 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. else item_actions = descriptor - if(worn_mob) - register_item_actions(worn_mob) + if(get_worn_mob) + register_item_actions(get_worn_mob) /** * handles action granting @@ -978,8 +969,8 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. var/requires_update = (item_flags & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD)) != (var_value & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD)) . = ..() if(. && requires_update) - var/mob/living/L = worn_mob() - // check, as worn_mob() returns /mob, not /living + var/mob/living/L = get_worn_mob() + // check, as get_worn_mob() returns /mob, not /living if(istype(L)) L.recalculate_carry() L.update_carry() @@ -987,15 +978,15 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. // todo: introspection system update - this should be 'handled', as opposed to hooked. . = ..() if(. ) - var/mob/living/L = worn_mob() - // check, as worn_mob() returns /mob, not /living + var/mob/living/L = get_worn_mob() + // check, as get_worn_mob() returns /mob, not /living if(istype(L)) L.update_carry_slowdown() if(NAMEOF(src, slowdown)) . = ..() if(.) - var/mob/living/L = worn_mob() - // check, as worn_mob() returns /mob, not /living + var/mob/living/L = get_worn_mob() + // check, as get_worn_mob() returns /mob, not /living if(istype(L)) L.update_item_slowdown() if(NAMEOF(src, w_class)) diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index 489f27b412b2..e617f8b5f080 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -175,7 +175,7 @@ var/obj/item/stack/ore/O = locate() in get_turf(source) if(isnull(O)) return - var/mob/user = worn_mob() + var/mob/user = get_worn_mob() if(isnull(user)) return INVOKE_ASYNC(src, PROC_REF(autoload), user, O) diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm index 027a90f6b4d9..86f12d7be284 100644 --- a/code/game/objects/items/weapons/material/twohanded.dm +++ b/code/game/objects/items/weapons/material/twohanded.dm @@ -18,12 +18,6 @@ */ /obj/item/material/twohanded w_class = WEIGHT_CLASS_BULKY - var/unwielded_force_multiplier = 0.25 - var/wielded = 0 - var/wieldsound = null - var/unwieldsound = null - var/base_icon - var/base_name attack_sound = "swing_hit" drop_sound = 'sound/items/drop/sword.ogg' pickup_sound = 'sound/items/pickup/sword.ogg' @@ -31,38 +25,33 @@ parry_chance_melee = 15; } -/obj/item/material/twohanded/update_worn_icon() - var/mob/living/M = loc - if(istype(M) && M.can_wield_item(src) && is_held_twohanded(M)) - wielded = 1 - name = "[base_name] (wielded)" - else - wielded = 0 - name = "[base_name]" - update_icon() - update_material_parts() - ..() + var/base_icon + var/base_name + var/unwielded_force_multiplier = 0.25 + +/obj/item/material/twohanded/Initialize(mapload, material_key) + . = ..() + //* datum component - wielding *// + AddComponent(/datum/component/wielding) + +/obj/item/material/twohanded/on_wield(mob/user, hands) + . = ..() -/obj/item/material/twohanded/update_material_parts() +/obj/item/material/twohanded/on_unwield(mob/user, hands) . = ..() - if(!wielded) - damage_force *= unwielded_force_multiplier - // don't affect throwforce + +/obj/item/material/twohanded/standard_melee_attack(atom/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) + if(!(item_flags & ITEM_MULTIHAND_WIELDED)) + mult *= unwielded_force_multiplier /obj/item/material/twohanded/Initialize(mapload, material_key) . = ..() update_icon() /obj/item/material/twohanded/update_icon() - icon_state = "[base_icon][wielded]" + icon_state = "[base_icon][!!(item_flags & ITEM_MULTIHAND_WIELDED)]" item_state = icon_state -/obj/item/material/twohanded/dropped(mob/user, flags, atom/newLoc) - ..() - if(wielded) - spawn(0) - update_worn_icon() - /* * Fireaxe */ @@ -83,17 +72,13 @@ pickup_sound = 'sound/items/pickup/axe.ogg' heavy = TRUE -/obj/item/material/twohanded/fireaxe/update_worn_icon() - var/mob/living/M = loc - if(istype(M) && M.can_wield_item(src) && M.is_holding(src) && !M.are_usable_hands_full()) - wielded = 1 - pry = 1 - name = "[base_name] (wielded)" - else - wielded = 0 - pry = 0 - name = "[base_name]" - ..() +/obj/item/material/twohanded/fireaxe/on_wield(mob/user, hands) + . = ..() + pry = TRUE + +/obj/item/material/twohanded/fireaxe/on_unwield(mob/user, hands) + . = ..() + pry = FALSE /obj/item/material/twohanded/fireaxe/attack_object(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags, mult = 1) if(istype(target, /obj/structure/window)) @@ -186,7 +171,7 @@ /obj/item/material/twohanded/spear/afterattack(atom/target, mob/user, clickchain_flags, list/params) . = ..() - if(explosive && wielded) //Citadel edit removes qdel and explosive.forcemove(AM) + if(explosive && (item_flags & ITEM_MULTIHAND_WIELDED)) //Citadel edit removes qdel and explosive.forcemove(AM) user.say("[war_cry]") explosive.detonate() diff --git a/code/game/objects/obj-defense.dm b/code/game/objects/obj-defense.dm index 38d23a105f26..a46900390016 100644 --- a/code/game/objects/obj-defense.dm +++ b/code/game/objects/obj-defense.dm @@ -19,7 +19,7 @@ return CLICKCHAIN_FULL_BLOCKED // todo: maybe the item side should handle this? run_damage_instance( - weapon.damage_force * (clickchain ? clickchain.damage_multiplier : 1), + weapon.damage_force * (clickchain ? clickchain.melee_damage_multiplier : 1), weapon.damage_type, weapon.damage_tier, weapon.damage_flag, @@ -39,7 +39,7 @@ return CLICKCHAIN_FULL_BLOCKED // todo: maybe the unarmed_style side should handle this? run_damage_instance( - style.get_unarmed_damage(attacker, src) * (clickchain ? clickchain.damage_multiplier : 1), + style.get_unarmed_damage(attacker, src) * (clickchain ? clickchain.melee_damage_multiplier : 1), style.damage_type, style.damage_tier, style.damage_flag, diff --git a/code/game/objects/structures/crates_lockers/closets/fireaxe.dm b/code/game/objects/structures/crates_lockers/closets/fireaxe.dm index b58927665393..c344cd4d73e5 100644 --- a/code/game/objects/structures/crates_lockers/closets/fireaxe.dm +++ b/code/game/objects/structures/crates_lockers/closets/fireaxe.dm @@ -65,9 +65,6 @@ if(!user.attempt_insert_item_for_installation(O, src)) return fireaxe = O - if(fireaxe.wielded) - fireaxe.wielded = FALSE - fireaxe.update_icon() to_chat(user, "You place the fire axe back in the [src.name].") update_icon() else 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 43f8d99d1a0f..99274e7a3e38 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/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm index 6e80937fc473..55aceead0c71 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -48,7 +48,7 @@ /obj/structure/extinguisher_cabinet/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args) if(isrobot(user)) return - if(!user.standard_hand_usability_check(src, e_args.hand_index, HAND_MANIPULATION_GENERAL)) + if(!user.standard_hand_usability_check(src, e_args.using_hand_index, HAND_MANIPULATION_GENERAL)) return if(has_extinguisher) user.put_in_hands_or_drop(has_extinguisher) diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm index 994b2e183976..7bee849b6732 100644 --- a/code/game/objects/structures/fireaxe.dm +++ b/code/game/objects/structures/fireaxe.dm @@ -62,9 +62,6 @@ if(!user.attempt_insert_item_for_installation(O, src)) return fireaxe = O - if(fireaxe.wielded) - fireaxe.wielded = FALSE - fireaxe.update_icon() to_chat(user, "You place the fire axe back in the [name].") update_icon() else diff --git a/code/game/objects/structures/props/puzzledoor.dm b/code/game/objects/structures/props/puzzledoor.dm index a73e4cd16311..6a6ad2494758 100644 --- a/code/game/objects/structures/props/puzzledoor.dm +++ b/code/game/objects/structures/props/puzzledoor.dm @@ -61,15 +61,8 @@ /obj/machinery/door/blast/puzzle/attackby(obj/item/C as obj, mob/user as mob) if(istype(C, /obj/item)) if(C.pry == 1 && (user.a_intent != INTENT_HARM || (machine_stat & BROKEN))) - if(istype(C,/obj/item/material/twohanded/fireaxe)) - var/obj/item/material/twohanded/fireaxe/F = C - if(!F.wielded) - to_chat(user, "You need to be wielding \the [F] to do that.") - return - if(check_locks()) force_toggle(1, user) - else to_chat(user, "[src]'s arcane workings resist your effort.") return diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index e9574cada346..06821cd30798 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -387,7 +387,7 @@ thing.update_icon() /obj/structure/sink/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args) - if(!user.standard_hand_usability_check(src, e_args.hand_index, HAND_MANIPULATION_GENERAL)) + if(!user.standard_hand_usability_check(src, e_args.using_hand_index, HAND_MANIPULATION_GENERAL)) return if(isrobot(user) || isAI(user)) diff --git a/code/game/objects/systems/cell_slot.dm b/code/game/objects/systems/cell_slot.dm index e298cb519a2b..cf190060fb02 100644 --- a/code/game/objects/systems/cell_slot.dm +++ b/code/game/objects/systems/cell_slot.dm @@ -199,20 +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 - -/** - * cell function wrapper - use x cell units, affected by GLOB.cellefficiency, returns the amount actually used or 0 if null - */ -/datum/object_system/cell_slot/proc/use_scaled(var/amount) - return cell?.use_scaled(amount) || 0 - -/** - * cell function wrapper - checked_use() but scaled by GLOB.cellefficiency - */ -/datum/object_system/cell_slot/proc/checked_use_scaled(var/amount) - return cell?.checked_use_scaled(amount) ? TRUE : FALSE +/datum/object_system/cell_slot/proc/checked_use(amount, reserve) + return cell?.checked_use(amount, reserve) ? TRUE : FALSE /** * cell function wrapper - recharge the cell by x amount returns the amount consumed or 0 if cell is null diff --git a/code/game/objects/systems/storage/storage.dm b/code/game/objects/systems/storage/storage.dm index 32f9799907d3..cff931224a7a 100644 --- a/code/game/objects/systems/storage/storage.dm +++ b/code/game/objects/systems/storage/storage.dm @@ -492,7 +492,7 @@ if(!can_be_inserted(inserting, actor, silent)) return FALSE // point of no return - if(actor && (inserting.worn_mob() == actor.performer && !actor.performer.temporarily_remove_from_inventory(inserting, user = actor.performer))) + if(actor && (inserting.get_worn_mob() == actor.performer && !actor.performer.temporarily_remove_from_inventory(inserting, user = actor.performer))) if(!silent) actor?.chat_feedback( msg = SPAN_WARNING("[inserting] is stuck to your hand / body!"), diff --git a/code/game/rendering/plane_masters/plane_master.dm b/code/game/rendering/plane_masters/plane_master.dm index 8287f7eec942..5ef24d98335b 100644 --- a/code/game/rendering/plane_masters/plane_master.dm +++ b/code/game/rendering/plane_masters/plane_master.dm @@ -97,6 +97,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/game/turfs/simulated/wall/wall-defense.dm b/code/game/turfs/simulated/wall/wall-defense.dm index e1d7eec2bbd8..e590f7841c76 100644 --- a/code/game/turfs/simulated/wall/wall-defense.dm +++ b/code/game/turfs/simulated/wall/wall-defense.dm @@ -26,7 +26,7 @@ return CLICKCHAIN_FULL_BLOCKED // todo: maybe the unarmed_style side should handle this? run_damage_instance( - style.damage * (clickchain ? clickchain.damage_multiplier : 1), + style.damage * (clickchain ? clickchain.melee_damage_multiplier : 1), style.damage_type, style.damage_tier, style.damage_flag, @@ -46,7 +46,7 @@ return CLICKCHAIN_FULL_BLOCKED // todo: maybe the item side should handle this? run_damage_instance( - weapon.damage_force * (clickchain ? clickchain.damage_multiplier : 1), + weapon.damage_force * (clickchain ? clickchain.melee_damage_multiplier : 1), weapon.damage_type, weapon.damage_tier, weapon.damage_flag, diff --git a/code/modules/actions/types/attachment_action.dm b/code/modules/actions/types/attachment_action.dm index 7fe7dd62b864..a188e72648c8 100644 --- a/code/modules/actions/types/attachment_action.dm +++ b/code/modules/actions/types/attachment_action.dm @@ -21,5 +21,5 @@ /datum/action/attachment_action/calculate_availability() var/obj/item/item = target - var/mob/worn = item.worn_mob() + var/mob/worn = item.get_worn_mob() return worn? (worn.mobility_flags & check_mobility_flags? 1 : 0) : 1 diff --git a/code/modules/actions/types/item_action.dm b/code/modules/actions/types/item_action.dm index d600aa9d4219..953c21c8aa1a 100644 --- a/code/modules/actions/types/item_action.dm +++ b/code/modules/actions/types/item_action.dm @@ -21,5 +21,5 @@ /datum/action/item_action/calculate_availability() var/obj/item/item = target - var/mob/worn = item.worn_mob() + var/mob/worn = item.get_worn_mob() return worn? (worn.mobility_flags & check_mobility_flags? 1 : 0) : 1 diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index f390727d7486..ad220ad38266 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() - user.getCritHealth()) / DPS] seconds to softcrit you. ([user.getSoftCritHealth()] health)") - to_chat(user, "[(user.getMaxHealth() - user.getCritHealth()) / DPS] seconds to hardcrit you. ([user.getCritHealth()] health)") - to_chat(user, "[(user.getMaxHealth() - user.getMinHealth()) / DPS] seconds to kill you. ([user.getMinHealth()] 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/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm index 1e314a16cb8f..024388676d82 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -84,7 +84,7 @@ last_flow_rate_legacy = 0 last_power_draw_legacy = 0 else - cell.use_scaled(DYNAMIC_W_TO_CELL_UNITS(power_draw, 1)) + cell.use(DYNAMIC_W_TO_CELL_UNITS(power_draw, 1)) last_power_draw_legacy = power_draw update_connected_network() diff --git a/code/modules/client/client.dm b/code/modules/client/client.dm index 9bbccd20ea69..c5044b57294e 100644 --- a/code/modules/client/client.dm +++ b/code/modules/client/client.dm @@ -163,13 +163,9 @@ // todo: rename to `preferences` & put it next to `persistent` to sate my OCD ~silicons ///Player preferences datum for the client var/datum/preferences/prefs = null - ///Current area of the controlled mob - var/area = null ///when the client last died as a mouse var/time_died_as_mouse = null - var/adminhelped = 0 - /////////////// //SOUND STUFF// /////////////// @@ -179,16 +175,11 @@ //////////// //SECURITY// //////////// - // comment out the line below when debugging locally to enable the options & messages menu - //control_freak = 1 var/received_irc_pm = -99999 ///IRC admin that spoke with them last. var/irc_admin var/mute_irc = 0 - ///Do we think they're using a proxy/vpn? Only if IP Reputation checking is enabled in config. - var/ip_reputation = 0 - //////////////////////////////////// //things that require the database// @@ -213,9 +204,6 @@ ///world.timeofday they connected var/connection_timeofday - /// If this client has been fully initialized or not - var/fully_created = FALSE - /client/vv_edit_var(var_name, var_value) switch (var_name) if (NAMEOF(src, holder)) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 8d20b0cbcad9..3b1e32c9c53a 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -430,8 +430,6 @@ holder.owner = null GLOB.admins -= src //delete them on the managed one too - active_mousedown_item = null - //* Cleanup rendering *// if(using_perspective) set_perspective(null) diff --git a/code/modules/client/game_preferences/game_preferences.dm b/code/modules/client/game_preferences/game_preferences.dm index 667a98c8d4ed..3d14486f6a83 100644 --- a/code/modules/client/game_preferences/game_preferences.dm +++ b/code/modules/client/game_preferences/game_preferences.dm @@ -9,10 +9,13 @@ stack_trace("we just kicked a client due to prefs not loading; something is horribly wrong!") qdel(src) return ..() + /** * Game preferences * * Game prefs don't need an init order because unlike character setup, there's no dependencies, in theory. + * + * todo: rework this a bit, the way i did tgui is pretty atrocious; */ /datum/game_preferences //* Loading *// @@ -27,7 +30,7 @@ // todo: move menu options in here and not from /datum/preferences //* Middleware - Keybindings *// - /// keybindings - key to list of keybinds + /// keybindings - key to list of keybind ids var/list/keybindings //* Middleware - Toggles *// diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 7b2cb0c6f4d1..c0b941814ce1 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/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 8d2de2bcc9d7..b328907d46e8 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -100,7 +100,7 @@ if(accessory_host) return FALSE // either attack_hand_auto_unequip off, not being worn - var/equipped_by_performer = actor.performer == worn_mob() + var/equipped_by_performer = actor.performer == get_worn_mob() . = ..() && (attack_hand_auto_unequip || !equipped_by_performer) if(!.) return diff --git a/code/modules/clothing/clothing_accessories.dm b/code/modules/clothing/clothing_accessories.dm index 8ff46e36b00b..049fe84d3bbe 100644 --- a/code/modules/clothing/clothing_accessories.dm +++ b/code/modules/clothing/clothing_accessories.dm @@ -38,8 +38,8 @@ return TRUE return FALSE -/obj/item/clothing/worn_mob() - return isnull(accessory_host)? ..() : accessory_host.worn_mob() +/obj/item/clothing/get_worn_mob() + return isnull(accessory_host)? ..() : accessory_host.get_worn_mob() /obj/item/clothing/update_worn_icon() if(accessory_host) @@ -304,7 +304,7 @@ var/choice = input(user, "What to take off?", "Strip Accessory") as null|anything in choices if(!choice) return - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() if(!M) return var/obj/item/clothing/accessory/A = choices[choice] @@ -319,7 +319,7 @@ return if(!(A in accessories)) return - add_attack_logs(user, worn_mob(), "Detached [choice] from [src]") + add_attack_logs(user, get_worn_mob(), "Detached [choice] from [src]") if(istype(A, /obj/item/clothing/accessory/badge) || istype(A, /obj/item/clothing/accessory/medal)) M.visible_message( SPAN_WARNING("[user] tears \the [A] off of [M]'s [src]!"), diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index b44e788cc9d6..14e5faa4288f 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -53,7 +53,7 @@ BLIND // can't see anything if(.) return if(toggleable) - var/mob/wearer = worn_mob() + var/mob/wearer = get_worn_mob() if(active) active = 0 icon_state = inactive_icon_state diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index ec4c69defa74..fa4b3a0559b3 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -83,7 +83,7 @@ return 1 /obj/item/clothing/head/update_icon() - var/mob/living/carbon/human/H = worn_mob() + var/mob/living/carbon/human/H = get_worn_mob() if(on) // Generate object icon. diff --git a/code/modules/clothing/head/misc/cakehat.dm b/code/modules/clothing/head/misc/cakehat.dm index 297d45835658..ae64042ac66b 100644 --- a/code/modules/clothing/head/misc/cakehat.dm +++ b/code/modules/clothing/head/misc/cakehat.dm @@ -10,7 +10,7 @@ STOP_PROCESSING(SSobj, src) return - var/turf/maybe_turf_location = inv_slot_or_index ? get_turf(worn_mob()) : loc + var/turf/maybe_turf_location = inv_slot_or_index ? get_turf(inv_inside?.owner) : loc if(isturf(maybe_turf_location)) maybe_turf_location.hotspot_expose(700, 1) diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index f7ce6de2bdfd..2a2e5a038f0a 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -170,7 +170,7 @@ /obj/item/clothing/under/proc/update_rolldown(updating) var/has_roll var/detected_bodytype = BODYTYPE_DEFAULT - var/mob/living/carbon/human/H = worn_mob() + var/mob/living/carbon/human/H = get_worn_mob() if(istype(H)) detected_bodytype = H.species.get_effective_bodytype(H, src, worn_slot) switch(worn_has_rolldown) @@ -192,7 +192,7 @@ /obj/item/clothing/under/proc/update_rollsleeve(updating) var/has_sleeves var/detected_bodytype = BODYTYPE_DEFAULT - var/mob/living/carbon/human/H = worn_mob() + var/mob/living/carbon/human/H = get_worn_mob() if(istype(H)) detected_bodytype = H.species.get_effective_bodytype(H, src, worn_slot) switch(worn_has_rollsleeve) @@ -286,11 +286,11 @@ SPAN_WARNING("[user] is trying to set \the [src]'s sensors!"), SPAN_WARNING("[user] is trying to set your sensors!") ) - var/mob/M = worn_mob() + var/mob/M = get_worn_mob() if(do_after(user, HUMAN_STRIP_DELAY, M, DO_AFTER_IGNORE_ACTIVE_ITEM)) . = strip_menu_sensor_interact(user, M) -/obj/item/clothing/under/proc/strip_menu_sensor_interact(mob/user, mob/wearer = worn_mob()) +/obj/item/clothing/under/proc/strip_menu_sensor_interact(mob/user, mob/wearer = get_worn_mob()) add_attack_logs(user, wearer, "Adjusted suit sensor level") set_sensors(user) diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm index bab227d86223..ed8c76008bba 100644 --- a/code/modules/clothing/under/accessories/accessory.dm +++ b/code/modules/clothing/under/accessories/accessory.dm @@ -91,8 +91,8 @@ // todo: don't call dropped/pickup if going to same person if(S.worn_slot) - pickup(S.worn_mob(), INV_OP_IS_ACCESSORY) - equipped(S.worn_mob(), S.worn_slot, INV_OP_IS_ACCESSORY) + pickup(S.get_worn_mob(), INV_OP_IS_ACCESSORY) + equipped(S.get_worn_mob(), S.worn_slot, INV_OP_IS_ACCESSORY) // inventory handling end @@ -112,10 +112,10 @@ // todo: don't call dropped/pickup if going to same person if(accessory_host.worn_slot) - unequipped(accessory_host.worn_mob(), accessory_host.worn_slot, INV_OP_IS_ACCESSORY) - var/mob/host_worn_mob = accessory_host.worn_mob() - on_unequipped(accessory_host.worn_mob(), accessory_host.worn_slot == SLOT_ID_HANDS ? host_worn_mob.get_held_index(accessory_host) : accessory_host.worn_slot, INV_OP_IS_ACCESSORY) - dropped(accessory_host.worn_mob(), INV_OP_IS_ACCESSORY) + unequipped(accessory_host.get_worn_mob(), accessory_host.worn_slot, INV_OP_IS_ACCESSORY) + var/mob/host_worn_mob = accessory_host.get_worn_mob() + on_unequipped(accessory_host.get_worn_mob(), accessory_host.worn_slot == SLOT_ID_HANDS ? host_worn_mob.get_held_index(accessory_host) : accessory_host.worn_slot, INV_OP_IS_ACCESSORY) + dropped(accessory_host.get_worn_mob(), INV_OP_IS_ACCESSORY) // inventory handling stop 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/hardsuits/modules/combat.dm b/code/modules/hardsuits/modules/combat.dm index 6f0229a74635..9dfd2fe9cf02 100644 --- a/code/modules/hardsuits/modules/combat.dm +++ b/code/modules/hardsuits/modules/combat.dm @@ -145,10 +145,10 @@ return 0 if(!target) - gun.attack_self(holder.wearer) - return + gun.switch_firemodes(holder.wearer) + return 1 - gun.Fire(target,holder.wearer) + gun.start_firing_cycle_async(holder.wearer, get_centered_entity_angle(holder.wearer, target), NONE, null, target, new /datum/event_args/actor(holder.wearer)) return 1 /obj/item/hardsuit_module/mounted/egun diff --git a/code/modules/hardsuits/modules/utility.dm b/code/modules/hardsuits/modules/utility.dm index f5fb2a8f031e..5e51f8c1ffe5 100644 --- a/code/modules/hardsuits/modules/utility.dm +++ b/code/modules/hardsuits/modules/utility.dm @@ -15,7 +15,7 @@ * /obj/item/hardsuit_module/device/paperdispenser * /obj/item/hardsuit_module/device/pen * /obj/item/hardsuit_module/device/stamp - * /obj/item/hardsuit_module/mounted/mop + * /obj/item/hardsuit_module/mop * /obj/item/hardsuit_module/cleaner_launcher * /obj/item/hardsuit_module/device/hand_defib */ @@ -411,7 +411,7 @@ //Deployable Mop -/obj/item/hardsuit_module/mounted/mop +/obj/item/hardsuit_module/mop name = "mop projector" desc = "A powerful mop projector." @@ -430,24 +430,7 @@ active_power_cost = 0 passive_power_cost = 0 - gun = /obj/item/reagent_containers/spray/cleaner - -//obj/item/reagent_containers/spray/cleaner -// spary = - -/obj/item/hardsuit_module/mounted/engage(atom/target) - - if(!..()) - return 0 - - if(!target) - gun.attack_self(holder.wearer) - return 1 - - gun.Fire(target,holder.wearer) - return 1 - -/obj/item/hardsuit_module/mounted/mop/process(delta_time) +/obj/item/hardsuit_module/mop/process(delta_time) if(holder && holder.wearer) if(!(locate(/obj/item/mop_deploy) in holder.wearer)) @@ -456,7 +439,7 @@ return ..() -/obj/item/hardsuit_module/mounted/mop/activate() +/obj/item/hardsuit_module/mop/activate() ..() @@ -471,7 +454,7 @@ blade.creator = M M.put_in_hands(blade) -/obj/item/hardsuit_module/mounted/mop/deactivate() +/obj/item/hardsuit_module/mop/deactivate() ..() diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm index acee9d66a4f8..676bf9de0045 100644 --- a/code/modules/integrated_electronics/subtypes/manipulation.dm +++ b/code/modules/integrated_electronics/subtypes/manipulation.dm @@ -961,8 +961,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/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm index 517cbb2fcbdd..75f059b944ca 100644 --- a/code/modules/keybindings/bindings_client.dm +++ b/code/modules/keybindings/bindings_client.dm @@ -168,3 +168,24 @@ movement_keys[key] = WEST if("South") movement_keys[key] = SOUTH + +/** + * Returns a list of human-readable (usually) keys. + */ +/client/proc/get_keys_for_keybind(datum/keybinding/binding_or_path) as /list + if(!preferences?.initialized) + return list() + var/bind_id = ispath(binding_or_path) ? binding_or_path::name : binding_or_path.name + . = list() + for(var/key in preferences.keybindings) + if(bind_id in preferences.keybindings[key]) + . += key + +/** + * Returns a string that can be interpolated in tgui-chat to allow a quick click to rebind keys + * + * todo: for now, this just returns a string without the keybind UI open link. + */ +/client/proc/print_keys_for_keybind_with_prefs_link(datum/keybinding/binding_or_path, append) as text + var/list/keys = get_keys_for_keybind(/datum/keybinding/mob/multihand_wield) + return length(keys) ? "([english_list(keys)])[append]" : "(Unbound)[append]" diff --git a/code/modules/keybindings/keybind/_keybind.dm b/code/modules/keybindings/keybind/_keybind.dm index 7e0744471d8a..5549cc9971a3 100644 --- a/code/modules/keybindings/keybind/_keybind.dm +++ b/code/modules/keybindings/keybind/_keybind.dm @@ -1,6 +1,8 @@ +// todo: rename file to keybinding.dm /datum/keybinding var/list/hotkey_keys var/list/classic_keys + /// Our unique ID. var/name var/full_name var/description = "No description provided." diff --git a/code/modules/mining/tools/kinetic_accelerator.dm b/code/modules/mining/tools/kinetic_accelerator.dm index cf0f6cf184f9..cd1f9a848ae1 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 attachment_alignment = list( @@ -50,15 +50,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() @@ -150,7 +150,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) @@ -165,12 +165,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 @@ -188,7 +188,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) @@ -199,7 +199,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/inventory/inventory.dm b/code/modules/mob/inventory/inventory.dm index e6f2c80548d4..a722b0cb7d80 100644 --- a/code/modules/mob/inventory/inventory.dm +++ b/code/modules/mob/inventory/inventory.dm @@ -9,7 +9,7 @@ */ /datum/inventory //* Basics *// - /// owning mob + /// owning mob, if any var/mob/owner //* Actions *// 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/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d471f5eaf9a2..9b29fbd50c57 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1625,13 +1625,6 @@ adjust_nutrition(got) return (got * SYNTHETIC_NUTRITION_KJ_PER_UNIT) / GLOB.cellrate / SYNTHETIC_NUTRITION_INDUCER_CHEAT_FACTOR -/mob/living/carbon/human/can_wield_item(obj/item/W) - //Since teshari are small by default, they have different logic to allow them to use certain guns despite that. - //If any other species need to adapt for this, you can modify this proc with a list instead - if(istype(species, /datum/species/teshari)) - return !W.heavy //return true if it is not heavy, false if it is heavy - else return ..() - /mob/living/carbon/human/set_nutrition(amount) nutrition = clamp(amount, 0, species.max_nutrition * 1.5) diff --git a/code/modules/mob/living/living-defense.dm b/code/modules/mob/living/living-defense.dm index bce917a898b5..4b99e58430c1 100644 --- a/code/modules/mob/living/living-defense.dm +++ b/code/modules/mob/living/living-defense.dm @@ -32,7 +32,7 @@ add_attack_logs( proj.firer, src, - "shot with [src] ([type]) (missed)", + "shot with [proj] ([type]) (missed)", ) impact_flags |= PROJECTILE_IMPACT_PASSTHROUGH return ..() @@ -47,13 +47,13 @@ add_attack_logs( proj.firer, src, - "shot with [src] ([type]) (aborted)", + "shot with [proj] ([type]) (aborted)", ) return add_attack_logs( proj.firer, src, - "shot with [src] ([type])[(impact_flags & PROJECTILE_IMPACT_BLOCKED)? " (blocked)" : ""]", + "shot with [proj] ([type])[(impact_flags & PROJECTILE_IMPACT_BLOCKED)? " (blocked)" : ""]", ) // emit feedback if(!(impact_flags & PROJECTILE_IMPACT_BLOCKED)) 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/mob/mob-inventory-abstraction.dm b/code/modules/mob/mob-inventory-abstraction.dm index 22cfe5f3bc85..583105ee2fb4 100644 --- a/code/modules/mob/mob-inventory-abstraction.dm +++ b/code/modules/mob/mob-inventory-abstraction.dm @@ -191,10 +191,6 @@ inventory.held_items[index] = I inventory.on_item_entered(I, index) - //! LEGACY BEGIN - I.update_twohanding() - //! END - if(!(flags & INV_OP_NO_UPDATE_ICONS)) update_inv_hand(index) diff --git a/code/modules/mob/mob-inventory.dm b/code/modules/mob/mob-inventory.dm index 2207340baa24..8f010e16a44f 100644 --- a/code/modules/mob/mob-inventory.dm +++ b/code/modules/mob/mob-inventory.dm @@ -50,7 +50,7 @@ * SLOT_ID_HANDS if in hands */ /mob/proc/is_in_inventory(obj/item/I) - return (I?.worn_mob() == src) ? I.worn_slot : null + return (I?.inv_inside?.owner == src) ? I.worn_slot : null // we use entirely cached vars for speed. // if this returns bad data well fuck you, don't break equipped()/unequipped(). diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 889e85b4b709..844a9108b6e2 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -15,11 +15,6 @@ /proc/mob_size_difference(var/mob_size_A, var/mob_size_B) return round(log(2, mob_size_A/mob_size_B), 1) -/mob/proc/can_wield_item(obj/item/W) - if(W.w_class >= WEIGHT_CLASS_BULKY && issmall(src)) - return FALSE //M is too small to wield this - return TRUE - /proc/istiny(A) if(A && istype(A, /mob/living)) var/mob/living/L = A diff --git a/code/modules/movespeed/modifiers/mob.dm b/code/modules/movespeed/modifiers/mob.dm index ea695cbc65b2..220e91a71be0 100644 --- a/code/modules/movespeed/modifiers/mob.dm +++ b/code/modules/movespeed/modifiers/mob.dm @@ -1,5 +1,5 @@ /datum/movespeed_modifier/mob_crawling - multiplicative_slowdown = 3.5 + hyperbolic_slowdown = 3.5 /datum/movespeed_modifier/mob_staggered variable = TRUE diff --git a/code/modules/movespeed/movespeed_modifier.dm b/code/modules/movespeed/movespeed_modifier.dm index 97be21d22456..25cf16061cbc 100644 --- a/code/modules/movespeed/movespeed_modifier.dm +++ b/code/modules/movespeed/movespeed_modifier.dm @@ -26,46 +26,46 @@ Key procs */ /datum/movespeed_modifier - /// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason) - var/variable = FALSE - /// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID. var/id + /// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason) + var/variable = FALSE /// Determines order. Lower priorities are applied first. var/priority = MOVESPEED_PRIORITY_DEFAULT /// flags var/movespeed_modifier_flags = NONE + //* Filtering - Movetypes *// + /// Movetypes this applies to + var/required_movetypes = ALL + /// Movetypes this never applies to + var/blacklisted_movetypes = NONE + + //* Caclulations *// /// calculation type var/calculation_type = MOVESPEED_CALCULATION_HYPERBOLIC - //* HYPERBOLIC, HYPERBOLIC_BOOST calculations - /// Multiplicative slowdown - var/multiplicative_slowdown = 0 + //* Calculations - HYPERBOLIC, HYPERBOLIC_BOOST *// + /// For: HYPERBOLIC, HYPERBOLIC_BOOST + /// * This is just a raw modifier to current movement delay + /// * This has a hyperbolic effect; reducing movement delay at already low values speeds someone up a lot more + /// than at high values. + var/hyperbolic_slowdown = 0 - //* MULTIPLY calculations + //* Calculations - MULTIPLY *// /// multiply resulting speed by var/multiply_speed = 1 - //* HYPERBOLIC_BOOST, MULTIPLY calculations + //* Calculations - HYPERBOLIC_BOOST, MULTIPLY *// /// Absolute max tiles we can boost to var/absolute_max_tiles_per_second = INFINITY /// Max tiles per second we can boost var/max_tiles_per_second_boost = INFINITY - /// Movetypes this applies to - var/movement_type = ALL - - /// Movetypes this never applies to - var/blacklisted_movetypes = NONE - - /// Other modification datums this conflicts with. Enum string. - /// If there is, it prioritizes the highest slow *or* the highest speedup, with abs(). - var/conflicts_with /datum/movespeed_modifier/New() - . = ..() + ..() if(!id) id = "[type]" //We turn the path into a string. @@ -81,10 +81,10 @@ Key procs switch(calculation_type) /* if(MOVESPEED_CALCULATION_HYPERBOLIC) - return max(world.tick_lag, existing + multiplicative_slowdown) + return max(world.tick_lag, existing + hyperbolic_slowdown) if(MOVESPEED_CALCULATION_HYPERBOLIC_BOOST) var/current_tiles = 10 / max(existing, world.tick_lag) - var/max_buff_to = max(existing + multiplicative_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost)) + var/max_buff_to = max(existing + hyperbolic_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost)) return clamp(max_buff_to, world.tick_lag, existing) if(MOVESPEED_CALCULATION_MULTIPLY) var/current_tiles = 10 / max(world.tick_lag, existing) @@ -92,12 +92,12 @@ Key procs */ if(MOVESPEED_CALCULATION_HYPERBOLIC) // going below 0 would fuck multipliers up pretty badly - // return max(0, existing + multiplicative_slowdown) + // return max(0, existing + hyperbolic_slowdown) //! WE DO IT ANYWAYS - LEGACY - return existing + multiplicative_slowdown + return existing + hyperbolic_slowdown if(MOVESPEED_CALCULATION_HYPERBOLIC_BOOST) var/current_tiles = 10 / max(existing, world.tick_lag) - var/max_buff_to = max(existing + multiplicative_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost)) + var/max_buff_to = max(existing + hyperbolic_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost)) return min(existing, max_buff_to) if(MOVESPEED_CALCULATION_MULTIPLY) if(existing > 0) @@ -117,9 +117,9 @@ Key procs */ /datum/movespeed_modifier/proc/parse(list/params) . = FALSE - if(!isnull(params[MOVESPEED_PARAM_DELAY_MOD])) + if(!isnull(params[MOVESPEED_PARAM_HYPERBOLIC_SLOWDOWN])) . = TRUE - multiplicative_slowdown = params[MOVESPEED_PARAM_DELAY_MOD] + hyperbolic_slowdown = params[MOVESPEED_PARAM_HYPERBOLIC_SLOWDOWN] if(!isnull(params[MOVESPEED_PARAM_MULTIPLY_SPEED])) . = TRUE multiply_speed = params[MOVESPEED_PARAM_MULTIPLY_SPEED] @@ -221,14 +221,14 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) /// Handles the special case of editing the movement var /mob/vv_edit_var(var_name, var_value) - var/slowdown_edit = (var_name == NAMEOF(src, cached_multiplicative_slowdown)) + var/slowdown_edit = (var_name == NAMEOF(src, cached_hyperbolic_slowdown)) var/diff - if(slowdown_edit && isnum(cached_multiplicative_slowdown) && isnum(var_value)) + if(slowdown_edit && isnum(cached_hyperbolic_slowdown) && isnum(var_value)) remove_movespeed_modifier(/datum/movespeed_modifier/admin_varedit) - diff = var_value - cached_multiplicative_slowdown + diff = var_value - cached_hyperbolic_slowdown . = ..() if(. && slowdown_edit && isnum(diff)) - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/admin_varedit, params = list(MOVESPEED_PARAM_DELAY_MOD = diff)) + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/admin_varedit, params = list(MOVESPEED_PARAM_HYPERBOLIC_SLOWDOWN = diff)) ///Is there a movespeed modifier for this mob /mob/proc/has_movespeed_modifier(datum/movespeed_modifier/datum_type_id) @@ -245,8 +245,8 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) /mob/proc/update_config_movespeed() // todo: this /* - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod, multiplicative_slowdown = get_config_multiplicative_speed()) - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod_floating, multiplicative_slowdown = get_config_multiplicative_speed(TRUE)) + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod, hyperbolic_slowdown = get_config_multiplicative_speed()) + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod_floating, hyperbolic_slowdown = get_config_multiplicative_speed(TRUE)) */ /// Get the global config movespeed of a mob by type @@ -264,12 +264,11 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) /// Go through the list of movespeed modifiers and calculate a final movespeed. ANY ADD/REMOVE DONE IN UPDATE_MOVESPEED MUST HAVE THE UPDATE ARGUMENT SET AS FALSE! /mob/proc/update_movespeed() . = 0 - var/list/conflict_tracker = list() //! TODO: LEGACY cached_movespeed_multiply = 1 //! END for(var/datum/movespeed_modifier/M in get_movespeed_modifiers()) - if(!(M.movement_type & movement_type)) // We don't affect any of these move types, skip + if(!(M.required_movetypes & movement_type)) // We don't affect any of these move types, skip continue if(M.blacklisted_movetypes & movement_type) // There's a movetype here that disables this modifier, skip continue @@ -277,20 +276,12 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) if((M.movespeed_modifier_flags & MOVESPEED_MODIFIER_REQUIRES_GRAVITY) && !in_gravity) continue //! END - var/conflict = M.conflicts_with - var/amt = M.multiplicative_slowdown - if(conflict) - // Conflicting modifiers prioritize the larger slowdown or the larger speedup - // We purposefuly don't handle mixing speedups and slowdowns on the same id - if(abs(conflict_tracker[conflict]) < abs(amt)) - conflict_tracker[conflict] = amt - else - continue + var/amt = M.hyperbolic_slowdown . = M.apply_multiplicative(., src) - cached_multiplicative_slowdown = min(., 10 / MOVESPEED_ABSOLUTE_MINIMUM_TILES_PER_SECOND) + cached_hyperbolic_slowdown = min(., 10 / MOVESPEED_ABSOLUTE_MINIMUM_TILES_PER_SECOND) if(!client) return - var/diff = (last_self_move - move_delay) - cached_multiplicative_slowdown + var/diff = (last_self_move - move_delay) - cached_hyperbolic_slowdown if(diff > 0) // your delay decreases, "give" the delay back to the client if(move_delay > world.time + 1.5) @@ -325,11 +316,11 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) . -= id /// Calculate the total slowdown of all movespeed modifiers -/mob/proc/total_multiplicative_slowdown() +/mob/proc/total_hyperbolic_slowdown() . = 0 for(var/id in get_movespeed_modifiers()) var/datum/movespeed_modifier/M = movespeed_modification[id] - . += M.multiplicative_slowdown + . += M.hyperbolic_slowdown /** * Gets the movespeed modifier datum of a modifier on a mob. Returns null if not found. @@ -341,5 +332,5 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) /// Checks if a move speed modifier is valid and not missing any data /proc/movespeed_data_null_check(datum/movespeed_modifier/M) //Determines if a data list is not meaningful and should be discarded. . = TRUE - if(M.multiplicative_slowdown) + if(M.hyperbolic_slowdown) . = FALSE diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index 9595885112d2..442b7667ee1b 100644 --- a/code/modules/paperwork/paperbin.dm +++ b/code/modules/paperwork/paperbin.dm @@ -30,7 +30,7 @@ to_chat(user, "You pick up the [src].") /obj/item/paper_bin/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args) - if(!user.standard_hand_usability_check(src, e_args.hand_index, HAND_MANIPULATION_GENERAL)) + if(!user.standard_hand_usability_check(src, e_args.using_hand_index, HAND_MANIPULATION_GENERAL)) return var/response = "" diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 9757c4173c31..2a84bae56ef0 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -128,26 +128,12 @@ // 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 -/** - * use x cell units, affected by GLOB.cellefficiency - */ -/obj/item/cell/proc/use_scaled(amount) - return use(amount / GLOB.cellefficiency) * GLOB.cellefficiency - -/** - * uses x cell units but only if we have enough, affected by GLOB.cellefficiency - * - * returns TRUE/FALSE - */ -/obj/item/cell/proc/checked_use_scaled(amount) - return checked_use(amount / GLOB.cellefficiency) - // recharge the cell /obj/item/cell/proc/give(var/amount) if(rigged && amount > 0) @@ -274,3 +260,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_caliber.dm b/code/modules/projectiles/ammunition/ammo_caliber.dm index a16391347c3c..e3b335972aa2 100644 --- a/code/modules/projectiles/ammunition/ammo_caliber.dm +++ b/code/modules/projectiles/ammunition/ammo_caliber.dm @@ -28,6 +28,8 @@ GLOBAL_LIST_INIT(calibers, init_calibers()) /** * desc wip * + * todo: make this a /prototype + * * welcome to hell: brought to you by a webdev * * naming convention: c[whatever] for the caliber, subtype to a[whatever] for different ammo lengths @@ -39,7 +41,7 @@ GLOBAL_LIST_INIT(calibers, init_calibers()) abstract_type = /datum/ammo_caliber /// caliber string var/caliber - /// does dynamic measurements + /// both diameter / length are set, which means we can do dynamic measurements var/measured /// width in millimeters var/diameter diff --git a/code/modules/projectiles/ammunition/ammo_casing.dm b/code/modules/projectiles/ammunition/ammo_casing.dm index 2504a9d05031..de6d09d15c36 100644 --- a/code/modules/projectiles/ammunition/ammo_casing.dm +++ b/code/modules/projectiles/ammunition/ammo_casing.dm @@ -10,19 +10,28 @@ drop_sound = 'sound/items/drop/ring.ogg' pickup_sound = 'sound/items/pickup/ring.ogg' - //* Casing + //* Casing *// /// casing flags - see __DEFINES/projectiles/ammo_casing.dm var/casing_flags = NONE /// what types of primer we react to var/casing_primer = CASING_PRIMER_CHEMICAL - /// projectile type - var/projectile_type /// caliber - set to typepath of datum for compile checking /// + /// todo: rename to casing_caliber? + /// /// * may be typepath of caliber (recommended) /// * may be instance of caliber (not recommended, but allowable for special cases) /// * may NOT be string of caliber, currently var/caliber + /// Effective mass multiplier. + /// + /// * This is used to calculate energy draw for magnetic weapons. + /// * Set this as a multiple of a parent type's multiplier. + var/effective_mass_multiplier = 1 + + //* Projectile *// + /// projectile type + var/projectile_type /// stored projectile - either null for un-init'd, FALSE for empty, or an instance VAR_PROTECTED/obj/projectile/stored 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/ammo_magazine.dm b/code/modules/projectiles/ammunition/ammo_magazine.dm index eb75cc0b18b5..4fd703b03a8f 100644 --- a/code/modules/projectiles/ammunition/ammo_magazine.dm +++ b/code/modules/projectiles/ammunition/ammo_magazine.dm @@ -31,6 +31,7 @@ /// the class we ask the gun to render us as /// /// * uses MAGAZINE_CLASS_* flags + /// * while quirky, guns do reserve the right to filter magazines by class. /// * if our requested class isn't on a gun, the gun reserves the right to render us as the default class ('-mag') var/magazine_class = MAGAZINE_CLASS_GENERIC @@ -39,9 +40,6 @@ /// setting this to a gun's typepath is allowed, this is an arbitrary field. // todo: impl var/magazine_restrict - /// considered an extended magazine for guns that support rendering extended magazines? - // todo: impl - var/magazine_extended = FALSE // todo: magazine_insert_delay // todo: magazine_remove_delay @@ -91,10 +89,12 @@ /// * ammo by default has their typepath as the restrict value /// * ammo can set strings / enums to this too; make sure to #define them. var/ammo_restrict - /// if set, doesn't allow subtypes + /// if set and ammo_restrict uses typepaths, doesn't allow subtypes var/ammo_restrict_no_subtypes = FALSE /// init all contents on initialize instead of lazy-drawing /// + /// todo: kill this with fire + /// /// * used for things like microbatteries / legacy content var/ammo_legacy_init_everything = FALSE 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 18bce0997603..77239029a550 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() . = ..() @@ -533,7 +549,7 @@ desc = " A Bolt Action Rifle taken apart and retooled into a primitive machine gun. Bulky and obtuse, it still capable of unleashing devastating firepower with its 15 round internal drum magazine. Loads with 7.62 stripper clips." icon_state = "automat" item_state = "automat" - fire_anim = "automat_fire" + // fire_anim = "automat_fire" w_class = WEIGHT_CLASS_BULKY damage_force = 10 caliber = /datum/ammo_caliber/a7_62mm @@ -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" @@ -559,7 +576,7 @@ icon_state = "automat-taj" item_state = "automat-taj" wielded_item_state = "automat-taj-wielded" - fire_anim = "" + // fire_anim = "" /obj/item/gun/ballistic/automatic/holyshot name = "Holy automatic shotgun" 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..e4a114bcddc4 --- /dev/null +++ b/code/modules/projectiles/guns/ballistic/magnetic.dm @@ -0,0 +1,33 @@ +//* 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 * (1 / 24) + + + /// Render battery state. + /// + /// * Uses MAGNETIC_RENDER_BATTERY_* enums + #warn impl + var/render_battery_overlay = MAGNETIC_RENDER_BATTERY_NEVER + +#warn impl all + +/obj/item/gun/ballistic/magnetic/consume_next_projectile(datum/gun_firing_cycle/cycle) + var/obj/item/ammo_casing/chambered_peek = chambered + var/shot_power_draw = base_shot_power * chambered_peek.effective_mass_multiplier + if(!obj_cell_slot.check_charge(shot_power_draw)) + return GUN_FIRED_FAIL_EMPTY + + #warn impl + . = ..() + // not a failure result + if(!isnum(.)) + obj_cell_slot.use(shot_power_draw) 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 e5e909f1d2b9..30d8260d3adc 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 97% rename from code/modules/projectiles/guns/projectile/revolver.dm rename to code/modules/projectiles/guns/ballistic/revolver.dm index 73c2d28eb987..d5b0f94b6c21 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,8 +304,10 @@ 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 - fire_anim = "mosley_fire" + firemodes = /datum/firemode{ + cycle_cooldown = 0.5 SECONDS; + } + // fire_anim = "mosley_fire" origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 2) /obj/item/gun/ballistic/revolver/dirty_harry @@ -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..99ebb740b6af --- /dev/null +++ b/code/modules/projectiles/guns/energy-firemode.dm @@ -0,0 +1,31 @@ +//* 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 + + //* Projectile Formation *// + /// projectile type + var/projectile_type + +// 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 1fbe3f438125..8b8460bd645e 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,12 @@ 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 + + // todo: do not use this var, use firemodes 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 +32,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 +47,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 +85,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,62 +102,16 @@ ..() update_icon() -/obj/item/gun/energy/consume_next_projectile() - if(!power_supply) - return null - if(!ispath(projectile_type)) - return null - if(!power_supply.checked_use(charge_cost)) +/obj/item/gun/energy/consume_next_projectile(datum/gun_firing_cycle/cycle) + var/datum/firemode/energy/energy_firemode = legacy_get_firemode() + if(!istype(energy_firemode)) 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_worn_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_worn_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 ..() + var/effective_power_use = isnull(energy_firemode.charge_cost) ? charge_cost : energy_firemode.charge_cost + if(effective_power_use) + if(!obj_cell_slot?.checked_use(effective_power_use)) + return null + var/projectile_path = isnull(energy_firemode.projectile_type) ? projectile_type : energy_firemode.projectile_type + return new projectile_path /obj/item/gun/energy/proc/get_external_power_supply() if(isrobot(src.loc)) @@ -177,9 +129,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 +143,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 +163,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 +172,18 @@ if(!ignore_inhands) update_worn_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 3796836a2209..85c211b78442 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 @@ -112,7 +112,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.dm b/code/modules/projectiles/guns/energy/modular.dm new file mode 100644 index 000000000000..5ce54ea55e2d --- /dev/null +++ b/code/modules/projectiles/guns/energy/modular.dm @@ -0,0 +1,26 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/obj/item/gun/energy/modular + /// active particle array + var/obj/item/gun_component/particle_array/active_particle_array + +/obj/item/gun/energy/modular/on_modular_component_uninstall(obj/item/gun_component/component, datum/event_args/actor/actor, silent) + ..() + if(!istype(component, /obj/item/gun_component/particle_array)) + return + if(component == active_particle_array) + set_particle_array(null) + +/obj/item/gun/energy/modular/on_modular_component_install(obj/item/gun_component/component, datum/event_args/actor/actor, silent) + ..() + if(!istype(component, /obj/item/gun_component/particle_array)) + return + if(!active_particle_array) + set_particle_array(component) + +/obj/item/gun/energy/modular/proc/set_particle_array(obj/item/gun_component/particle_array/array) + + update_icon() + +#warn impl; action, etc diff --git a/code/modules/projectiles/guns/energy/modular/gunframes.dm b/code/modules/projectiles/guns/energy/modular/gunframes.dm deleted file mode 100644 index 8fb9c34a61f8..000000000000 --- a/code/modules/projectiles/guns/energy/modular/gunframes.dm +++ /dev/null @@ -1,88 +0,0 @@ -/obj/item/gun/energy/modular/basic - name = "modular energy pistol" - desc = "A basic, compact, modular energy weapon. The fire controller and power control unit are integral to the frame and are thus unremovable." - -/obj/item/gun/energy/modular/basic/Initialize(mapload) - . = ..() - lasercap = new /obj/item/modularlaser/capacitor/simple/integral(src) - circuit = new /obj/item/modularlaser/controller/basic/integral(src) - -/obj/item/gun/energy/modular/advanced - name = "advanced modular energy pistol" - desc = "A basic, compact, modular energy weapon. All parts are modular in this version." - -/obj/item/gun/energy/modular/carbine - name = "modular energy carbine" - desc = "A basic modular energy weapon. This carbine has the capability to mount two cores but relies on an aircooling system." - cores = 2 - icon_state = "mod_carbine" - -/obj/item/gun/energy/modular/carbine/Initialize(mapload) - . = ..() - lasercooler = new /obj/item/modularlaser/cooling/lame/integral(src) - -/obj/item/gun/energy/modular/rifle - name = "modular energy rifle" - desc = "A basic modular energy weapon. This rifle has the capability to mount two cores." - cores = 2 - icon_state = "mod_rifle" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/gun/energy/modular/rifle/tribeam - name = "tribeam modular energy rifle" - desc = "An advanced modular energy weapon. This rifle has the capability to mount three cores." - cores = 3 - -/obj/item/gun/energy/modular/compact - name = "compact modular energy pistol" - desc = "A compact energy pistol that can fit into a pocket. However, only the laser core can be replaced. All the other components are purpose-built for their size and are integrated into the frame." - icon_state = "taserblue" - w_class = WEIGHT_CLASS_SMALL - -/obj/item/gun/energy/modular/compact/Initialize(mapload) - . = ..() - lasercap = new /obj/item/modularlaser/capacitor/simple/integral(src) - circuit = new /obj/item/modularlaser/controller/basic/integral(src) - lasercooler = new /obj/item/modularlaser/cooling/lame/integral(src) - laserlens = new /obj/item/modularlaser/lens/lame/integral(src) - -/obj/item/gun/energy/modular/rifle/scatter - name = "modular energy scattergun" - desc = "A sophisticated modular energy weapon. This scattergun has the capability to mount two cores, and mounts a complex refracting lens to scatter most shots." - -/obj/item/gun/energy/modular/rifle/scatter/Initialize(mapload) - . = ..() - laserlens = new /obj/item/modularlaser/lens/scatter/hyper/integral(src) - -/obj/item/gun/energy/modular/cannon - 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 - cell_type = /obj/item/cell/device/weapon/modcannon - icon_state = "mod_cannon" - w_class = WEIGHT_CLASS_HUGE - heavy = TRUE - -/obj/item/gun/energy/modular/cannon/Initialize(mapload) - . = ..() - lasercap = new /obj/item/modularlaser/capacitor/cannon(src) - circuit = new /obj/item/modularlaser/controller/basic/integral(src) - -/obj/item/cell/device/weapon/modcannon - charge = 4800 - maxcharge = 4800 - -/obj/item/gun/energy/modular/nuke - 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 - cell_type = /obj/item/cell/device/weapon/recharge/captain - icon_state = "modnuc" - w_class = WEIGHT_CLASS_HUGE - heavy = TRUE - -/obj/item/gun/energy/modular/nuke/Initialize(mapload) - . = ..() - circuit = new /obj/item/modularlaser/controller/basic/integral(src) diff --git a/code/modules/projectiles/guns/energy/modular/lasermediums.dm b/code/modules/projectiles/guns/energy/modular/lasermediums.dm deleted file mode 100644 index 7d5dcaa162d0..000000000000 --- a/code/modules/projectiles/guns/energy/modular/lasermediums.dm +++ /dev/null @@ -1,130 +0,0 @@ -///////////////////////////////////////////// -//Laser cores -//////////////////////////////////////////// - -/obj/item/modularlaser/lasermedium - name = "modular laser part" - desc = "I shouldn't exist." - var/obj/projectile/beamtype = /obj/projectile/beam - var/obj/projectile/scatterbeam = /obj/projectile/scatter/laser - var/beamcost = 120 - var/firename = "pew" - -/obj/item/modularlaser/lasermedium/stun - name = "stun beam medium" - desc = "Allows a modular energy gun to fire basic stun beams." - beamtype = /obj/projectile/beam/stun - scatterbeam = /obj/projectile/scatter/stun - beamcost = 240 - firename = "stun" - -/obj/item/modularlaser/lasermedium/stun/weak - name = "low-power stun beam medium" - desc = "Allows a modular energy gun to fire weak stun beams." - beamtype = /obj/projectile/beam/stun/weak - scatterbeam = /obj/projectile/scatter/stun/weak - beamcost = 120 - firename = "weak stun" - -/obj/item/modularlaser/lasermedium/net - name = "entangling beam medium" - desc = "Allows a modular energy gun to fire entangling net beams." - beamtype = /obj/projectile/beam/energy_net - scatterbeam = /obj/projectile/scatter/energy_net - beamcost = 1200 //hefty cost. - firename = "energy net" - -/obj/item/modularlaser/lasermedium/electrode - name = "electrode projector tube" - desc = "Allows a modular energy gun to fire basic stunning electrodes." - beamtype = /obj/projectile/energy/electrode/strong - scatterbeam = /obj/projectile/scatter/stun/electrode - beamcost = 240 - firename = "electrode stun" - -/obj/item/modularlaser/lasermedium/laser - name = "laser beam medium" - desc = "Allows a modular energy gun to fire basic laser beams." - beamtype = /obj/projectile/beam - scatterbeam = /obj/projectile/scatter/laser - beamcost = 240 - firename = "lethal" - -/obj/item/modularlaser/lasermedium/laser/weak - name = "low-power laser beam medium" - desc = "Allows a modular energy gun to fire supressive laser beams." - beamtype = /obj/projectile/beam/weaklaser - scatterbeam = /obj/projectile/scatter/laser/weak - beamcost = 60 - firename = "weak laser" - -/obj/item/modularlaser/lasermedium/laser/sniper - name = "focused laser beam medium" - desc = "Allows a modular energy gun to fire extremely focused laser beams." - beamtype = /obj/projectile/beam/sniper - scatterbeam = /obj/projectile/beam/sniper //no shotgun snipers. you can have shotgun cannons though! - beamcost = 300 - firename = "focused laser" - -/obj/item/modularlaser/lasermedium/laser/heavy - name = "robust beam medium" - desc = "Allows a modular energy gun to fire heavy laser beams." - beamtype = /obj/projectile/beam/heavylaser - scatterbeam = /obj/projectile/scatter/laser/heavylaser - beamcost = 600 - firename = "heavy laser" - -/obj/item/modularlaser/lasermedium/laser/cannon - name = "uranium-235 excited medium" - desc = "Allows a modular energy gun to fire heavy laser cannon beams." - beamtype = /obj/projectile/beam/heavylaser/cannon - scatterbeam = /obj/projectile/scatter/laser/heavylaser/cannon - beamcost = 800 - firename = "cannon beam" - -/obj/item/modularlaser/lasermedium/laser/xray - name = "xraser beam medium" - desc = "Allows a modular energy gun to fire exotic x-ray beams." - beamtype = /obj/projectile/beam/gamma - scatterbeam = /obj/projectile/scatter/gamma - firename = "xraser" - -/obj/item/modularlaser/lasermedium/laser/pulse //Badmin only. - name = "pulse beam medium" - desc = "Allows a modular energy gun to fire pulse beams." - beamtype = /obj/projectile/beam/pulse - scatterbeam = /obj/projectile/scatter/laser/pulse //haha fuck - beamcost = 240 - firename = "DESTROY" - -/obj/item/modularlaser/lasermedium/dig - name = "excavation beam medium" - desc = "Allows a modular energy gun to fire excavation laser beams. Yours is the beam that will pierce the heavens!" - beamtype = /obj/projectile/beam/excavation - scatterbeam = /obj/projectile/scatter/excavation - beamcost = 12 - firename = "excavate" - -/obj/item/modularlaser/lasermedium/lightning - name = "electric beam medium" - desc = "Allows a modular energy gun to fire lightning!" - beamtype = /obj/projectile/beam/shock - scatterbeam = /obj/projectile/scatter/shock - beamcost = 300 - firename = "tesla" - -/obj/item/modularlaser/lasermedium/hook - name = "energy grappler projection tube" - desc = "Allows a modular energy gun to fire an energy grappler. " - beamtype = /obj/projectile/energy/hook - scatterbeam = /obj/projectile/energy/hook - beamcost = 400 - firename = "graviton grapple" - -/obj/item/modularlaser/lasermedium/phase - name = "phase projection tube" - desc = "Allows a modular energy gun to fire phase waves for killing wildlife. " - beamtype = /obj/projectile/energy/phase/heavy - scatterbeam = /obj/projectile/scatter/phase - beamcost = 80 - firename = "phase" diff --git a/code/modules/projectiles/guns/energy/modular/modularcooling.dm b/code/modules/projectiles/guns/energy/modular/modularcooling.dm deleted file mode 100644 index 7f9c1acfcf90..000000000000 --- a/code/modules/projectiles/guns/energy/modular/modularcooling.dm +++ /dev/null @@ -1,46 +0,0 @@ - - -/////////////////////////////////////////////////////// -//Cooling -/////////////////////////////////////////////////////// -/obj/item/modularlaser/cooling - name = "modular laser part" - desc = "I shouldn't exist." - var/delaymod = 1.0 - var/costadd = 0 - -/obj/item/modularlaser/cooling/basic - name = "basic modular cooling system" - desc = "A basic air-cooling system for a modular energy weapon." - -/obj/item/modularlaser/cooling/lame - name = "compact modular cooling system" - desc = "A tiny air-cooling system for a modular energy weapon." - delaymod = 1.1 - -/obj/item/modularlaser/cooling/lame/integral - removable = FALSE - -/obj/item/modularlaser/cooling/efficient - name = "heat recovery cooling system" - desc = "A cooling system that uses heat from firing to generate some power. Needs time between shots to work." - delaymod = 1.5 - costadd = -10 - -/obj/item/modularlaser/cooling/efficient/super - name = "advanced heat recovery cooling system" - desc = "A cooling system that uses heat from firing to generate a good amount of power. Needs a lot of time between shots to work." - delaymod = 2 - costadd = -20 - -/obj/item/modularlaser/cooling/speed - name = "active cooling system" - desc = "A cooling system that uses more energy to reduce the time needed between shots." - delaymod = 0.75 - costadd = 10 - -/obj/item/modularlaser/cooling/speed/adv - name = "superradiative cooling system" - desc = "A cooling system that forces heat from firing into the air around it extremely quickly, reducing the delay between shots. Uses a good amount of power." - delaymod = 0.3 - costadd = 20 diff --git a/code/modules/projectiles/guns/energy/modular/modularfcu.dm b/code/modules/projectiles/guns/energy/modular/modularfcu.dm deleted file mode 100644 index a1f6266fc4e2..000000000000 --- a/code/modules/projectiles/guns/energy/modular/modularfcu.dm +++ /dev/null @@ -1,52 +0,0 @@ - - -///////////////////////////////////////////////////// -//Burst controller -//////////////////////////////////////////////////// -/obj/item/modularlaser/controller - name = "modular laser part" - desc = "I shouldn't exist." - var/maxburst = 1 - var/burst_delay = 3 - var/robust = FALSE - -/obj/item/modularlaser/controller/basic - name = "weapon fire control unit" - desc = "A basic weapon firing system. Controls the power supply and energy emitter in order to make the gun go pew." - -/obj/item/modularlaser/controller/basic/integral - name = "integral fire control unit" - desc = "A basic weapon firing system. Controls the power supply and energy emitter in order to make the gun go pew. Should not be able to be removed." - removable = FALSE - -/obj/item/modularlaser/controller/twoburst - name = "AN-94 burst controller" - desc = "A modular energy weapon firing controller that allows single-shot and two-shot bursts." - maxburst = 2 - burst_delay = 0.5 - -/obj/item/modularlaser/controller/threeburst - name = "burst weapon fire control unit" - desc = "A modular weapon firing unit that allows single-shot and triple-shot bursts." - maxburst = 3 - -/obj/item/modularlaser/controller/fiveburst - name = "quintuple-burst fire control unit" - desc = "Can fire a burst of five shots, or in single-shot mode." - maxburst = 5 - burst_delay = 4 - -/obj/item/modularlaser/controller/supressive - name = "supressive fire control unit" - desc = "A weapon firing controller that adds a supressive fire burst mode, unleashing a large amount of inaccurate, supressive beams at a moderate delay." - maxburst = 8 - burst_delay = 7 - -/obj/item/modularlaser/controller/robust - name = "simple weapon fire control unit" - desc = "An incredibly robust weapon firing control unit, with minimal shielded electronics. Harmlessly dissipates electro-magnetic pulses." - robust = TRUE - -/obj/item/modularlaser/controller/robust/roburst //this is just for the pun, yes - name = "simple burst fire control unit" - maxburst = 3 diff --git a/code/modules/projectiles/guns/energy/modular/modulargun.dm b/code/modules/projectiles/guns/energy/modular/modulargun.dm deleted file mode 100644 index 89e7eb2eeca7..000000000000 --- a/code/modules/projectiles/guns/energy/modular/modulargun.dm +++ /dev/null @@ -1,311 +0,0 @@ -//modular weapons 2. shitcode ahead, be warned. -//i'd just like to say nitsah is annoying as FUCK but also sometimes nice - -// if i have to maintain this perfect example of why we shouldn't just merge code just because someone made it and instead we should enforce some modicrum of fucking code standards one more time, i'm going to remove it entirely because fuck off. - -/obj/item/gun/energy/modular - name = "the very concept of a modular weapon" - desc = "An idea, given physical form? Contact your God, the Maker has made a mistake." - icon_state = "mod_pistol" - cell_type = /obj/item/cell/device/weapon - charge_cost = 120 - projectile_type = /obj/projectile/beam - var/cores = 1//How many lasing cores can we support? - var/assembled = 1 //Are we open? - var/obj/item/modularlaser/lasermedium/primarycore //Lasing medium core. - var/obj/item/modularlaser/lasermedium/secondarycore //Lasing medium core. - var/obj/item/modularlaser/lasermedium/tertiarycore //Lasing medium core. - var/obj/item/modularlaser/lens/laserlens //Lens. Dictates accuracy. Certain lenses change the projectiles to ENERGY SHOTGUN. - var/obj/item/modularlaser/capacitor/lasercap - var/obj/item/modularlaser/cooling/lasercooler - var/obj/item/modularlaser/controller/circuit - firemodes = list() - var/emp_vuln = TRUE - -/obj/item/gun/energy/modular/Initialize(mapload) - . = ..() - generatefiremodes() - -/obj/item/gun/energy/modular/examine(mob/user, dist) - . = ..() - if(primarycore) - . += "The modular weapon has a [primarycore.name] installed in the primary core slot." - if(secondarycore) - . += "The modular weapon has a [secondarycore.name] installed in the secondary core slot." - if(tertiarycore) - . += "The modular weapon has a [tertiarycore.name] installed in the tertiary core slot." - if(laserlens) - . += "The modular weapon has a [laserlens.name] installed in the lens slot." - if(lasercap) - . += "The modular weapon has a [lasercap.name] installed in the power handler slot." - if(lasercooler) - . += "The modular weapon has a [lasercooler.name] installed in the cooling system slot." - if(circuit) - . += "The modular weapon has a [circuit.name] installed in the fire control slot." - -// hilariously snowflake proc to force a firemode switch because i can't be assed to do it properly holy shit fuck you -/obj/item/gun/energy/modular/proc/generatefiremodes() - do_generatefiremodes() - if(!length(firemodes)) - return - var/datum/firemode/new_mode = firemodes[1] - new_mode.apply_to(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) - return FALSE - if(!primarycore) - return FALSE - if(!laserlens) - return FALSE - if(!lasercooler) - return FALSE - if(!lasercap) - return FALSE - firemodes = list() - var/burstmode = circuit.maxburst //Max burst controlled by the laser control circuit. - //to_chat(world, "The modular weapon at [src.loc] has begun generating a firemode.") - var/obj/projectile/beammode = primarycore.beamtype //Primary mode fire type. - 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. - accuracy = laserlens.accuracy - var/chargecost_lethal = 120 - var/chargecost_special = 120 - var/obj/projectile/beammode_lethal - var/obj/projectile/beammode_special - if(cores > 1 && secondarycore) //Secondary firemode - beammode_lethal = secondarycore.beamtype - chargecost_lethal = secondarycore.beamcost * lasercap.costmod - chargecost_lethal += lasercooler.costadd - if(cores == 3 && tertiarycore) //Tertiary firemodes - beammode_special = tertiarycore.beamtype - chargecost_special = tertiarycore.beamcost * lasercap.costmod - chargecost_special += lasercooler.costadd - var/maxburst = circuit.maxburst //Max burst. - emp_vuln = circuit.robust //is the circuit strong enough to dissipate EMPs? - //to_chat(world, "The modular weapon at [src.loc] has a max burst of [burstmode], a primary beam type of [beammode], a chargecost of [chargecost], a scatter of [scatter], a firedelay of [fire_delay], a burstdelay of [burst_delay], an accuracy of [accuracy], a chargecost of core 2 [chargecost_lethal], a beamtype of core 2 [beammode_lethal], a chargecost of core 3 [chargecost_special], a beamtype of core 3 [beammode_special]") - if(primarycore && !secondarycore && !tertiarycore) //this makes me sick but ill ask if there's a better way to do this - if(chargecost < 0) - chargecost = 1 - if(scatter) - beammode = primarycore.scatterbeam - chargecost *= 2 - if(burstmode > 1) - firemodes = list( - new /datum/firemode(src, list(mode_name=primarycore.firename, projectile_type=beammode, charge_cost = chargecost, burst = 1)), - new /datum/firemode(src, list(mode_name="[maxburst] shot [primarycore.firename]", projectile_type=beammode, charge_cost = chargecost, burst = maxburst)) - ) - return TRUE - else - firemodes = list( - new /datum/firemode(src, list(mode_name=primarycore.firename, projectile_type=beammode, charge_cost = chargecost, burst = 1)) - ) - return TRUE - if(primarycore && secondarycore && !tertiarycore) - if(chargecost < 0) - chargecost = 0 - if(chargecost_lethal < 0) - chargecost_lethal = 0 - if(scatter) - beammode = primarycore.scatterbeam - beammode_lethal = secondarycore.scatterbeam - chargecost *= 2 - chargecost_lethal *= 2 - if(burstmode > 1) - firemodes = list( - new /datum/firemode(src, list(mode_name=primarycore.firename, projectile_type=beammode, charge_cost = chargecost, burst = 1)), - new /datum/firemode(src, list(mode_name=secondarycore.firename, projectile_type=beammode_lethal, charge_cost = chargecost_lethal, burst = 1)), - new /datum/firemode(src, list(mode_name="[maxburst] shot [primarycore.firename]", projectile_type=beammode, charge_cost = chargecost, burst = maxburst)), - new /datum/firemode(src, list(mode_name="[maxburst] shot [secondarycore.firename]", projectile_type=beammode_lethal, charge_cost = chargecost_lethal, burst = maxburst)) - ) - return TRUE - else - firemodes = list( - new /datum/firemode(src, list(mode_name=primarycore.firename, projectile_type=beammode, charge_cost = chargecost, burst = 1)), - new /datum/firemode(src, list(mode_name=secondarycore.firename, projectile_type=beammode_lethal, charge_cost = chargecost_lethal, burst = 1)) - ) - return TRUE - if(primarycore && secondarycore && tertiarycore) - if(chargecost < 0) - chargecost = 1 - if(chargecost_lethal < 0) - chargecost_lethal = 1 - if(chargecost_special < 0) - chargecost_special = 1 - if(scatter) - beammode = primarycore.scatterbeam - beammode_lethal = secondarycore.scatterbeam - beammode_special = tertiarycore.scatterbeam - chargecost *= 2 - chargecost_lethal *= 2 - chargecost_special *= 2 - if(burstmode > 1) - firemodes = list( - new /datum/firemode(src, list(mode_name=primarycore.firename, projectile_type=beammode, charge_cost = chargecost, burst = 1)), - new /datum/firemode(src, list(mode_name=secondarycore.firename, projectile_type=beammode_lethal, charge_cost = chargecost_lethal, burst = 1)), - new /datum/firemode(src, list(mode_name=tertiarycore.firename, projectile_type=beammode_special, charge_cost = chargecost_special, burst = 1)), - new /datum/firemode(src, list(mode_name="[maxburst] shot [primarycore.firename]", projectile_type=beammode, charge_cost = chargecost, burst = maxburst)), - new /datum/firemode(src, list(mode_name="[maxburst] shot [secondarycore.firename]", projectile_type=beammode_lethal, charge_cost = chargecost_lethal, burst = maxburst)), - new /datum/firemode(src, list(mode_name="[maxburst] shot [tertiarycore.firename]", projectile_type=beammode_special, charge_cost = chargecost_special, burst = maxburst)) - ) - return TRUE - else - return FALSE - -/obj/item/gun/energy/modular/emp_act(severity) - if(!emp_vuln) - return FALSE - return ..() - -/obj/item/gun/energy/modular/AltClick(mob/user) - generatefiremodes() - to_chat(user, "You hit the reset on the weapon's internal checking system.") - - -/obj/item/gun/energy/modular/special_check(mob/user) - . = ..() - if(!circuit) - to_chat(user, "The gun is missing parts!") - return FALSE - if(!primarycore) - to_chat(user, "The gun is missing parts!") - return FALSE - if(!laserlens) - to_chat(user, "The gun is missing parts!") - return FALSE - if(!lasercooler) - to_chat(user, "The gun is missing parts!") - return FALSE - if(!lasercap) - to_chat(user, "The gun is missing parts!") - return FALSE - if(!assembled) - to_chat(user, "The gun is open!") - return FALSE - 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) - return FALSE - -/obj/item/gun/energy/modular/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) - if(I.is_screwdriver()) - to_chat(user, "You [assembled ? "disassemble" : "assemble"] the gun.") - assembled = !assembled - playsound(src, I.tool_sound, 50, 1) - generatefiremodes() - return - if(I.is_crowbar()) - if(assembled == TRUE) - to_chat(user, "Open [src] first!") - return - var/turf/T = get_turf(src) - if(primarycore && primarycore.removable == TRUE) - primarycore.forceMove(T) - primarycore = null - if(secondarycore && secondarycore.removable == TRUE) - secondarycore.forceMove(T) - secondarycore = null - if(tertiarycore && tertiarycore.removable == TRUE) - tertiarycore.forceMove(T) - tertiarycore = null - if(laserlens && laserlens.removable == TRUE) - laserlens.forceMove(T) - laserlens = null - if(lasercap && lasercap.removable == TRUE) - lasercap.forceMove(T) - lasercap = null - if(lasercooler && lasercooler.removable == TRUE) - lasercooler.forceMove(T) - lasercooler = null - if(circuit && circuit.removable == TRUE) - circuit.forceMove(T) - circuit = null - generatefiremodes() - if(istype(I, /obj/item/modularlaser)) - if(assembled == TRUE) - to_chat(user, "Open [src] first!") - return - var/obj/item/modularlaser/ML = I - if(istype(ML,/obj/item/modularlaser/lasermedium)) - var/obj/item/modularlaser/lasermedium/med = ML - if(!primarycore && cores >= 1) - if(!user.attempt_insert_item_for_installation(med, src)) - return - primarycore = med - to_chat(user, "You install the [med.name] in the primary core slot.") - generatefiremodes() - return - if(!secondarycore && cores >= 2) - if(!user.attempt_insert_item_for_installation(med, src)) - return - secondarycore = med - to_chat(user, "You install the [med.name] in the secondary core slot.") - generatefiremodes() - return - if(!tertiarycore && cores == 3) - if(!user.attempt_insert_item_for_installation(med, src)) - return - tertiarycore = med - to_chat(user, "You install the [med.name] in the tertiary core slot.") - generatefiremodes() - return - if(istype(ML, /obj/item/modularlaser/lens)) - var/obj/item/modularlaser/lens/L = ML - if(!laserlens) - if(!user.attempt_insert_item_for_installation(I, src)) - return - laserlens = L - to_chat(user, "You install the [L.name] in the lens holder.") - generatefiremodes() - return - if(istype(ML, /obj/item/modularlaser/capacitor)) - var/obj/item/modularlaser/capacitor/C = ML - if(!lasercap) - if(!user.attempt_insert_item_for_installation(I, src)) - return - lasercap = C - to_chat(user, "You install the [C.name] in the power supply slot.") - generatefiremodes() - return - if(istype(ML, /obj/item/modularlaser/cooling)) - var/obj/item/modularlaser/cooling/CO = ML - if(!lasercooler) - if(!user.attempt_insert_item_for_installation(I, src)) - return - lasercooler = CO - to_chat(user, "You install the [CO.name] in the cooling system mount.") - generatefiremodes() - return - if(istype(ML, /obj/item/modularlaser/controller)) - var/obj/item/modularlaser/controller/CON = ML - if(!circuit) - if(!user.attempt_insert_item_for_installation(I, src)) - return - circuit = CON - to_chat(user, "You install the [CON.name] in the fire control unit mount and connect it.") - generatefiremodes() - return - return ..() - -//these are debug ones. -/obj/item/gun/energy/modular/twocore - name = "bicore modular weapon" - cores = 2 - -/obj/item/gun/energy/modular/threecore - name = "tricore modular weapon" - cores = 3 - - - -//parts -/obj/item/modularlaser - name = "modular laser part" - desc = "I shouldn't exist." - icon = 'icons/obj/device_alt.dmi' - icon_state = "modkit" - var/removable = TRUE diff --git a/code/modules/projectiles/guns/energy/modular/modularlenses.dm b/code/modules/projectiles/guns/energy/modular/modularlenses.dm deleted file mode 100644 index 93947d3e6e5e..000000000000 --- a/code/modules/projectiles/guns/energy/modular/modularlenses.dm +++ /dev/null @@ -1,67 +0,0 @@ -////////////////////////////////////////////////// -//Lenses -/////////////////////////////////////////////////// - -/obj/item/modularlaser/lens - name = "modular laser part" - desc = "I shouldn't exist." - var/scatter = FALSE - var/accuracy = 0 - -/obj/item/modularlaser/lens/basic - name = "basic modular lens" - desc = "A basic lens with no drawbacks or upsides." - -/obj/item/modularlaser/lens/lame - name = "weaksauce modular lens" - desc = "A shitty lens with drawbacks." - accuracy = -5 - -/obj/item/modularlaser/lens/lame/integral - name = "weaksauce integral modular lens" - removable = FALSE - -/obj/item/modularlaser/lens/advanced - name = "advanced modular lens" - desc = "An advanced metamaterial lens that focuses beams more accurately." - accuracy = 15 //1 tile closer - scatter = FALSE - -/obj/item/modularlaser/lens/super - name = "metamaterial modular lens" - desc = "An advanced metamaterial lens that focuses beams extremely accurately." - accuracy = 30 //2 tiles closer - -/obj/item/modularlaser/lens/admin //badmin only - name = "nanomachined modular lens" - desc = "A swarm of transparent nanites that causes your beams to hit always, 100% of time time or your money back." - accuracy = 225 //you shouldn't miss - -/obj/item/modularlaser/lens/scatter - name = "refracting modular lens" - desc = "A simple glass lens that splits beams on contact. Very hard to aim with." - scatter = TRUE - accuracy = -60 //4 tiles further - -/obj/item/modularlaser/lens/scatter/adv - name = "advanced refracting modular lens" - desc = "A well-machined glass lens that splits beams on contact. Hard to aim with." - accuracy = -45 //3 tiles further - -/obj/item/modularlaser/lens/scatter/super - name = "metamaterial refracting modular lens" - desc = "An advanced metamaterial lens that splits beams. Somewhat hard to aim with." - accuracy = -15 //1 tile further - -/obj/item/modularlaser/lens/scatter/hyper //VERY expensive. Precursor tech only. - name = "supermaterial refracting modular lens" - desc = "A bleeding-edge metamaterial lens that splits beams." - accuracy = 0 - -/obj/item/modularlaser/lens/scatter/hyper/integral - removable = FALSE - -/obj/item/modularlaser/lens/scatter/admin - name = "nanomachined refracting modular lens" - desc = "An advanced nanomachined lens that splits beams." - accuracy = 225 //100% of shots should land. diff --git a/code/modules/projectiles/guns/energy/modular/modularpower.dm b/code/modules/projectiles/guns/energy/modular/modularpower.dm deleted file mode 100644 index ae1d52228469..000000000000 --- a/code/modules/projectiles/guns/energy/modular/modularpower.dm +++ /dev/null @@ -1,69 +0,0 @@ - -/////////////////////////////////////////// -//Power supplies -//////////////////////////////////////////// -/obj/item/modularlaser/capacitor - name = "modular laser part" - desc = "I shouldn't exist." - var/costmod = 1.0 - var/firedelay = 6 //im 99% sure these are in deciseconds - -/obj/item/modularlaser/capacitor/basic - name = "weapon power handler" - desc = "A garden-variety power handling unit for a modular energy weapon." - -/obj/item/modularlaser/capacitor/simple - name = "simple weapon power handler" - desc = "A simplistic power handling unit for a modular energy weapon." - firedelay = 10 - -/obj/item/modularlaser/capacitor/simple/integral //meant to be unremoveable - name = "integrated compact weapon power handler" - desc = "A compact energy weapon power handling system. Unable to be removed from the weapon it is mounted in, normally." - removable = FALSE - -/obj/item/modularlaser/capacitor/eco - name = "efficient weapon power handler" - desc = "An energy handler for a modular energy weapon that recoups some of the energy used, at the cost of longer delay between shots." - costmod = 0.9 - firedelay = 12 //twice as long - -/obj/item/modularlaser/capacitor/eco/super - name = "advanced energy-recovery weapon power handler" - desc = "A power handler for a modular energy weapon that recoups a significant amount of the energy used, at the cost of a significant delay between shots." - costmod = 0.75 - firedelay = 20 - -/obj/item/modularlaser/capacitor/eco/hyper - name = "bleeding-edge energy-recovery weapon power handler" - desc = "A power handler for a modular energy weapon that recoups half of the energy used, at the cost of a crippling delay between shots." - costmod = 0.5 - firedelay = 40 - -/obj/item/modularlaser/capacitor/eco/admin //admin only. - name = "zero-point energy-recovery weapon power handler" - desc = "A power handler for a modular energy weapon that recoups almost all of the energy used, at the cost of a delay between shots." - costmod = 0.01 - firedelay = 10 - -/obj/item/modularlaser/capacitor/speed - name = "throughput-calibrated weapon power handler" - desc = "A power handler for a modular energy weapon that is less efficient, but has half the delay between shots." - costmod = 1.2 - firedelay = 3 - -/obj/item/modularlaser/capacitor/speed/advanced - name = "throughput-focused weapon power handler" - desc = "A power handler for a modular energy weapon that is inefficient, but has less than half the delay between shots." - costmod = 1.5 - firedelay = 1 - -/obj/item/modularlaser/capacitor/speed/admin - name = "superconductive weapon power handler" - desc = "A power handler for a modular energy weapon that is efficient, and has no delay between shots." - costmod = 1 - firedelay = 0 - -/obj/item/modularlaser/capacitor/cannon - firedelay = 3 - removable = FALSE 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 4dca17d1d3a4..7c2637a17add 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( @@ -58,8 +61,7 @@ /obj/item/gun/energy/floragun/afterattack(atom/target, mob/user, clickchain_flags, list/params) //allow shooting into adjacent hydrotrays regardless of intent if((clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) && istype(target,/obj/machinery/portable_atmospherics/hydroponics)) - user.visible_message("\The [user] fires \the [src] into \the [target]!") - Fire(target,user) + start_firing_cycle_async(user, get_centered_entity_angle(user, target), NONE, null, target, new /datum/event_args/actor(user)) return ..() @@ -77,9 +79,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 +142,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 +184,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 +213,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 +238,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 +266,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 +309,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 +325,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 +345,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 +360,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 +386,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 +418,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 +430,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_worn_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 +449,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_worn_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..e29f95bc2238 --- /dev/null +++ b/code/modules/projectiles/guns/firemode.dm @@ -0,0 +1,85 @@ +//* 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 *// + /// modify the gun's base state when active + /// * very, very dangerous, know what you are doing. + var/override_icon_base + /// 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 + // these don't corrospond to our own vars, these corrospond to old hardcoded strings. + 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-attachment.dm b/code/modules/projectiles/guns/gun-attachment.dm new file mode 100644 index 000000000000..588c9cf172ed --- /dev/null +++ b/code/modules/projectiles/guns/gun-attachment.dm @@ -0,0 +1,157 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Check if we can attach an attachment + */ +/obj/item/gun/proc/can_install_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, silent) + if(!attachment.attachment_slot || !attachment_alignment[attachment.attachment_slot]) + if(!silent) + actor?.chat_feedback( + SPAN_WARNING("[attachment] won't fit anywhere on [src]!"), + target = src, + ) + return FALSE + if(attachment.attachment_type & attachment_type_blacklist) + if(!silent) + actor?.chat_feedback( + SPAN_WARNING("[attachment] doesn't work with [src]!"), + target = src, + ) + return FALSE + for(var/obj/item/gun_attachment/existing as anything in attachments) + if(existing.attachment_slot == attachment.attachment_slot) + if(!silent) + actor?.chat_feedback( + SPAN_WARNING("[src] already has [existing] installed on its [existing.attachment_slot]!"), + target = src, + ) + return FALSE + if(existing.attachment_type & attachment.attachment_type) + if(!silent) + actor?.chat_feedback( + SPAN_WARNING("[src]'s [existing] conflicts with [attachment]!"), + target = src, + ) + return FALSE + if(!attachment.fits_on_gun(src, actor, silent)) + return FALSE + return TRUE + +/** + * Called when a mob tries to uninstall an attachment + */ +/obj/item/gun/proc/user_install_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor) + if(actor) + if(actor.performer && actor.performer.is_in_inventory(attachment)) + if(!actor.performer.can_unequip(attachment, attachment.worn_slot)) + actor.chat_feedback( + SPAN_WARNING("[attachment] is stuck to your hand!"), + target = src, + ) + return FALSE + if(!install_attachment(attachment, actor)) + return FALSE + // todo: better sound + playsound(src, 'sound/weapons/empty.ogg', 25, TRUE, -3) + return TRUE + +/** + * Installs an attachment + * + * * This moves the attachment into the gun if it isn't already. + * * This does have default visible feedback for the installation. + * + * @return TRUE / FALSE on success / failure + */ +/obj/item/gun/proc/install_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, silent) + if(!can_install_attachment(attachment, actor, silent)) + return FALSE + + if(!silent) + actor?.visible_feedback( + target = src, + visible = SPAN_NOTICE("[actor.performer] attaches [attachment] to [src]'s [attachment.attachment_slot]."), + ) + if(attachment.loc != src) + attachment.forceMove(src) + + LAZYADD(attachments, attachment) + attachment.attached = src + attachment.on_attach(src) + attachment.update_gun_overlay() + on_attachment_install(attachment) + var/mob/holding_mob = get_worn_mob() + if(holding_mob) + attachment.register_attachment_actions(holding_mob) + return TRUE + +/** + * Called when a mob tries to uninstall an attachment + */ +/obj/item/gun/proc/user_uninstall_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, put_in_hands) + if(!attachment.can_detach) + actor?.chat_feedback( + SPAN_WARNING("[attachment] is not removable."), + target = src, + ) + return FALSE + var/obj/item/uninstalled = uninstall_attachment(attachment, actor) + if(put_in_hands && actor?.performer) + actor.performer.put_in_hands_or_drop(uninstalled) + else + var/atom/where_to_drop = drop_location() + ASSERT(where_to_drop) + uninstalled.forceMove(where_to_drop) + // todo: better sound + playsound(src, 'sound/weapons/empty.ogg', 25, TRUE, -3) + return TRUE + +/** + * Uninstalls an attachment + * + * * This does not move the attachment after uninstall; you have to do that. + * * This does not have default visible feedback for the uninstallation / removal. + * + * @return the /obj/item uninstalled + */ +/obj/item/gun/proc/uninstall_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, silent, deleting) + ASSERT(attachment.attached == src) + var/mob/holding_mob = get_worn_mob() + if(holding_mob) + attachment.unregister_attachment_actions(holding_mob) + attachment.on_detach(src) + attachment.remove_gun_overlay() + attachment.attached = null + on_attachment_uninstall(attachment) + LAZYREMOVE(attachments, attachment) + return deleting ? null : attachment.uninstall_product_transform(src) + +/** + * Align an attachment overlay. + * + * @return TRUE / FALSE on success / failure + */ +/obj/item/gun/proc/align_attachment_overlay(obj/item/gun_attachment/attachment, image/appearancelike) + var/list/alignment = attachment_alignment?[attachment.attachment_slot] + if(!alignment) + return FALSE + appearancelike.pixel_x = (alignment[1] - attachment.align_x) + appearancelike.pixel_y = (alignment[2] - attachment.align_y) + return TRUE + +/** + * Called exactly once when an attachment is installed + * + * * Called before the attachment's on_attach() + */ +/obj/item/gun/proc/on_attachment_install(obj/item/gun_attachment/attachment) + PROTECTED_PROC(TRUE) + +/** + * Called exactly once when an attachment is uninstalled + * + * * Called after the attachment's on_detach() + */ +/obj/item/gun/proc/on_attachment_uninstall(obj/item/gun_attachment/attachment) + PROTECTED_PROC(TRUE) diff --git a/code/modules/projectiles/guns/gun-firing.dm b/code/modules/projectiles/guns/gun-firing.dm new file mode 100644 index 000000000000..53f7bcc46548 --- /dev/null +++ b/code/modules/projectiles/guns/gun-firing.dm @@ -0,0 +1,233 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Auto Handling *// + +/** + * @return clickchain flags + */ +/obj/item/gun/proc/handle_clickchain_fire(datum/event_args/actor/clickchain/clickchain, clickchain_flags) + var/mob/resolved_firer = clickchain.performer + var/resolved_angle = clickchain.resolve_click_angle() + var/resolved_firing_flags = NONE + if(resolved_firer.Reachability(clickchain.target)) + resolved_firing_flags |= GUN_FIRING_POINT_BLANK + start_firing_cycle_async( + resolved_firer, + resolved_angle, + resolved_firing_flags, + legacy_get_firemode(), + clickchain.target, + clickchain, + clickchain.click_params_tile_px, + clickchain.click_params_tile_py, + clickchain.legacy_get_target_zone(), + ) + return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING + #warn handle pointblank + #warn impl + +//* 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, tile_pixel_x, tile_pixel_y, target_zone) 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, tile_pixel_x, tile_pixel_y, target_zone) + // 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, tile_pixel_x, tile_pixel_y, target_zone) 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, tile_pixel_x, tile_pixel_y, target_zone) + +/** + * 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, a gun, a person, or anything else. + * this is where the projectile will originate regardles of where the gun actually is! + * * angle - the angle to fire in. + * * firing_flags - (optional) 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, tile_pixel_x, tile_pixel_y, target_zone) 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. + */ + + if(isnull(firemode)) + firemode = legacy_get_firemode() + ASSERT(firemode) + #warn logging + + // 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 + our_cycle.original_tile_pixel_x = tile_pixel_x + our_cycle.original_tile_pixel_y = tile_pixel_y + our_cycle.original_target_zone = target_zone + // 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_PREFIRE, our_cycle) + // did they abort? + if(our_cycle.next_firing_fail_result) + our_cycle.finish_iteration(our_cycle.next_firing_fail_result) + // else fire + else + our_cycle.finish_iteration(fire(our_cycle)) + SEND_SIGNAL(src, COMSIG_GUN_FIRING_POSTFIRE, our_cycle) + // post-fire + if(!post_fire(our_cycle)) + break + // reset variables + // 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) + return post_live_fire(cycle) + if(GUN_FIRED_FAIL_EMPTY, GUN_FIRED_FAIL_INERT) + return post_empty_fire(cycle) + else + return FALSE + +//* Firing - Default Handlers (Overridable) *// + +/** + * Called on successful firing + * + * @return FALSE to abort firing cycle. + */ +/obj/item/gun/proc/post_live_fire(datum/gun_firing_cycle/cycle) + return TRUE + +/** + * 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 = get_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..15dc1cd9f24e --- /dev/null +++ b/code/modules/projectiles/guns/gun-modular.dm @@ -0,0 +1,89 @@ +//* 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/count_for_slot = 1 // 1 because we're adding one + for(var/obj/item/gun_component/existing in modular_components) + if(existing.component_slot == component.component_slot) + count_for_slot++ + if(existing.component_conflict & component.component_conflict) + if(!silent) + actor?.chat_feedback( + SPAN_WARNING("[existing] conflicts with [component] due to being too similar!"), + target = src, + ) + return FALSE + if((existing.component_type || existing.type) == (component.component_type || component.type)) + if(!silent) + actor?.chat_feedback( + SPAN_WARNING("[existing] conflicts with [component] due to being too similar!"), + target = src, + ) + return FALSE + var/is_full = (count_for_slot >= modular_component_slots?[component.component_slot]) + 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/install_modular_component(obj/item/gun_component/component, datum/event_args/actor/actor, silent, force) + SHOULD_NOT_OVERRIDE(TRUE) + SHOULD_NOT_SLEEP(TRUE) + #warn impl + +/** + * * deletes the component if no location is provided to move it to + */ +/obj/item/gun/proc/uninstall_modular_component(obj/item/gun_component/component, datum/event_args/actor/actor, silent, force, atom/new_loc) + SHOULD_NOT_OVERRIDE(TRUE) + SHOULD_NOT_SLEEP(TRUE) + #warn impl + +#warn hook everything in attackby's + +//* Modular Components - Hooks *// + +/obj/item/gun/proc/on_modular_component_install(obj/item/gun_component/component, datum/event_args/actor/actor, silent) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + +/obj/item/gun/proc/on_modular_component_uninstall(obj/item/gun_component/component, datum/event_args/actor/actor, silent) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + +//* 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..6e030ef8fd16 --- /dev/null +++ b/code/modules/projectiles/guns/gun-projectile-implementation.dm @@ -0,0 +1,105 @@ +//* 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) + + // handle legacy systems + var/held_twohanded = TRUE + if(ismob(cycle.firing_atom)) + var/mob/mob_firer = cycle.firing_atom + held_twohanded = item_flags & ITEM_MULTIHAND_WIELDED + 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 + // sike; real poitn of no return + SEND_SIGNAL(src, COMSIG_GUN_FIRING_PROJECTILE_INJECTION, cycle, firing_projectile) + // if they want to abort.. + if(cycle.next_firing_fail_result) + qdel(firing_projectile) + return cycle.next_firing_fail_result + + //! 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) + launch_projectile(cycle, 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 + launching.p_x = cycle.original_tile_pixel_x + launching.p_y = cycle.original_tile_pixel_y + //! END + + launching.original_target = cycle.original_target + launching.firer = cycle.firing_atom + launching.def_zone = cycle.original_target_zone + + var/effective_angle = cycle.original_angle + cycle.base_angle_adjust + cycle.next_angle_adjust + var/effective_dispersion = cycle.base_dispersion_adjust + cycle.next_dispersion_adjust + + effective_angle += rand(-effective_dispersion, effective_dispersion) + + #warn launching's launch_projectile_common + + launching.fire(effective_angle, get_turf(cycle.original_target) == get_turf(src) ? cycle.original_target : null) + +/** + * 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 + * * Everything is optional here. Things like portable turrets reserve the right to 'pull' from the gun without caring about params. + * + * This should be called as the point of no return. + * + * * All of your checks that can / should fail go before the ..() call, as that's what makes the projectile. + * * Anything that doesn't do anything but emit side effects go after. + * * Once the projectile is made, you must delete it if you want to cancel. Otherwise, it's a memory leak. + */ +/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/guns/gun-projectile-legacy.dm b/code/modules/projectiles/guns/gun-projectile-legacy.dm new file mode 100644 index 000000000000..73c3ba9c8ecd --- /dev/null +++ b/code/modules/projectiles/guns/gun-projectile-legacy.dm @@ -0,0 +1,55 @@ +/obj/item/gun/proc/process_point_blank(obj/projectile, mob/user, atom/target) + var/obj/projectile/P = projectile + if(!istype(P)) + return //default behaviour only applies to true projectiles + + //default point blank multiplier + var/damage_mult = 1.3 + + //determine multiplier due to the target being grabbed + if(ismob(target)) + var/mob/M = target + if(M.grabbed_by.len) + var/grabstate = 0 + for(var/obj/item/grab/G in M.grabbed_by) + grabstate = max(grabstate, G.state) + if(grabstate >= GRAB_NECK) + damage_mult = 2.5 + else if(grabstate >= GRAB_AGGRESSIVE) + damage_mult = 1.5 + P.damage_force *= damage_mult + +/obj/item/gun/proc/process_accuracy(obj/projectile, mob/living/user, atom/target, var/burst, var/held_twohanded) + var/obj/projectile/P = projectile + if(!istype(P)) + return //default behaviour only applies to true projectiles + + var/acc_mod = burst_accuracy[min(burst, burst_accuracy.len)] + var/disp_mod = dispersion[min(burst, dispersion.len)] + + if(one_handed_penalty) + if(!held_twohanded) + acc_mod += -CEILING(one_handed_penalty/2, 1) + disp_mod += one_handed_penalty*0.5 //dispersion per point of two-handedness + + //Accuracy modifiers + if(!isnull(accuracy_disabled)) + P.accuracy_disabled = accuracy_disabled + + P.accuracy_overall_modify *= 1 + (acc_mod / 100) + P.accuracy_overall_modify *= 1 - (user.get_accuracy_penalty() / 100) + P.dispersion = disp_mod + + //accuracy bonus from aiming + if (aim_targets && (target in aim_targets)) + //If you aim at someone beforehead, it'll hit more often. + //Kinda balanced by fact you need like 2 seconds to aim + //As opposed to no-delay pew pew + P.accuracy_overall_modify *= 1.3 + + // Some modifiers make it harder or easier to hit things. + for(var/datum/modifier/M in user.modifiers) + if(!isnull(M.accuracy)) + P.accuracy_overall_modify += 1 + (M.accuracy / 100) + if(!isnull(M.accuracy_dispersion)) + P.dispersion = max(P.dispersion + M.accuracy_dispersion, 0) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/guns/gun.dm similarity index 53% rename from code/modules/projectiles/gun.dm rename to code/modules/projectiles/guns/gun.dm index 7e23875486c7..b5fe20fa8d3b 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/guns/gun.dm @@ -1,39 +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] - /** - * Weapons that can be aimed at an angle or a mob or whatever. + * # 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. * - * Current caveats: + * 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. + * + * ## Current Caveats * * * Flashlight attachments directly edit the light variable of the gun. This means that they'll trample the gun's * inherent light if there is one. @@ -89,15 +89,43 @@ /// Blacklisted attachment types. var/attachment_type_blacklist = NONE - // 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 + + //! legacy below !// - 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 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 var/recoil = 0 //screen shake var/suppressible = FALSE var/silenced = FALSE @@ -107,21 +135,17 @@ 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 + // todo: do not use this var, use firemodes on /energy 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 @@ -129,25 +153,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 @@ -157,7 +171,46 @@ var/unstable = 0 var/destroyed = 0 - //* Rendering *// + //! legacy above !// + + //* 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 @@ -188,13 +241,16 @@ /// * ignores [mob_renderer] /// * ignores [render_additional_exclusive] / [render_additional_worn] /// * ordering: [base]-wield-[additional]-[...rest] + #warn impl var/render_mob_wielded = FALSE /// state to add as an append /// /// * segment and overlay renders add [base_icon_state]-[append] /// * state renders set state to [base_icon_state]-[wield?]-[append]-[...rest] + #warn erase? var/render_additional_state /// only render [render_additional_state] + #warn deal with this var/render_additional_exclusive = FALSE /// [render_additional_state] and [render_additional_exclusive] apply to worn sprites // todo: impl @@ -206,6 +262,8 @@ /obj/item/gun/Initialize(mapload) . = ..() + //* datum component - wielding *// + AddComponent(/datum/component/wielding) //* instantiate & dedupe renderers *// var/requires_icon_update @@ -224,7 +282,9 @@ 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', @@ -253,6 +313,8 @@ qdel(actual) //! LEGACY: firemodes + if(!islist(firemodes)) + firemodes = list(firemodes) for(var/i in 1 to firemodes.len) var/key = firemodes[i] if(islist(key)) @@ -273,54 +335,69 @@ if(pin) pin = new pin(src) + //! LEGACY ABOVE !// + + // 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 + + // 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/Destroy() + if(locate(/obj/projectile) in src) + stack_trace("found an /obj/projectile in ourselves. this is not only invalid state, but means someone probably caused a memory leak.") QDEL_NULL(pin) QDEL_LIST(attachments) return ..() -/obj/item/gun/CtrlClick(mob/user) - if(can_flashlight && ishuman(user) && src.loc == usr && !user.incapacitated(INCAPACITATION_ALL)) - toggle_flashlight() - else - return ..() - -/obj/item/gun/proc/toggle_flashlight() - if(gun_light) - set_light(0) - gun_light = FALSE - else - set_light(light_brightness) - gun_light = TRUE - - playsound(src, 'sound/machines/button.ogg', 25) - update_icon() - -/obj/item/gun/update_twohanding() - if(one_handed_penalty) - var/mob/living/M = loc - if(istype(M)) - if(M.can_wield_item(src) && src.is_held_twohanded(M)) - name = "[initial(name)] (wielded)" - else - name = initial(name) +/obj/item/gun/examine(mob/user, dist) + . = ..() + if(!no_pin_required) + if(pin) + . += "It has \a [pin] installed." else - name = initial(name) - update_icon() // In case item_state is set somewhere 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) + . += SPAN_NOTICE("The safety is [check_safety() ? "on" : "off"].") + for(var/obj/item/gun_attachment/attachment as anything in attachments) + . += "It has [attachment] installed on its [attachment.attachment_slot].[attachment.can_detach ? "" : " It doesn't look like it can be removed."]" + for(var/obj/item/gun_component/component as anything in modular_components) + . += "It has a [component.get_examine_fragment()] installed." -/obj/item/gun/update_worn_icon() +/obj/item/gun/on_wield(mob/user, hands) + . = ..() + // legacy if(wielded_item_state) - var/mob/living/M = loc - if(istype(M)) - LAZYINITLIST(item_state_slots) - if(M.can_wield_item(src) && src.is_held_twohanded(M)) - item_state_slots[SLOT_ID_LEFT_HAND] = wielded_item_state - item_state_slots[SLOT_ID_RIGHT_HAND] = wielded_item_state - else - item_state_slots[SLOT_ID_LEFT_HAND] = initial(item_state) - item_state_slots[SLOT_ID_RIGHT_HAND] = initial(item_state) - ..() + LAZYINITLIST(item_state_slots) + item_state_slots[SLOT_ID_LEFT_HAND] = wielded_item_state + item_state_slots[SLOT_ID_RIGHT_HAND] = wielded_item_state +/obj/item/gun/on_unwield(mob/user, hands) + . = ..() + // legacy + if(wielded_item_state) + LAZYINITLIST(item_state_slots) + item_state_slots[SLOT_ID_LEFT_HAND] = initial(item_state) + 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. @@ -337,31 +414,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() @@ -373,19 +427,33 @@ /obj/item/gun/afterattack(atom/target, mob/living/user, clickchain_flags, list/params) if(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) return - if(!istype(user)) + + 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/shitty_legacy_params = list2params(params) 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 && 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 else - Fire(target, user, shitty_legacy_params) //Otherwise, fire normally. - return + var/datum/event_args/actor/clickchain/e_args = new(user) + e_args.click_params = params + e_args.target = target + e_args.using_intent = user.a_intent + return handle_clickchain_fire(e_args, clickchain_flags) /obj/item/gun/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) var/mob/living/A = target @@ -401,8 +469,11 @@ PreFire(A,user) //They're using the new gun system, locate what they're aiming at. return else - Fire(A, user, pointblank=1) - return + var/datum/event_args/actor/clickchain/e_args = new(user) + e_args.click_params = params + e_args.target = target + e_args.using_intent = user.a_intent + return handle_clickchain_fire(e_args, clickchain_flags) return ..() //Pistolwhippin' /obj/item/gun/using_item_on(obj/item/using, datum/event_args/actor/clickchain/e_args, clickchain_flags, datum/callback/reachability_check) @@ -412,13 +483,14 @@ if(istype(using, /obj/item/gun_attachment)) user_install_attachment(using, e_args) return CLICKCHAIN_DO_NOT_PROPAGATE + #warn gun component attach -/obj/item/gun/attackby(obj/item/A, mob/user) - if(A.is_multitool()) +/obj/item/gun/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) + if(I.is_multitool()) if(!scrambled) to_chat(user, "You begin scrambling \the [src]'s electronic pins.") - playsound(src, A.tool_sound, 50, 1) - if(do_after(user, 60 * A.tool_speed)) + playsound(src, I.tool_sound, 50, 1) + if(do_after(user, 60 * I.tool_speed)) switch(rand(1,100)) if(1 to 10) to_chat(user, "The electronic pin suite detects the intrusion and explodes!") @@ -434,11 +506,11 @@ else to_chat(user, "\The [src] does not have an active electronic warfare suite!") - if(A.is_wirecutter()) + if(I.is_wirecutter()) if(pin && scrambled) to_chat(user, "You attempt to remove \the firing pin from \the [src].") - playsound(src, A.tool_sound, 50, 1) - if(do_after(user, 60* A.tool_speed)) + playsound(src, I.tool_sound, 50, 1) + if(do_after(user, 60 * I.tool_speed)) switch(rand(1,100)) if(1 to 10) to_chat(user, "You twist the firing pin as you tug, destroying the firing pin.") @@ -457,200 +529,19 @@ else to_chat(user, "\The [src] does not have a firing pin installed!") - ..() + return ..() /obj/item/gun/emag_act(var/remaining_charges, var/mob/user) if(pin) 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. - - 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) - - 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.are_usable_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)) - target_for_log = target - else - target_for_log = "[target.name]" - - 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 - - // todo: better muzzle flash - // 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) - if(fire_anim) - flick(fire_anim, src) - + SHOULD_NOT_OVERRIDE(TRUE) + #warn obliterate this if(silenced) to_chat(user, "You fire \the [src][pointblank ? " point blank at \the [target]":""][reflex ? " by reflex":""]") for(var/mob/living/L in oview(2,user)) @@ -667,11 +558,8 @@ "You hear a [fire_sound_text]!" ) - if(muzzle_flash) - set_light(muzzle_flash) - if(one_handed_penalty) - if(!src.is_held_twohanded(user)) + if(!(item_flags & ITEM_MULTIHAND_WIELDED)) switch(one_handed_penalty) if(1 to 15) if(prob(50)) //don't need to tell them every single time @@ -697,86 +585,6 @@ if(recoil) spawn() shake_camera(user, recoil+1, recoil) - update_icon() - -/obj/item/gun/proc/process_point_blank(obj/projectile, mob/user, atom/target) - var/obj/projectile/P = projectile - if(!istype(P)) - return //default behaviour only applies to true projectiles - - //default point blank multiplier - var/damage_mult = 1.3 - - //determine multiplier due to the target being grabbed - if(ismob(target)) - var/mob/M = target - if(M.grabbed_by.len) - var/grabstate = 0 - for(var/obj/item/grab/G in M.grabbed_by) - grabstate = max(grabstate, G.state) - if(grabstate >= GRAB_NECK) - damage_mult = 2.5 - else if(grabstate >= GRAB_AGGRESSIVE) - damage_mult = 1.5 - P.damage_force *= damage_mult - -/obj/item/gun/proc/process_accuracy(obj/projectile, mob/living/user, atom/target, var/burst, var/held_twohanded) - var/obj/projectile/P = projectile - if(!istype(P)) - return //default behaviour only applies to true projectiles - - var/acc_mod = burst_accuracy[min(burst, burst_accuracy.len)] - var/disp_mod = dispersion[min(burst, dispersion.len)] - - if(one_handed_penalty) - if(!held_twohanded) - acc_mod += -CEILING(one_handed_penalty/2, 1) - disp_mod += one_handed_penalty*0.5 //dispersion per point of two-handedness - - //Accuracy modifiers - if(!isnull(accuracy_disabled)) - P.accuracy_disabled = accuracy_disabled - - P.accuracy_overall_modify *= 1 + (acc_mod / 100) - P.accuracy_overall_modify *= 1 - (user.get_accuracy_penalty() / 100) - P.dispersion = disp_mod - - //accuracy bonus from aiming - if (aim_targets && (target in aim_targets)) - //If you aim at someone beforehead, it'll hit more often. - //Kinda balanced by fact you need like 2 seconds to aim - //As opposed to no-delay pew pew - P.accuracy_overall_modify *= 1.3 - - // Some modifiers make it harder or easier to hit things. - for(var/datum/modifier/M in user.modifiers) - if(!isnull(M.accuracy)) - P.accuracy_overall_modify += 1 + (M.accuracy / 100) - 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 @@ -791,46 +599,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 @@ -851,21 +619,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) - . += SPAN_NOTICE("The safety is [check_safety() ? "on" : "off"].") - for(var/obj/item/gun_attachment/attachment as anything in attachments) - . += "It has [attachment] installed on its [attachment.attachment_slot].[attachment.can_detach ? "" : " It doesn't look like it can be removed."]" - /obj/item/gun/proc/switch_firemodes(mob/user) if(firemodes.len <= 1) return null @@ -874,7 +627,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) @@ -944,7 +697,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] @@ -971,163 +724,6 @@ /obj/item/gun/proc/get_ammo_ratio() return 0 -//* Attachments *// - -/** - * Check if we can attach an attachment - */ -/obj/item/gun/proc/can_install_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, silent) - if(!attachment.attachment_slot || !attachment_alignment[attachment.attachment_slot]) - if(!silent) - actor?.chat_feedback( - SPAN_WARNING("[attachment] won't fit anywhere on [src]!"), - target = src, - ) - return FALSE - if(attachment.attachment_type & attachment_type_blacklist) - if(!silent) - actor?.chat_feedback( - SPAN_WARNING("[attachment] doesn't work with [src]!"), - target = src, - ) - return FALSE - for(var/obj/item/gun_attachment/existing as anything in attachments) - if(existing.attachment_slot == attachment.attachment_slot) - if(!silent) - actor?.chat_feedback( - SPAN_WARNING("[src] already has [existing] installed on its [existing.attachment_slot]!"), - target = src, - ) - return FALSE - if(existing.attachment_type & attachment.attachment_type) - if(!silent) - actor?.chat_feedback( - SPAN_WARNING("[src]'s [existing] conflicts with [attachment]!"), - target = src, - ) - return FALSE - if(!attachment.fits_on_gun(src, actor, silent)) - return FALSE - return TRUE - -/** - * Called when a mob tries to uninstall an attachment - */ -/obj/item/gun/proc/user_install_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor) - if(actor) - if(actor.performer && actor.performer.is_in_inventory(attachment)) - if(!actor.performer.can_unequip(attachment, attachment.worn_slot)) - actor.chat_feedback( - SPAN_WARNING("[attachment] is stuck to your hand!"), - target = src, - ) - return FALSE - if(!install_attachment(attachment, actor)) - return FALSE - // todo: better sound - playsound(src, 'sound/weapons/empty.ogg', 25, TRUE, -3) - return TRUE - -/** - * Installs an attachment - * - * * This moves the attachment into the gun if it isn't already. - * * This does have default visible feedback for the installation. - * - * @return TRUE / FALSE on success / failure - */ -/obj/item/gun/proc/install_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, silent) - if(!can_install_attachment(attachment, actor, silent)) - return FALSE - - if(!silent) - actor?.visible_feedback( - target = src, - visible = SPAN_NOTICE("[actor.performer] attaches [attachment] to [src]'s [attachment.attachment_slot]."), - ) - if(attachment.loc != src) - attachment.forceMove(src) - - LAZYADD(attachments, attachment) - attachment.attached = src - attachment.on_attach(src) - attachment.update_gun_overlay() - on_attachment_install(attachment) - var/mob/holding_mob = worn_mob() - if(holding_mob) - attachment.register_attachment_actions(holding_mob) - return TRUE - -/** - * Called when a mob tries to uninstall an attachment - */ -/obj/item/gun/proc/user_uninstall_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, put_in_hands) - if(!attachment.can_detach) - actor?.chat_feedback( - SPAN_WARNING("[attachment] is not removable."), - target = src, - ) - return FALSE - var/obj/item/uninstalled = uninstall_attachment(attachment, actor) - if(put_in_hands && actor?.performer) - actor.performer.put_in_hands_or_drop(uninstalled) - else - var/atom/where_to_drop = drop_location() - ASSERT(where_to_drop) - uninstalled.forceMove(where_to_drop) - // todo: better sound - playsound(src, 'sound/weapons/empty.ogg', 25, TRUE, -3) - return TRUE - -/** - * Uninstalls an attachment - * - * * This does not move the attachment after uninstall; you have to do that. - * * This does not have default visible feedback for the uninstallation / removal. - * - * @return the /obj/item uninstalled - */ -/obj/item/gun/proc/uninstall_attachment(obj/item/gun_attachment/attachment, datum/event_args/actor/actor, silent, deleting) - ASSERT(attachment.attached == src) - var/mob/holding_mob = worn_mob() - if(holding_mob) - attachment.unregister_attachment_actions(holding_mob) - attachment.on_detach(src) - attachment.remove_gun_overlay() - attachment.attached = null - on_attachment_uninstall(attachment) - LAZYREMOVE(attachments, attachment) - return deleting ? null : attachment.uninstall_product_transform(src) - -/** - * Align an attachment overlay. - * - * @return TRUE / FALSE on success / failure - */ -/obj/item/gun/proc/align_attachment_overlay(obj/item/gun_attachment/attachment, image/appearancelike) - var/list/alignment = attachment_alignment?[attachment.attachment_slot] - if(!alignment) - return FALSE - appearancelike.pixel_x = (alignment[1] - attachment.align_x) - appearancelike.pixel_y = (alignment[2] - attachment.align_y) - return TRUE - -/** - * Called exactly once when an attachment is installed - * - * * Called before the attachment's on_attach() - */ -/obj/item/gun/proc/on_attachment_install(obj/item/gun_attachment/attachment) - PROTECTED_PROC(TRUE) - -/** - * Called exactly once when an attachment is uninstalled - * - * * Called after the attachment's on_detach() - */ -/obj/item/gun/proc/on_attachment_uninstall(obj/item/gun_attachment/attachment) - PROTECTED_PROC(TRUE) - //* Context *// /obj/item/gun/context_query(datum/event_args/actor/e_args) @@ -1136,6 +732,7 @@ .["remove-attachment"] = atom_context_tuple("Remove Attachment", image('icons/screen/radial/actions.dmi', "red-arrow-up"), 0, MOBILITY_CAN_USE) if(safety_state != GUN_NO_SAFETY) .["toggle-safety"] = atom_context_tuple("Toggle Safety", image(src), 0, MOBILITY_CAN_USE, TRUE) + #warn gun component detach /obj/item/gun/context_act(datum/event_args/actor/e_args, key) . = ..() @@ -1155,6 +752,26 @@ toggle_safety(e_args.performer) return TRUE +//* 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) + +//* Interaction *// + +/obj/item/gun/CtrlClick(mob/user) + . = ..() + if(user.is_holding(src)) + toggle_safety(user) + //* Rendering *// /obj/item/gun/update_icon(updates) diff --git a/code/modules/projectiles/guns/gun_attachment.dm b/code/modules/projectiles/guns/gun_attachment.dm index 4d97d0132d75..d09c9df23538 100644 --- a/code/modules/projectiles/guns/gun_attachment.dm +++ b/code/modules/projectiles/guns/gun_attachment.dm @@ -236,7 +236,7 @@ * * null */ /obj/item/gun_attachment/proc/set_attachment_actions_to(descriptor) - var/mob/worn_mob = attached.worn_mob() + var/mob/worn_mob = attached.get_worn_mob() if(worn_mob) unregister_attachment_actions(worn_mob) diff --git a/code/modules/projectiles/guns/gun_component.dm b/code/modules/projectiles/guns/gun_component.dm new file mode 100644 index 000000000000..6ca2fb70b7ce --- /dev/null +++ b/code/modules/projectiles/guns/gun_component.dm @@ -0,0 +1,127 @@ +//* 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_state = "stock" + + /// component slot + /// + /// * This is just a suggestion. + /// * The actual APIs used are agnostic of this value. + var/component_slot + /// Conflict flags + /// + /// * This is done with hard enforcement. + var/component_conflict = NONE + /// Component type. + /// + /// * Two of the same component will never be allowed to be put on the same gun. + /// * This defaults to the gun's typepath if unset. + var/component_type + + /// 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 + + /// The gun we are installed in. + var/obj/item/gun/installed + +/obj/item/gun_component/examine(mob/user, dist) + . = ..() + var/list/summarized = summarize_bullet_points() + if(!length(summarized)) + return + var/list/transformed = list() + for(var/string in summarized) + . += "
  • [string]
  • " + +//* 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_install(obj/item/gun/gun, datum/event_args/actor/actor, silent) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + if(hook_iteration_pre_fire) + RegisterSignal(gun, COMSIG_GUN_FIRING_PREFIRE, PROC_REF(on_firing_cycle_iteration)) + +/** + * called on detach + */ +/obj/item/gun_component/proc/on_uninstall(obj/item/gun/gun, datum/event_args/actor/actor, silent) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + if(hook_iteration_pre_fire) + UnregisterSignal(gun, COMSIG_GUN_FIRING_PREFIRE) + +//* Gun API - Hooks *// + +/** + * 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 + +//* Gun API - Actions *// + +/obj/item/gun_component/proc/use_power(joules) + return installed.modular_use_power(src, joules) + +/obj/item/gun_component/proc/use_checked_power(joules, reserve) + return installed.modular_use_checked_power(src, joules, reserve) + +//* Information *// + +/** + * Called to query the stat bullet points of this component + * + * @params + * * actor - (optional) actor data + * + * @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() + +/** + * Called to return our examine name injection. + */ +/obj/item/gun_component/proc/get_examine_fragment() + // todo: render as icon & name + var/use_name = name + var/list/summarized = summarize_bullet_points() + if(!length(summarized)) + return SPAN_TOOLTIP(desc, use_name) + var/list/transformed = list() + for(var/string in summarized) + transformed += "
  • [string]
  • " + var/use_tooltip = {" + [desc] + [jointext(transformed, "")] + "} + return SPAN_TOOLTIP(use_tooltip, use_name) 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..fb829b5db863 --- /dev/null +++ b/code/modules/projectiles/guns/gun_component/acceleration_coil.dm @@ -0,0 +1,25 @@ +//* 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." + icon = 'icons/modules/projectiles/components/acceleration_coil.dmi' + 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 impacts. + This does not have the intended effect on all projectiles. + "} + +/obj/item/gun_component/acceleration_coil/overcharger + name = "weapon acceleration coil (overcharger)" + desc = {" + A magnetic acceleration coil designed to overcharge a passing projectile. This has minimal effect + on projectiles not equipped to handle a charge. + "} + +// 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..0f26e62e6294 --- /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" + desc = "A basic cooling unit used in modular weaponry." + icon = 'icons/modules/projectiles/components/active_cooler.dmi' + 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. + "} + +// 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..8c7166d3e001 --- /dev/null +++ b/code/modules/projectiles/guns/gun_component/energy_handler.dm @@ -0,0 +1,41 @@ +//* 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" + desc = "A basic power regulator used in powered weaponry." + icon = 'icons/modules/projectiles/components/energy_handler.dmi' + component_slot = GUN_COMPONENT_ENERGY_HANDLER + +/obj/item/gun_component/energy_handler/slide_rack + name = "weapon energy handler (slide charging)" + desc = {" + An energy handler that requires an user to rack the weapon to perform a charge cycle. + "} + +/obj/item/gun_component/energy_handler/slide_rack/summarize_bullet_points(datum/event_args/actor/actor, range) + . = list() + var/keybind_render_unique_action = actor?.initiator.client?.print_keys_for_keybind_with_prefs_link(/datum/keybinding/item/unique_action) + . += "Requires racking the weapon via Unique Action ([keybind_render_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 ([keybind_render_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/slide_rack/active_reload + name = "weapon energy handler (synchronous slide charging)" + desc = {" + An energy handler that requires an user to rack the weapon at a specific point in the charge cycle. + "} + +/obj/item/gun_component/energy_handler/slide_rack/active_reload/summarize_bullet_points(datum/event_args/actor/actor, range) + . = list() + var/keybind_render_unique_action = actor?.initiator.client?.print_keys_for_keybind_with_prefs_link(/datum/keybinding/item/unique_action) + . += "Requires racking the weapon via Unique Action ([keybind_render_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 ([keybind_render_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." + +// TODO: This file is mostly stubs and WIPs. diff --git a/code/modules/projectiles/guns/gun_component/focusing_lens.dm b/code/modules/projectiles/guns/gun_component/focusing_lens.dm new file mode 100644 index 000000000000..ac3f67593983 --- /dev/null +++ b/code/modules/projectiles/guns/gun_component/focusing_lens.dm @@ -0,0 +1,37 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/obj/item/gun_component/focusing_lens + name = "weapon focusing lens" + desc = "A standard focusing lens used in energy weapons." + icon = 'icons/modules/projectiles/components/focusing_lens.dmi' + component_slot = GUN_COMPONENT_FOCUSING_LENS + +// TODO: This file is mostly stubs and WIPs. + +/obj/item/gun_component/focusing_lens/divide_by + name = "weapon focusing lens (linear multiplexer)" + desc = {" + A special focusing lens that multiplexes a passing particle beam. + "} + component_type = /obj/item/gun_component/focusing_lens/divide_by + + /// divides a beam to this number of parallel beams + var/divide_by = 1 + /// this is extremely dangerous and i advise you not to mess with it + /// * the default is enough to make this worth it as a damage upgrade + /// without making it too op. if you set this to a ridiculous value like 2, + /// i will replace your eyelids with limes. + var/cheat_factor = 1 + +/obj/item/gun_component/focusing_lens/divide_by/two + name = "weapon focusing lens (2-linear multiplexer)" + divide_by = 2 + cheat_factor = 1.15 + +/obj/item/gun_component/focusing_lens/divide_by/three + name = "weapon focusing lens (3-linear multiplexer)" + divide_by = 3 + cheat_factor = 1.3 + +#warn impl 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..aed212114827 --- /dev/null +++ b/code/modules/projectiles/guns/gun_component/internal_module.dm @@ -0,0 +1,44 @@ +//* 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." + icon = 'icons/modules/projectiles/components/internal_module.dmi' + component_slot = GUN_COMPONENT_INTERNAL_MODULE + +// 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 + */ +#warn test this +/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." + + /// 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_adjust += 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/particle_array.dm b/code/modules/projectiles/guns/gun_component/particle_array.dm new file mode 100644 index 000000000000..0bcf5077ab38 --- /dev/null +++ b/code/modules/projectiles/guns/gun_component/particle_array.dm @@ -0,0 +1,16 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/obj/item/gun_component/particle_array + name = "weapon particle array" + desc = "A nondescript particle array used in energy weapons." + icon = 'icons/modules/projectiles/components/particle_array.dmi' + component_slot = GUN_COMPONENT_PARTICLE_ARRAY + + /// base charge cost + var/charge_cost = /obj/item/cell/device/weapon::maxcharge / 24 + +/obj/item/gun_component/particle_array/proc/generate_projectile(datum/gun_firing_cycle/cycle) + return + +// TODO: This file is mostly stubs and WIPs. 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..c09d6528e036 --- /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" + desc = "A standard power interface used in energy-based weaponry." + icon = 'icons/modules/projectiles/components/power_unit.dmi' + component_slot = GUN_COMPONENT_POWER_UNIT + +// 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..f405a2f277f5 --- /dev/null +++ b/code/modules/projectiles/guns/gun_firing_cycle.dm @@ -0,0 +1,87 @@ +//* 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 + /// original target pixel x of tile + // todo: re-evaluate if this is needed. this is usually for detecting exact impact spots, but is + // kinda janky for what it is, though there might not be a better option. + var/original_tile_pixel_x + /// original target pixel y of tile + // todo: re-evaluate if this is needed. this is usually for detecting exact impact spots, but is + // kinda janky for what it is, though there might not be a better option. + var/original_tile_pixel_y + /// original target zone + var/original_target_zone + + //* 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 + + //* firing modifier args *// + //* this is where things like modular gun components will inject into. *// + /// for all iterations, this is the base dispersion + var/base_dispersion_adjust + /// for all iterations, this is the base angle adjust (pos = cw, neg = ccw); this is in degrees + var/base_angle_adjust + /// current GUN_FIRED_* to inject; this is used to fail a cycle from a component signal + /// by setting this on us. + var/next_firing_fail_result + /// 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); this is in degrees + var/next_angle_adjust + #warn hook + /// blackboard for modular gun components to use + var/list/blackboard + +/datum/gun_firing_cycle/proc/finish_iteration(result) + last_firing_result = result + ++cycle_iterations_fired + + next_dispersion_adjust = \ + next_angle_adjust = \ + next_firing_fail_result = \ + null diff --git a/code/modules/projectiles/gun_item_renderer.dm b/code/modules/projectiles/guns/gun_item_renderer.dm similarity index 93% rename from code/modules/projectiles/gun_item_renderer.dm rename to code/modules/projectiles/guns/gun_item_renderer.dm index a2cd3a4715fa..d98e3d3b375a 100644 --- a/code/modules/projectiles/gun_item_renderer.dm +++ b/code/modules/projectiles/guns/gun_item_renderer.dm @@ -10,7 +10,7 @@ * todo: better documentation */ /datum/gun_item_renderer - /// firemode state is taken into account + /// firemode render_key is appended, somewhere, to the overall state when generating states var/use_firemode = FALSE /** @@ -83,11 +83,16 @@ * otherwise we render -1 to -n, or -empty if empty (and use empty state is on) * * * firemode is not taken into account for empty state. + * * "[base]-[firemode]-[count]" */ /datum/gun_item_renderer/overlays + /// total count of overlays, from 1 to amount var/count + /// add "-empty" overlay when we're empty, instead of adding nothing var/use_empty + /// only use the n-th overlay, instead of adding 1 to n var/use_single + /// additionally, add an "-[firemode]" state for our firemode's render_key var/independent_firemode /datum/gun_item_renderer/overlays/render(obj/item/gun/gun, ammo_ratio, firemode_key) 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 ee5e5db521d6..e38c46dafa22 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 ae00b9653639..75391da16c6c 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 87% rename from code/modules/projectiles/guns/magnetic/magnetic.dm rename to code/modules/projectiles/guns/magnetic.dm index 8dffab58d8e4..9dcf05e64180 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. @@ -21,27 +28,31 @@ var/power_cost = 950 // Cost per fire, should consume almost an entire basic cell. var/power_per_tick // Capacitor charge per process(). Updated based on capacitor rating. + /// Render battery state. + /// + /// todo: impl + /// + /// * Uses MAGNETIC_RENDER_BATTERY_* enums + var/render_battery_overlay = MAGNETIC_RENDER_BATTERY_NEVER + /obj/item/gun/magnetic/Initialize(mapload) START_PROCESSING(SSobj, src) 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 +62,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 +86,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 +100,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 +155,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 +172,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 +207,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 +252,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/modular_guns.dm b/code/modules/projectiles/guns/modular_guns.dm deleted file mode 100644 index 4a7a0a128ab5..000000000000 --- a/code/modules/projectiles/guns/modular_guns.dm +++ /dev/null @@ -1,175 +0,0 @@ -//This will likely drive me insane, but fuck it. Let's give it a shot. -k22 -//This was heavily assisted by MoondancerPony -/obj/item/gun/energy/modular - name = "modular weapon" - desc = "You shouldn't be seeing this. Contact your local time-police station." - icon_state = "mod_pistol" - cell_type = /obj/item/cell/device/weapon - charge_cost = 120 - - var/max_components = 3 //How many components we can hold. - var/capacitor_rating = 0 //How good are the capacitors inside us? - var/laser_rating = 0 //How good are the lasers inside of us? - var/manipulator_rating = 0 //How good are the manipulators inside us? - var/assembled = 1 //Are we closed up? - var/max_burst_size = 5 //Don't let our maximum burst size get too high. - var/list/guncomponents = list() //Generate our list of components. - var/accepted_components = list( - /obj/item/stock_parts/capacitor/, - /obj/item/stock_parts/capacitor/adv, - /obj/item/stock_parts/capacitor/super, - /obj/item/stock_parts/micro_laser/, - /obj/item/stock_parts/micro_laser/high, - /obj/item/stock_parts/micro_laser/ultra, - /obj/item/stock_parts/manipulator/, - /obj/item/stock_parts/manipulator/nano, - /obj/item/stock_parts/manipulator/pico, - ) - //Excessively long because it won't accept subtypes for some reason! - - -/obj/item/gun/energy/modular/Initialize(mapload) - . = ..() - guncomponents = list() - guncomponents += new /obj/item/stock_parts/capacitor - guncomponents += new /obj/item/stock_parts/micro_laser - guncomponents += new /obj/item/stock_parts/manipulator - CheckParts() - FireModeModify() - -/obj/item/gun/energy/modular/proc/CheckParts() //What parts do we have inside us, and how good are they? - capacitor_rating = 0 - laser_rating = 0 - manipulator_rating = 0 - for(var/obj/item/stock_parts/capacitor/CA in guncomponents) - capacitor_rating += CA.rating - for(var/obj/item/stock_parts/micro_laser/ML in guncomponents) - laser_rating += ML.rating - for(var/obj/item/stock_parts/manipulator/MA in guncomponents) - manipulator_rating += MA.rating - FireModeModify() - -/obj/item/gun/energy/modular/attackby(obj/item/O, mob/user) - if(O.is_screwdriver()) - to_chat(user, "You [assembled ? "disassemble" : "assemble"] the gun.") - assembled = !assembled - playsound(src, O.tool_sound, 50, 1) - return - if(O.is_crowbar()) - if(assembled == 1) - to_chat(user, "Disassemble the [src] first!") - return - for(var/obj/item/I in guncomponents) - to_chat(user, "You remove the gun's components.") - playsound(src, O.tool_sound, 50, 1) - I.forceMove(get_turf(src)) - guncomponents.Remove(I) - CheckParts() - return - //Someone's attacking us, and it's not anything we have a special case for (i.e. a tool) - ..() - if(assembled) // can't put anything in - return - if(!(O.type in accepted_components))//check if we can accept it - to_chat(user, "You can't add this to [src]!") - return - if(guncomponents.len >= max_components) //We have too many componenets and can't fit more. - to_chat(user, "You can't add any more components!") - return - if(istype(O, /obj/item/stock_parts/capacitor) && capacitor_rating == 5) - to_chat(user, "You can't add any more capacitors!") - return - user.drop_item() - guncomponents += O - O.forceMove(src) - to_chat(user, "You add a component to the [src]") - CheckParts() - - -/obj/item/gun/energy/modular/proc/FireModeModify() //Check our laser, manipulator, and capacitor ratings, adjust stun and lethal firemodes depending on laser / manipulator rating and burst size depending on capacitors. - //check our lethal and stun ratings depending on laser and manipulator rating. - var/burstmode = capacitor_rating - var/beammode - var/beammode_lethal - var/chargecost - var/chargecost_lethal - - if(laser_rating >= 15) - beammode_lethal = /obj/projectile/beam/sniper - beammode = /obj/projectile/beam/stun - chargecost = 300 - chargecost_lethal = 600 - else if(laser_rating >= 10) - beammode_lethal = /obj/projectile/beam/xray - beammode = /obj/projectile/beam/stun - chargecost = 300 - chargecost_lethal = 200 - else if(laser_rating == 8 && manipulator_rating == 5) //very specific set of combinations. No, you can't make a pulse rifle. Sorry research. - beammode_lethal = /obj/projectile/beam/heavylaser - beammode = /obj/projectile/beam/stun - chargecost = 300 - chargecost_lethal = 600 - else if(laser_rating >= 5) - beammode_lethal = /obj/projectile/beam/midlaser - beammode = /obj/projectile/beam/stun/med - chargecost = 180 - chargecost_lethal = 240 - else if(laser_rating < 5) - beammode_lethal = /obj/projectile/beam/weaklaser - beammode = /obj/projectile/beam/stun/weak - chargecost = 100 - chargecost_lethal = 200 - - firemodes = list( - new /datum/firemode(src, list(mode_name="stun", projectile_type=beammode, charge_cost = chargecost)), - new /datum/firemode(src, list(mode_name="lethal", projectile_type=beammode_lethal, charge_cost = chargecost_lethal)), - new /datum/firemode(src, list(mode_name="[burstmode] shot stun", projectile_type=beammode, charge_cost = chargecost, burst = burstmode)), - new /datum/firemode(src, list(mode_name="[burstmode] shot lethal", projectile_type=beammode_lethal, charge_cost = chargecost_lethal, burst = burstmode)), - ) - -/obj/item/gun/energy/modular/load_ammo(var/obj/item/C, mob/user) - if(istype(C, cell_type)) - if(self_recharge || battery_lock) - to_chat(user, "[src] does not have a battery port.") - return - 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, 10)) - user.remove_from_mob(P) - power_supply = P - P.loc = src - user.visible_message("[user] inserts [P] into [src].", "You insert [P] into [src].") - playsound(src.loc, 'sound/weapons/flipblade.ogg', 50, 1) - update_icon() - update_worn_icon() - return - -/obj/item/gun/energy/modular/pistol - name = "modular pistol" - icon_state = "mod_pistol" - max_components = 6 - desc = "A bulky modular pistol frame. This only only accepts six parts." - origin_tech = list(TECH_COMBAT = 3, TECH_MAGNET = 3) - burst_delay = 2 - -/obj/item/gun/energy/modular/carbine - name = "modular carbine" - icon_state = "mod_carbine" - max_components = 8 - desc = "A modular version of the standard laser carbine. This one can hold 8 components." - origin_tech = list(TECH_COMBAT = 4, TECH_MAGNET = 3, TECH_MATERIAL = 3) - burst_delay = 2 - -/obj/item/gun/energy/modular/cannon - name = "modular cannon" - icon_state = "mod_cannon" - max_components = 14 - desc = "Say hello, to my little friend!" - heavy = TRUE - one_handed_penalty = 4 //dual wielding = no. - cell_type = /obj/item/cell //We're bigger. We can use much larger power cells. - origin_tech = list(TECH_COMBAT = 6, TECH_MAGNET = 6, TECH_MATERIAL = 5, TECH_BLUESPACE = 4) //its a damn cannon capable of holding a huge amount of parts. - burst_delay = 4 //preventing extreme silliness. 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-alter.dm b/code/modules/projectiles/projectile/projectile-alter.dm new file mode 100644 index 000000000000..f42b699a581f --- /dev/null +++ b/code/modules/projectiles/projectile/projectile-alter.dm @@ -0,0 +1,23 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Alteration API. Called on projectiles by things like modular weapons + * to modify them. + */ + +/** + * Superheats a projectile. + * + * @return joules - amount consumed. null for rejected. + */ +/obj/projectile/proc/alter_superheat(joules) + return + +/** + * Overcharges a projectile. + * + * @return joules - amount consumed. null for rejected. + */ +/obj/projectile/proc/alter_overcharge(joules) + return diff --git a/code/modules/projectiles/projectile/projectile-lazy_helpers.dm b/code/modules/projectiles/projectile/projectile-lazy_helpers.dm new file mode 100644 index 000000000000..6ff385bffb61 --- /dev/null +++ b/code/modules/projectiles/projectile/projectile-lazy_helpers.dm @@ -0,0 +1,32 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Imprint data from a clickchain + * + * Set 'use_firer' to not use the performer as the firer. This affects where the projectile raycasts from. + */ +/obj/projectile/proc/lazy_imprint_from_clickchain(datum/event_args/actor/clickchain/clickchain, clickchain_flags, atom/use_firer) + clickchain.unpack_click_params() + + original_target = clickchain.target + + //! legacy + firer = use_firer ? use_firer : clickchain.performer + def_zone = clickchain.legacy_get_target_zone() + p_x = clickchain.click_params_tile_px + p_x = clickchain.click_params_tile_py + //! end + + set_angle(clickchain.resolve_click_angle(use_firer)) + +/** + * Fire from a clickchain + * + * Set 'use_firer' to not use the performer as the firer. This affects where the projectile raycasts from. + */ +/obj/projectile/proc/lazy_fire_from_clickchain(datum/event_args/actor/clickchain/clickchain, clickchain_flags, atom/use_firer) + if(isnull(use_firer)) + use_firer = clickchain.performer + lazy_imprint_from_clickchain(clickchain, clickchain_flags) + fire() diff --git a/code/modules/projectiles/projectile/projectile.dm b/code/modules/projectiles/projectile/projectile.dm index 489ec2c9828c..d7af7a038a3c 100644 --- a/code/modules/projectiles/projectile/projectile.dm +++ b/code/modules/projectiles/projectile/projectile.dm @@ -95,6 +95,11 @@ //* Combat - Effects *// + /// multiplier to projectile effects + /// + /// * Set when splitting into submunitions. + /// * It's the projectile effect datum's duty to read this. + var/projectile_effect_multiplier = 1 /// projectile effects /// /// * this is a static typelist on this typepath @@ -110,14 +115,12 @@ VAR_PROTECTED/list/additional_projectile_effects //* Configuration *// - /// Projectile type bitfield; set all that is relevant var/projectile_type = NONE /// Impact ground on expiry (range, or lifetime) var/impact_ground_on_expiry = FALSE //* Physics - Configuration *// - /// speed, in pixels per second var/speed = 25 * WORLD_ICON_SIZE /// are we a hitscan projectile? @@ -151,7 +154,6 @@ var/homing_offset_y = 0 //* Physics - Tracers *// - /// tracer /datum/point's var/list/tracer_vertices /// first point is a muzzle effect @@ -162,7 +164,6 @@ var/tracer_duration = 5 //* Physics - State *// - /// paused? if so, we completely get passed over during processing var/paused = FALSE /// currently hitscanning @@ -221,8 +222,34 @@ // optimally physics loop should handle tracking for stuff like animations, not require on hit processing to check turfs var/turf/trajectory_moving_to - //* Targeting *// + //* Submunitions *// + //* While projectile has procs to handle this, these vars are used automatically if 'submunitions' is set. *// + /// Submunitions to use by default on fire(). Either a number to split self into n fragments. + var/submunitions + /// Delete ourselves after splitting. + var/submunitions_only = FALSE + /// Typepath to use for submunitions. Defaults to self, if not specified. + var/submunition_type + /// Default submunition dispersion. + var/submunition_dispersion = 0 + /// Default submunition dispersion gaussian mode off + var/submunition_uniform_disperson = FALSE + /// Default submunition linear spread + var/submunition_linear_spread = 0 + /// Default submunition distribution enabled? Distrbution means that our stats + /// are passed to our submunitions, potentially overwriting their own stats. + /// + /// * This only really makes sense under [submunitions_only] mode as otherwise + /// the calculations will be all off / weird, as distribution won't actually affect our + /// own stats, resulting in effectively a multiplied effectiveness out of nowhere. + var/submunition_distribution = FALSE + /// Default submunition distribution multiplier. Higher than 1 will give 'unfair' + /// amounts of power to submunitions, below 1 dampens. + var/submunition_distribution_mod = 1 + /// Overwrite target projectile instead of adding? + var/submunition_distribution_overwrite = TRUE + //* Targeting *// /// Originally clicked target var/atom/original_target @@ -275,22 +302,10 @@ var/dispersion = 0.0 - // Sub-munitions. Basically, multi-projectile shotgun, rather than pellets. - var/use_submunitions = FALSE - var/only_submunitions = FALSE // Will the projectile delete itself after firing the submunitions? - var/list/submunitions = list() // Assoc list of the paths of any submunitions, and how many they are. [projectilepath] = [projectilecount]. - var/submunition_spread_max = 30 // Divided by 10 to get the percentile dispersion. - var/submunition_spread_min = 5 // Above. - /// randomize spread? if so, evenly space between 0 and max on each side. - var/submunition_constant_spread = FALSE - var/force_max_submunition_spread = FALSE // Do we just force the maximum? - var/spread_submunition_damage = FALSE // Do we assign damage to our sub projectiles based on our main projectile damage? - - //? 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] @@ -384,12 +399,38 @@ expire() -/obj/projectile/proc/fire(set_angle_to, atom/direct_target, no_source_check) - if(only_submunitions) // refactor projectiles whwen holy shit this is awful lmao - // todo: this should make a muzzle flash - qdel(src) - return +/** + * Fires a projectile. + * + * todo: reverify no_source_check; it probably shouldn't be in here. maybe have a proc + * for giving the firer immunity? + * + * @params + * * set_angle_to - if set, overrides our angle to this angle + * * direct_target - just hit this target and return + * * no_source_check - allow hitting the firer + * * on_submunition_ready - (optional) callback to execute when a submunition is readied, right before it's fire()'d. + */ +/obj/projectile/proc/fire(set_angle_to, atom/direct_target, no_source_check, datum/callback/on_submunition_ready) + // don't fire multiple times + ASSERT(!fired) + // set angle if needed + if(isnum(set_angle_to)) + set_angle(set_angle_to) + // handle submunitions - this can qdelete ourselves! + var/list/obj/projectile/submunitions = submunitions ? split_into_default_submunitions(TRUE) : null + if(!QDELETED(src)) + launch(direct_target, no_source_check) + for(var/obj/projectile/submunition as anything in submunitions) + submunition.launch(direct_target, no_source_check) + +/** + * Handles actually launching a projectile. + */ +/obj/projectile/proc/launch(atom/direct_target, no_source_check) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) // setup impact checking impacted = list() // handle direct hit @@ -413,9 +454,6 @@ buckle_iterating = cast_for_next.buckled else break - // set angle if needed - if(isnum(set_angle_to)) - set_angle(set_angle_to) // setup physics setup_physics() @@ -443,6 +481,7 @@ physics_iteration(WORLD_ICON_SIZE, SSprojectiles.wait) //Spread is FORCED! +// todo: obliterate /obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) var/turf/curloc = get_turf(source) var/turf/targloc = get_turf(target) @@ -478,6 +517,7 @@ stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") qdel(src) +// todo: obliterate /proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params) var/list/mouse_control = params2list(params) var/p_x = 0 @@ -538,58 +578,16 @@ return 0 /** - * i hate everything - * - * todo: refactor guns - * projectiles - * and everything else - * - * i am losing my fucking mind - * this shouldn't have to fucking exist because the ammo casing and/or gun should be doing it - * and submunitions SHOULDNT BE HANDLED HERE!! + * todo: annihilate this */ /obj/projectile/proc/launch_projectile_common(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) original_target = target def_zone = check_zone(target_zone) firer = user - if(use_submunitions && submunitions.len) - var/temp_min_spread = 0 - if(force_max_submunition_spread) - temp_min_spread = submunition_spread_max - else - temp_min_spread = submunition_spread_min - - var/damage_override = null - - if(spread_submunition_damage) - damage_override = damage_force - if(nodamage) - damage_override = 0 - - var/projectile_count = 0 - - for(var/proj in submunitions) - projectile_count += submunitions[proj] - - damage_override = round(damage_override / max(1, projectile_count)) - - for(var/path in submunitions) - var/amt = submunitions[path] - for(var/count in 1 to amt) - var/obj/projectile/SM = new path(get_turf(loc)) - SM.shot_from = shot_from - SM.silenced = silenced - if(!isnull(damage_override)) - SM.damage_force = damage_override - if(submunition_constant_spread) - SM.dispersion = 0 - var/calculated = angle + round((count / amt - 0.5) * submunition_spread_max, 1) - SM.launch_projectile(target, target_zone, user, params, calculated) - else - SM.dispersion = rand(temp_min_spread, submunition_spread_max) / 10 - SM.launch_projectile(target, target_zone, user, params, angle_override) - +/** + * todo: annihilate this + */ /obj/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) var/direct_target if(get_turf(target) == get_turf(src)) @@ -599,14 +597,9 @@ 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) - +/** + * todo: annihilate this + */ /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)) @@ -1176,6 +1169,113 @@ var/malus = dmult >= 1 ? ((1 / dmult) ** tdiff * 10) : (10 * ((1 / dmult) / (1 + tdiff))) src.damage_force = clamp(src.damage_force - malus, src.damage_force * 0.5, src.damage_force) +//* Submunitions *// + +/** + * This can qdel() ourselves! + */ +/obj/projectile/proc/split_into_default_submunitions(fire_immediately, datum/callback/on_submunition_ready) + . = split_into_submunitions( + submunitions, + submunition_type || type, + submunition_dispersion, + submunition_uniform_disperson, + submunition_linear_spread, + submunition_distribution, + submunition_distribution_mod, + submunition_distribution_overwrite, + fire_immediately, + on_submunition_ready, + ) + if(submunitions_only) + qdel(src) + +/** + * @params + * * amount - amount to use + * * path - typepath to use + * * dispersion - angular dispersion. uniform min/max, or standard deviation if gaussian. + * * uniform_dispersion - use deterministic instead of gaussian for dispersion + * * linear_spread - spread apart this many pixels over every projectile + * * distribute - distribute our stats across submunitions? + * * distribute_mod - 2 = each pellet is 2x stronger than it should be in a fair divide, 0.5 = 50% as strong, etc. + * * distribute_overwrite - overwrite stats of the split submunitions instead of adding. + * * fire_immediately - fire the split shots. + * * on_submunition_ready - (optional) callback to execute when a submunition is readied, right before it's fire()'d. + * The callback is executed asynchronously. + * + * @return list() of submunitions + */ +/obj/projectile/proc/split_into_submunitions(amount, path, dispersion, uniform_dispersion, linear_spread, distribute, distribute_mod, distribute_overwrite, fire_immediately, datum/callback/on_submunition_ready) + // we must be fired; otherwise, things don't work right. + ASSERT(fired) + . = list() + // halve it as we're going in both directions, and artificially multiply by 10 to be + // divided out after the rand to boost randomness by a slight bit as rand only returns + // whole numbers. + linear_spread *= 0.5 * 10 + // artificially multiply by 10 to boost randomness + dispersion *= 10 + var/px_rand_mul = calculated_dy + var/py_rand_mul = calculated_dx + for(var/iter in 1 to amount) + var/obj/projectile/split = new path + split.imprint_from_supermunition(src, amount, distribute, distribute_mod, distribute_overwrite) + var/our_linear_spread = rand(-linear_spread, linear_spread) * 0.1 + var/our_angle_mod = uniform_dispersion ? rand(-dispersion, dispersion) * 0.1 : gaussian(0, dispersion) + split.pixel_x = pixel_x + px_rand_mul * our_linear_spread + split.pixel_y = pixel_y + py_rand_mul * our_linear_spread + split.forceMove(loc) + split.set_angle(angle + our_angle_mod) + on_submunition_ready?.InvokeAsync(split) + if(fire_immediately) + split.fire() + . += split + +/** + * @params + * * parent - what we're splitting from + * * split_count - the number of projectiles being split into. we are 1 of this. + * * distribute - distribute parent's stats on ourselves? + * * distribute_mod - 2 = each pellet is 2x stronger than it should be in a fair divide, 0.5 = 50% as strong, etc. + * This will never make a submunition more powerful than if the overall divisor was 1. + * * distribute_overwrite - overwrite stats of the split submunitions instead of adding. + */ +/obj/projectile/proc/imprint_from_supermunition(obj/projectile/parent, split_count, distribute, distribute_mod, distribute_overwrite) + SHOULD_CALL_PARENT(TRUE) + + //! legacy - clone variables + shot_from = parent.shot_from + silenced = parent.silenced + firer = parent.firer + //! end + + // share impacted data + impacted = parent.impacted?.Copy() + // distrbute if needed + if(distribute && distribute_mod) + var/effective_multiplier = 1 / (split_count / min(distribute_mod, split_count)) + // todo: how to handle split damagetypes? + if(distribute_overwrite) + damage_type = parent.damage_type + damage_force = parent.damage_force * effective_multiplier + damage_flag = parent.damage_flag + damage_tier = parent.damage_tier + projectile_effect_multiplier = parent.projectile_effect_multiplier * effective_multiplier + base_projectile_effects = parent.base_projectile_effects + additional_projectile_effects = parent.additional_projectile_effects + else + damage_force += parent.damage_force * effective_multiplier + damage_tier = max(damage_tier, parent.damage_tier) + // todo: proper collation for effects, not just an add and multiplier, maybe? + projectile_effect_multiplier = max(projectile_effect_multiplier, parent.projectile_effect_multiplier * effective_multiplier) + if(parent.base_projectile_effects) + LAZYINITLIST(base_projectile_effects) + base_projectile_effects += parent.base_projectile_effects + if(parent.additional_projectile_effects) + LAZYINITLIST(additional_projectile_effects) + additional_projectile_effects += parent.additional_projectile_effects + //* Targeting *// /** diff --git a/code/modules/projectiles/projectile/projectile_effect/electrical_probe.dm b/code/modules/projectiles/projectile/projectile_effect/electrical_probe.dm new file mode 100644 index 000000000000..4e2a228853e9 --- /dev/null +++ b/code/modules/projectiles/projectile/projectile_effect/electrical_probe.dm @@ -0,0 +1,9 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Electrical probes; hits the target with an electrical stun probe + */ +/datum/projectile_effect/electrical_probe + +#warn impl diff --git a/code/modules/projectiles/projectile/projectile_effect/electrical_stun.dm b/code/modules/projectiles/projectile/projectile_effect/electrical_stun.dm new file mode 100644 index 000000000000..4d0c6607cdb4 --- /dev/null +++ b/code/modules/projectiles/projectile/projectile_effect/electrical_stun.dm @@ -0,0 +1,9 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Electrical stuns; releases a shock on impact. + */ +/datum/projectile_effect/electrical_stun + +#warn impl 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/bullet/bullets.dm b/code/modules/projectiles/projectile/subtypes/bullet/bullets.dm index e5eb2bb37682..291ba5f6e15e 100644 --- a/code/modules/projectiles/projectile/subtypes/bullet/bullets.dm +++ b/code/modules/projectiles/projectile/subtypes/bullet/bullets.dm @@ -406,6 +406,8 @@ if(prob(10) && (efficiency >= 0.9)) L.afflict_stun(2 SECONDS) L.Confuse(1) + if(L.throwing) + continue L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10) /* Incendiary */ 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/scatter.dm b/code/modules/projectiles/projectile/subtypes/scatter.dm index d1eccc7dd578..08df3d73f1ba 100644 --- a/code/modules/projectiles/projectile/subtypes/scatter.dm +++ b/code/modules/projectiles/projectile/subtypes/scatter.dm @@ -7,166 +7,51 @@ icon = 'icons/obj/projectiles.dmi' icon_state = "bullet" - use_submunitions = TRUE - - damage_force = 8 - spread_submunition_damage = TRUE - only_submunitions = TRUE - range = WORLD_ICON_SIZE * 0 // Immediately deletes itself after firing, as its only job is to fire other projectiles. - - submunition_spread_max = 30 - submunition_spread_min = 2 + submunitions_only = TRUE + submunition_distribution = TRUE +#warn damage tiers for these +/obj/projectile/scatter/flak_shotgun + damage_force = 24 + submunition_dispersion = 17.5 submunitions = list( - /obj/projectile/bullet/pellet/shotgun/flak = 3 - ) + /obj/projectile/bullet/pellet/shotgun/flak = 3, + ) -//Spread Shot /obj/projectile/scatter/heavy_shotgun - damage_force = 15 - - submunition_spread_max = 100 - submunition_spread_min = 30 - - submunitions = list( - /obj/projectile/bullet/pellet/heavy_shotgun = 5 - ) - -/obj/projectile/scatter/heavy_shotgun/accurized - damage_force = 15 - - submunition_spread_max = 30 - submunition_spread_min = 10 - + damage_force = 60 + submunition_dispersion = 30 submunitions = list( - /obj/projectile/bullet/pellet/heavy_shotgun = 5 - ) + /obj/projectile/bullet/pellet/heavy_shotgun = 5, + ) /obj/projectile/scatter/heavy_shotgun/silver - damage_force = 15 - - submunition_spread_max = 30 - submunition_spread_min = 10 - submunitions = list( - /obj/projectile/bullet/pellet/heavy_shotgun/silver = 5 - ) + /obj/projectile/bullet/pellet/heavy_shotgun/silver = 5, + ) -//Custom knockback buckshot variant for Grit. /obj/projectile/scatter/heavy_shotgun/grit - submunitions = list( - /obj/projectile/bullet/pellet/heavy_shotgun/grit = 5 - ) + /obj/projectile/bullet/pellet/heavy_shotgun/grit = 5, + ) -//Energy Scatter /obj/projectile/scatter/laser - submunition_spread_max = 30 - spread_submunition_damage = FALSE - submunition_constant_spread = TRUE - - submunitions = list( - /obj/projectile/beam/blaster/pellet = 6 - ) - -/obj/projectile/scatter/laser/weak - damage_force = 15 - - submunitions = list( - /obj/projectile/beam/weaklaser = 3 - ) - -/obj/projectile/scatter/laser/pulse //Badmin only. - damage_force = 100 - armor_penetration = 100 - submunitions = list( - /obj/projectile/beam/pulse = 3 - ) - -/obj/projectile/scatter/gamma - damage_force = 10 - submunition_spread_max = 60 - submunition_spread_min = 30 + damage_force = 75 + submunition_dispersion = 22.5 submunitions = list( - /obj/projectile/beam/gamma = 3 - ) - -/obj/projectile/scatter/laser/heavylaser - damage_force = 60 - armor_penetration = 30 - submunitions = list( - /obj/projectile/beam/heavylaser = 1 //nope - ) - -/obj/projectile/scatter/laser/heavylaser/cannon - damage_force = 80 - armor_penetration = 50 - submunitions = list( - /obj/projectile/beam/heavylaser/cannon = 1 //haha no - ) - -/obj/projectile/scatter/stun - submunition_spread_max = 70 - submunition_spread_min = 30 - submunitions = list( - /obj/projectile/beam/stun = 2 - ) - fire_sound = 'sound/weapons/Taser.ogg' - nodamage = 1 - agony = 40 - -/obj/projectile/scatter/stun/weak - submunitions = list( - /obj/projectile/beam/stun/weak = 2 - ) - agony = 20 - -/obj/projectile/scatter/stun/electrode - submunitions = list( - /obj/projectile/energy/electrode = 1 - ) - agony = 55 + /obj/projectile/beam/blaster/pellet = 6, + ) /obj/projectile/scatter/ion - damage_force = 0 - - submunition_spread_max = 60 - submunition_constant_spread = TRUE - + submunition_dispersion = 30 submunitions = list( - /obj/projectile/bullet/shotgun/ion = 3 - ) + /obj/projectile/bullet/shotgun/ion = 3, + ) /obj/projectile/scatter/excavation - damage_force = 2 //mining tool - submunition_spread_max = 80 - submunition_spread_min = 40 + damage_force = 10 + submunition_dispersion = 30 fire_sound = 'sound/weapons/weaponsounds_laserweak.ogg' submunitions = list( - /obj/projectile/beam/excavation = 2 - ) - -/obj/projectile/scatter/shock - damage_force = 25 - submunition_spread_max = 80 - submunition_spread_min = 20 - submunitions = list( - /obj/projectile/beam/shock = 6 - ) - -/obj/projectile/scatter/energy_net - submunition_spread_max = 80 - submunition_spread_min = 30 - submunitions = list( - /obj/projectile/beam/energy_net = 2 - ) - nodamage = 1 - agony = 5 - -/obj/projectile/scatter/phase - damage_force = 5 - submunition_spread_max = 70 - submunition_spread_min = 30 - submunitions = list( - /obj/projectile/energy/phase/heavy = 4 - ) + /obj/projectile/beam/excavation = 3, + ) 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/projectiles/targeting/targeting_triggers.dm b/code/modules/projectiles/targeting/targeting_triggers.dm index b4c6c1c9a41d..f301855f1a1d 100644 --- a/code/modules/projectiles/targeting/targeting_triggers.dm +++ b/code/modules/projectiles/targeting/targeting_triggers.dm @@ -23,6 +23,6 @@ owner.visible_message("\The [owner] pulls the trigger reflexively!") var/obj/item/gun/G = aiming_with if(istype(G)) - INVOKE_ASYNC(G, TYPE_PROC_REF(/obj/item/gun, Fire), aiming_at, owner, null, null, TRUE) + G.start_firing_cycle_async(owner, get_centered_entity_angle(owner, aiming_at), GUN_FIRING_BY_REFLEX, null, aiming_at, new /datum/event_args/actor(owner)) locked = 0 lock_time = world.time+10 diff --git a/code/modules/research/designs/weapons.dm b/code/modules/research/designs/weapons.dm index 13e5ecb0d67d..65d0c686ac3f 100644 --- a/code/modules/research/designs/weapons.dm +++ b/code/modules/research/designs/weapons.dm @@ -485,351 +485,6 @@ materials_base = list(MAT_STEEL = 5000, MAT_GLASS = 5000, MAT_URANIUM = 2000, MAT_PHORON = 2000, MAT_DIAMOND = 500) build_path = /obj/item/ammo_casing/microbattery/combat/stripper -/datum/prototype/design/science/modweapon - category = DESIGN_CATEGORY_MUNITIONS - abstract_type = /datum/prototype/design/science/modweapon - -/datum/prototype/design/science/modweapon/generate_name(template) - return "Modular weapon prototype ([..()])" - -/datum/prototype/design/science/modweapon/basic - design_name = "modular energy pistol" - id = "modpistol" - req_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_POWER = 3, TECH_COMBAT = 3) - materials_base = list(MAT_STEEL = 5000, MAT_GLASS = 5000, MAT_SILVER = 3000) - build_path = /obj/item/gun/energy/modular/basic - -/* - -/datum/prototype/design/science/modweapon/adv - design_name = "advanced modular energy pistol" - id = "advmodpistol" - req_tech = list(TECH_MATERIAL = 6, TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5) - materials_base = list(MAT_STEEL = 6000, MAT_GLASS = 6000, MAT_SILVER = 3000, MAT_GOLD = 2000, MAT_URANIUM = 3000) - build_path = /obj/item/gun/energy/modular/advanced - -*/ - -/datum/prototype/design/science/modweapon/carbine - design_name = "modular energy carbine" - id = "modcarbine" - req_tech = list(TECH_MATERIAL = 6, TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_URANIUM = 2000, MAT_GOLD = 4000, MAT_DIAMOND = 500) - build_path = /obj/item/gun/energy/modular/carbine - -/datum/prototype/design/science/modweapon/rifle - design_name = "modular energy rifle" - id = "modrifle" - req_tech = list(TECH_MATERIAL = 6, TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5) - materials_base = list(MAT_STEEL = 12000, MAT_GLASS = 12000, MAT_SILVER = 6000, MAT_GOLD = 6000, MAT_URANIUM = 4000, MAT_DIAMOND = 500) - build_path = /obj/item/gun/energy/modular/rifle - -/datum/prototype/design/science/modweapon/tririfle - design_name = "tri-core modular energy rifle" - id = "threemodrifle" - req_tech = list(TECH_MATERIAL = 7, TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5) - materials_base = list(MAT_STEEL = 12000, MAT_GLASS = 12000, MAT_SILVER = 6000, MAT_GOLD = 6000, MAT_URANIUM = 4000, MAT_DIAMOND = 1500) - build_path = /obj/item/gun/energy/modular/rifle/tribeam - -/* - -/datum/prototype/design/science/modweapon/compact - design_name = "compact modular energy pistol" - id = "modcompact" - req_tech = list(TECH_MATERIAL = 7, TECH_MAGNET = 6, TECH_POWER = 6, TECH_COMBAT = 5) - materials_base = list(MAT_STEEL = 5000, MAT_GLASS = 5000, MAT_DIAMOND = 3000) - build_path = /obj/item/gun/energy/modular/compact - -/datum/prototype/design/science/modweapon/scatter - design_name = "modular energy scattergun" - id = "modscatter" - req_tech = list(TECH_MATERIAL = 7, TECH_MAGNET = 6, TECH_POWER = 6, TECH_COMBAT = 5, TECH_ILLEGAL = 4) - materials_base = list(MAT_STEEL = 12000, MAT_GLASS = 12000, MAT_SILVER = 6000, MAT_GOLD = 6000, MAT_URANIUM = 4000, MAT_DIAMOND = 2000) - build_path = /obj/item/gun/energy/modular/rifle/scatter - -/datum/prototype/design/science/modweapon/cannon - design_name = "modular energy cannon" - id = "modcannon" - req_tech = list(TECH_MATERIAL = 6, TECH_MAGNET = 6, TECH_POWER = 6, TECH_COMBAT = 6, TECH_ENGINEERING = 6) - materials_base = list(MAT_STEEL = 12000, MAT_GLASS = 12000, MAT_SILVER = 6000, MAT_GOLD = 6000, MAT_URANIUM = 4000, MAT_DIAMOND = 4000) - build_path = /obj/item/gun/energy/modular/cannon - -*/ - -/datum/prototype/design/science/modweapon/nuclear - design_name = "modular AEG" - id = "modAEG" - req_tech = list(TECH_MATERIAL = 6, TECH_MAGNET = 6, TECH_POWER = 6, TECH_COMBAT = 6, TECH_ENGINEERING = 6, TECH_BLUESPACE = 4) - materials_base = list(MAT_STEEL = 12000, MAT_GLASS = 12000, MAT_SILVER = 6000, MAT_LEAD = 20000, MAT_URANIUM = 20000, MAT_DIAMOND = 4000) - build_path = /obj/item/gun/energy/modular/nuke - -/datum/prototype/design/science/modweaponnodule - category = DESIGN_CATEGORY_MUNITIONS - subcategory = DESIGN_SUBCATEGORY_PARTS - abstract_type = /datum/prototype/design/science/modweaponnodule - -/datum/prototype/design/science/modweaponnodule/generate_name(template) - return "Modular weapon module design ([..()])" - -/datum/prototype/design/science/modweaponnodule/stunmedium - design_name = "stun medium" - id = "stunmedium" - req_tech = list(TECH_MAGNET = 3, TECH_POWER = 3, TECH_COMBAT = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_GOLD = 4000) - build_path = /obj/item/modularlaser/lasermedium/stun - -/datum/prototype/design/science/modweaponnodule/stunweak - design_name = "weak stun medium" - id = "stunweak" - req_tech = list(TECH_MAGNET = 1, TECH_POWER = 2, TECH_COMBAT = 1) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000) - build_path = /obj/item/modularlaser/lasermedium/stun/weak - -/* - -/datum/prototype/design/science/modweaponnodule/netmedium - design_name = "net projector medium" - id = "netmedium" - req_tech = list(TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_GOLD = 4000, MAT_URANIUM = 2000) - build_path = /obj/item/modularlaser/lasermedium/net - -*/ - -/datum/prototype/design/science/modweaponnodule/electrode - design_name = "electrode projector tube" - id = "electrodetube" - req_tech = list(TECH_MAGNET = 2, TECH_POWER = 3, TECH_COMBAT = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000) - build_path = /obj/item/modularlaser/lasermedium/electrode - -/datum/prototype/design/science/modweaponnodule/laser - design_name = "laser medium" - id = "lasermedium" - req_tech = list(TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_URANIUM = 2000, MAT_SILVER = 4000) - build_path = /obj/item/modularlaser/lasermedium/laser - -/datum/prototype/design/science/modweaponnodule/weaklaser - design_name = "low-power laser medium" - id = "weaklaser" - req_tech = list(TECH_MAGNET = 3, TECH_POWER = 3, TECH_COMBAT = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000) - build_path = /obj/item/modularlaser/lasermedium/laser/weak - -/* - -/datum/prototype/design/science/modweaponnodule/sniper - design_name = "laser sniper medium" - id = "sniperlaser" - req_tech = list(TECH_MAGNET = 5, TECH_POWER = 6, TECH_COMBAT = 4) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_URANIUM = 2000, MAT_DIAMOND = 10) - build_path = /obj/item/modularlaser/lasermedium/laser/sniper - -/datum/prototype/design/science/modweaponnodule/heavylaser - design_name = "heavy laser medium" - id = "heavylasermedium" - req_tech = list(TECH_MAGNET = 6, TECH_POWER = 6, TECH_COMBAT = 5, TECH_ILLEGAL = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_URANIUM = 2000, MAT_GOLD = 4000, MAT_DIAMOND = 500) - build_path = /obj/item/modularlaser/lasermedium/laser/heavy - -/datum/prototype/design/science/modweaponnodule/cannonmedium - design_name = "cannon beam medium" - id = "cannonmedium" - req_tech = list(TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5, TECH_ILLEGAL = 5) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_URANIUM = 2000, MAT_GOLD = 4000, MAT_DIAMOND = 500) - build_path = /obj/item/modularlaser/lasermedium/laser/cannon - -*/ - -/datum/prototype/design/science/modweaponnodule/xraser - design_name = "xraser medium" - id = "xraser" - req_tech = list(TECH_MAGNET = 5, TECH_POWER = 5, TECH_COMBAT = 5, TECH_ILLEGAL = 5) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_URANIUM = 4000, MAT_GOLD = 4000, MAT_DIAMOND = 500) - build_path = /obj/item/modularlaser/lasermedium/laser/xray - -/datum/prototype/design/science/modweaponnodule/dig - design_name = "excavation beam medium" - id = "digbeam" - req_tech = list(TECH_MAGNET = 3, TECH_POWER = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_PLASTEEL = 500) - build_path = /obj/item/modularlaser/lasermedium/dig - -/datum/prototype/design/science/modweaponnodule/lightning - design_name = "lightning arc tube" - id = "lightning" - req_tech = list(TECH_MAGNET = 6, TECH_POWER = 7) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_SILVER = 4000, MAT_GOLD = 4000) - build_path = /obj/item/modularlaser/lasermedium/lightning - -/datum/prototype/design/science/modweaponnodule/hook - design_name = "graviton grapple tube" - id = "hook" - req_tech = list(TECH_ARCANE = 4, TECH_POWER = 2) - materials_base =list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_MORPHIUM = 4000, MAT_VERDANTIUM = 4000) - build_path = /obj/item/modularlaser/lasermedium/hook - -/datum/prototype/design/science/modweaponnodule/phasemedium - design_name = "phase projector tube" - id = "phasemedium" - req_tech = list(TECH_MAGNET = 3, TECH_POWER = 2) - materials_base = list(MAT_STEEL = 8000, MAT_GLASS = 8000) - build_path = /obj/item/modularlaser/lasermedium/phase - -/datum/prototype/design/science/modweaponnodule/basiclens - design_name = "basic lens" - id = "basiclens" - req_tech = list(TECH_MATERIAL = 3) - materials_base = list(MAT_GLASS = 8000) - build_path = /obj/item/modularlaser/lens/basic - -/datum/prototype/design/science/modweaponnodule/advlens - design_name = "advanced lens" - id = "advlens" - req_tech = list(TECH_MATERIAL = 6) - materials_base = list(MAT_GLASS = 8000, MAT_SILVER = 3000) - build_path = /obj/item/modularlaser/lens/advanced - -/datum/prototype/design/science/modweaponnodule/superlens - design_name = "superior lens" - id = "superlens" - req_tech = list(TECH_MATERIAL = 9) - materials_base = list(MAT_GLASS = 8000, MAT_SILVER = 3000, MAT_DURASTEEL = 2000) - build_path = /obj/item/modularlaser/lens/super - -/* - -/datum/prototype/design/science/modweaponnodule/scatterlens - design_name = "scatter lens" - id = "scatterlens" - req_tech = list(TECH_MATERIAL = 6) - materials_base = list(MAT_GLASS = 8000, MAT_SILVER = 3000) - build_path = /obj/item/modularlaser/lens/scatter - -/datum/prototype/design/science/modweaponnodule/advscatterlens - design_name = "advanced scatter lens" - id = "advscatterlens" - req_tech = list(TECH_MATERIAL = 9) - materials_base = list(MAT_GLASS = 8000, MAT_SILVER = 3000, MAT_VERDANTIUM = 2000) - build_path = /obj/item/modularlaser/lens/scatter/adv - -*/ - -/datum/prototype/design/science/modweaponnodule/basiccap - design_name = "basic capacitor" - id = "basiccap" - req_tech = list(TECH_POWER = 2) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000) - build_path = /obj/item/modularlaser/capacitor/basic - -/datum/prototype/design/science/modweaponnodule/ecocap - design_name = "efficient capacitor" - id = "ecocap" - req_tech = list(TECH_POWER = 4) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500) - build_path = /obj/item/modularlaser/capacitor/eco - -/datum/prototype/design/science/modweaponnodule/supereco - design_name = "economical capacitor" - id = "superecocap" - req_tech = list(TECH_POWER = 6) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500) - build_path = /obj/item/modularlaser/capacitor/eco/super - -/* - -/datum/prototype/design/science/modweaponnodule/quickcap - design_name = "high throughput capactior" - id = "quickcap" - req_tech = list(TECH_POWER = 8) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500) - build_path = /obj/item/modularlaser/capacitor/speed - -/datum/prototype/design/science/modweaponnodule/advqcap - design_name = "very high throughput capactior" - id = "advqcap" - req_tech = list(TECH_POWER = 8) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500, MAT_VERDANTIUM = 300) - build_path = /obj/item/modularlaser/capacitor/speed/advanced - -*/ - -/datum/prototype/design/science/modweaponnodule/basicac - design_name = "cooling system" - id = "basicac" - req_tech = list(TECH_ENGINEERING = 2) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000) - build_path = /obj/item/modularlaser/cooling/basic - -/datum/prototype/design/science/modweaponnodule/recoverac - design_name = "regenerative cooling system" - id = "recoverac" - req_tech = list(TECH_ENGINEERING = 4) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500) - build_path = /obj/item/modularlaser/cooling/efficient - -/datum/prototype/design/science/modweaponnodule/fastac - design_name = "high-power regenerative cooling system" - id = "fastac" - req_tech = list(TECH_ENGINEERING = 7) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500, MAT_VERDANTIUM = 300) - build_path = /obj/item/modularlaser/cooling/efficient/super - -/* - -/datum/prototype/design/science/modweaponnodule/superac - design_name = "supercharged cooling system" - id = "superac" - req_tech = list(TECH_ENGINEERING = 4) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500) - build_path = /obj/item/modularlaser/cooling/speed - -/datum/prototype/design/science/modweaponnodule/bestac - design_name = "hypercharged cooling system" - id = "hyperac" - req_tech = list(TECH_ENGINEERING = 7) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500, MAT_VERDANTIUM = 300) - build_path = /obj/item/modularlaser/cooling/speed/adv - -*/ - -/datum/prototype/design/science/modweaponnodule/modcontrol - design_name = "controller" - id = "modcontrol" - req_tech = list(TECH_DATA = 2) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000) - build_path = /obj/item/modularlaser/controller/basic - -/* - -/datum/prototype/design/science/modweaponnodule/an94 - design_name = "AN-94 patterned fire controller" - id = "an94" - req_tech = list(TECH_DATA = 5, TECH_COMBAT = 3) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000) - build_path = /obj/item/modularlaser/controller/twoburst - -*/ - -/datum/prototype/design/science/modweaponnodule/threecontrol - design_name = "burst FCU" - id = "threecontrol" - req_tech = list(TECH_DATA = 5, TECH_COMBAT = 3) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500) - build_path = /obj/item/modularlaser/controller/threeburst - -/* - -/datum/prototype/design/science/modweaponnodule/fivecontrol - design_name = "quintburst FCU" - id = "fivecontrol" - req_tech = list(TECH_DATA = 5, TECH_COMBAT = 4) - materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 1000, MAT_SILVER = 500, MAT_GOLD = 500) - build_path = /obj/item/modularlaser/controller/fiveburst - -*/ - /datum/prototype/design/science/pin category = DESIGN_CATEGORY_MUNITIONS subcategory = DESIGN_SUBCATEGORY_PINS diff --git a/code/modules/shieldgen/handheld_defuser.dm b/code/modules/shieldgen/handheld_defuser.dm index 3235d95a9ed1..d71a7f38edff 100644 --- a/code/modules/shieldgen/handheld_defuser.dm +++ b/code/modules/shieldgen/handheld_defuser.dm @@ -31,11 +31,11 @@ for(var/direction in GLOB.cardinal) var/turf/simulated/shielded_tile = get_step(get_turf(src), direction) for(var/obj/effect/shield/S in shielded_tile) - if(istype(S) && !S.diffused_for && !S.disabled_for && cell.checked_use_scaled(CELL_COST_SHIELD_DIFFUSION)) + if(istype(S) && !S.diffused_for && !S.disabled_for && cell.checked_use(CELL_COST_SHIELD_DIFFUSION)) S.diffuse(20) // Legacy shield support for(var/obj/effect/energy_field/S in shielded_tile) - if(istype(S) && cell.checked_use_scaled(CELL_COST_SHIELD_DIFFUSION)) + if(istype(S) && cell.checked_use(CELL_COST_SHIELD_DIFFUSION)) qdel(S) /obj/item/shield_diffuser/update_icon() 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/gun.dmi b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/gun.dmi new file mode 100644 index 000000000000..38408491e314 Binary files /dev/null and b/icons/content/factions/corporations/nanotrasen/items/guns/protomag/gun.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..a579f62dc583 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/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/modules/projectiles/components/acceleration_coil.dmi b/icons/modules/projectiles/components/acceleration_coil.dmi new file mode 100644 index 000000000000..35e1d82d97d4 Binary files /dev/null and b/icons/modules/projectiles/components/acceleration_coil.dmi differ diff --git a/icons/modules/projectiles/components/active_cooler.dmi b/icons/modules/projectiles/components/active_cooler.dmi new file mode 100644 index 000000000000..f20e83c56ba9 Binary files /dev/null and b/icons/modules/projectiles/components/active_cooler.dmi differ diff --git a/icons/modules/projectiles/components/energy_handler.dmi b/icons/modules/projectiles/components/energy_handler.dmi new file mode 100644 index 000000000000..b8062b2fe441 Binary files /dev/null and b/icons/modules/projectiles/components/energy_handler.dmi differ diff --git a/icons/modules/projectiles/components/focusing_lens.dmi b/icons/modules/projectiles/components/focusing_lens.dmi new file mode 100644 index 000000000000..388d8a1b5ad8 Binary files /dev/null and b/icons/modules/projectiles/components/focusing_lens.dmi differ diff --git a/icons/modules/projectiles/components/internal_module.dmi b/icons/modules/projectiles/components/internal_module.dmi new file mode 100644 index 000000000000..8eb366854282 Binary files /dev/null and b/icons/modules/projectiles/components/internal_module.dmi differ diff --git a/icons/modules/projectiles/components/particle_array.dmi b/icons/modules/projectiles/components/particle_array.dmi new file mode 100644 index 000000000000..ad52149a5138 Binary files /dev/null and b/icons/modules/projectiles/components/particle_array.dmi differ diff --git a/icons/modules/projectiles/components/power_unit.dmi b/icons/modules/projectiles/components/power_unit.dmi new file mode 100644 index 000000000000..ce0a6cf36d09 Binary files /dev/null and b/icons/modules/projectiles/components/power_unit.dmi differ diff --git a/icons/modules/projectiles/components/unused.dmi b/icons/modules/projectiles/components/unused.dmi new file mode 100644 index 000000000000..084dcf6e8342 Binary files /dev/null and b/icons/modules/projectiles/components/unused.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 4bca0261c364..dfab60174950 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, diff --git a/sound/content/factions/corporations/nanotrasen/protomag-pistol.ogg b/sound/content/factions/corporations/nanotrasen/protomag-pistol.ogg new file mode 100644 index 000000000000..96eb22c77d58 Binary files /dev/null and b/sound/content/factions/corporations/nanotrasen/protomag-pistol.ogg differ diff --git a/sound/content/factions/corporations/nanotrasen/protomag-rifle.ogg b/sound/content/factions/corporations/nanotrasen/protomag-rifle.ogg new file mode 100644 index 000000000000..1c10ea6b0ee1 Binary files /dev/null and b/sound/content/factions/corporations/nanotrasen/protomag-rifle.ogg differ