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) + . += "