diff --git a/.github/workflows/size_labeling.yml b/.github/workflows/size_labeling.yml
index d0dfe06c8377..47d3ee779c4a 100644
--- a/.github/workflows/size_labeling.yml
+++ b/.github/workflows/size_labeling.yml
@@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: size-label
- uses: pascalgn/size-label-action@v0.4.3
+ uses: pascalgn/size-label-action@v0.5.4
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
IGNORED: "**/*.bundle.*\n**/*.chunk.*" # **/*.dmm\n
diff --git a/citadel.dme b/citadel.dme
index 898d2771d3f1..ff216dc4a2ad 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -46,7 +46,6 @@
#include "code\__DEFINES\damage_organs.dm"
#include "code\__DEFINES\directional.dm"
#include "code\__DEFINES\dna.dm"
-#include "code\__DEFINES\event_args.dm"
#include "code\__DEFINES\fonts.dm"
#include "code\__DEFINES\frames.dm"
#include "code\__DEFINES\gamemode.dm"
@@ -162,6 +161,7 @@
#include "code\__DEFINES\controllers\timer.dm"
#include "code\__DEFINES\datums\beam.dm"
#include "code\__DEFINES\datums\design.dm"
+#include "code\__DEFINES\datums\event_args.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\components\riding.dm"
@@ -177,33 +177,34 @@
#include "code\__DEFINES\dcs\signals\elements\signals_element_conflict_checking.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"
+#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-context_system.dm"
+#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-defense.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-reachability.dm"
+#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-throwing.dm"
+#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-tool_system.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_appearance.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm"
-#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_buckling.dm"
-#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_context_system.dm"
-#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_defense.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_main.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_mouse.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movable.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_radiation.dm"
-#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_throwing.dm"
-#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_tool_system.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm"
#include "code\__DEFINES\dcs\signals\signals_item\signals_item-interaction.dm"
#include "code\__DEFINES\dcs\signals\signals_item\signals_item_economy.dm"
#include "code\__DEFINES\dcs\signals\signals_item\signals_item_inventory.dm"
#include "code\__DEFINES\dcs\signals\signals_item\signals_item_mouse.dm"
#include "code\__DEFINES\dcs\signals\signals_item\signals_item_storage.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob-inventory.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob-perspective.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_appearance.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_combat.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_defense.dm"
-#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_inventory.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_legacy.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_mobility.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_organs.dm"
-#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_perspectiive.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_simple.dm"
#include "code\__DEFINES\economy\category.dm"
#include "code\__DEFINES\economy\currency.dm"
@@ -293,6 +294,7 @@
#include "code\__DEFINES\projectiles\ammo_casing.dm"
#include "code\__DEFINES\projectiles\ammo_magazine.dm"
#include "code\__DEFINES\projectiles\guns.dm"
+#include "code\__DEFINES\projectiles\projectile.dm"
#include "code\__DEFINES\projectiles\system.dm"
#include "code\__DEFINES\radiation\flags.dm"
#include "code\__DEFINES\radiation\ignore.dm"
@@ -380,7 +382,6 @@
#include "code\__HELPERS\vector.dm"
#include "code\__HELPERS\verbs.dm"
#include "code\__HELPERS\animations\attack.dm"
-#include "code\__HELPERS\datastructs\armor.dm"
#include "code\__HELPERS\datastructs\bodytypes.dm"
#include "code\__HELPERS\datastructs\filters.dm"
#include "code\__HELPERS\datastructs\ingredients.dm"
@@ -391,6 +392,7 @@
#include "code\__HELPERS\files\paths.dm"
#include "code\__HELPERS\files\walk.dm"
#include "code\__HELPERS\game\depth.dm"
+#include "code\__HELPERS\game\combat\arc.dm"
#include "code\__HELPERS\game\turfs\line.dm"
#include "code\__HELPERS\game\turfs\offsets.dm"
#include "code\__HELPERS\graphs\astar.dm"
@@ -639,10 +641,11 @@
#include "code\controllers\subsystem\persistence\modules\bulk_entity_serializers\trash.dm"
#include "code\controllers\subsystem\processing\chemistry.dm"
#include "code\controllers\subsystem\processing\circuits.dm"
-#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\instruments.dm"
#include "code\controllers\subsystem\processing\moving_cameras.dm"
#include "code\controllers\subsystem\processing\obj.dm"
+#include "code\controllers\subsystem\processing\process_20fps.dm"
+#include "code\controllers\subsystem\processing\process_5fps.dm"
#include "code\controllers\subsystem\processing\processing.dm"
#include "code\controllers\subsystem\processing\projectiles.dm"
#include "code\controllers\subsystem\processing\turfs.dm"
@@ -698,6 +701,7 @@
#include "code\datums\announce\location\global.dm"
#include "code\datums\announce\location\main_station.dm"
#include "code\datums\announce\location\overmap_sector.dm"
+#include "code\datums\armor\armor.dm"
#include "code\datums\armor\core.dm"
#include "code\datums\armor\mecha.dm"
#include "code\datums\armor\military.dm"
@@ -766,7 +770,10 @@
#include "code\datums\components\atoms\radioactive.dm"
#include "code\datums\components\crafting\crafting.dm"
#include "code\datums\components\crafting\guncrafting.dm"
+#include "code\datums\components\items\passive_parry.dm"
#include "code\datums\components\items\wielding.dm"
+#include "code\datums\components\mobs\block_frame.dm"
+#include "code\datums\components\mobs\parry_frame.dm"
#include "code\datums\components\movable\aquarium.dm"
#include "code\datums\components\movable\spatial_grid.dm"
#include "code\datums\components\objects\slippery.dm"
@@ -896,6 +903,7 @@
#include "code\datums\repositories\unique.dm"
#include "code\datums\soundbytes\_soundbyte.dm"
#include "code\datums\soundbytes\announcer.dm"
+#include "code\datums\soundbytes\effects\combat.dm"
#include "code\datums\soundbytes\effects\explosion.dm"
#include "code\datums\soundbytes\effects\sparks.dm"
#include "code\datums\soundbytes\effects\spray.dm"
@@ -1012,18 +1020,20 @@
#include "code\game\area\station\security_areas.dm"
#include "code\game\atoms\action_feedback.dm"
#include "code\game\atoms\appearance.dm"
+#include "code\game\atoms\atom-construction.dm"
+#include "code\game\atoms\atom-damage.dm"
+#include "code\game\atoms\atom-defense.dm"
#include "code\game\atoms\atom.dm"
#include "code\game\atoms\buckling.dm"
-#include "code\game\atoms\defense.dm"
#include "code\game\atoms\defense_old.dm"
#include "code\game\atoms\materials.dm"
#include "code\game\atoms\movement.dm"
#include "code\game\atoms\say.dm"
#include "code\game\atoms\vv.dm"
+#include "code\game\atoms\movable\movable-throw.dm"
#include "code\game\atoms\movable\movable.dm"
#include "code\game\atoms\movable\movement.dm"
#include "code\game\atoms\movable\pulling.dm"
-#include "code\game\atoms\movable\throwing.dm"
#include "code\game\atoms\movable\vv.dm"
#include "code\game\atoms\movable\special\overlay.dm"
#include "code\game\atoms\movable\special\render.dm"
@@ -1450,16 +1460,19 @@
#include "code\game\magic\Uristrunes.dm"
#include "code\game\objects\attacks.dm"
#include "code\game\objects\banners.dm"
-#include "code\game\objects\defense.dm"
#include "code\game\objects\empulse.dm"
#include "code\game\objects\explosion.dm"
#include "code\game\objects\explosion_recursive.dm"
+#include "code\game\objects\items-carry_weight.dm"
+#include "code\game\objects\items-defense.dm"
#include "code\game\objects\items-interaction.dm"
#include "code\game\objects\items.dm"
#include "code\game\objects\materials.dm"
#include "code\game\objects\misc.dm"
#include "code\game\objects\mob_spawner.dm"
-#include "code\game\objects\objs.dm"
+#include "code\game\objects\obj-construction.dm"
+#include "code\game\objects\obj-defense.dm"
+#include "code\game\objects\obj.dm"
#include "code\game\objects\structures.dm"
#include "code\game\objects\stumble_into_vr.dm"
#include "code\game\objects\topic.dm"
@@ -1644,6 +1657,15 @@
#include "code\game\objects\items\id_cards\station_ids.dm"
#include "code\game\objects\items\id_cards\syndicate_ids.dm"
#include "code\game\objects\items\janitorial\soap.dm"
+#include "code\game\objects\items\melee\melee.dm"
+#include "code\game\objects\items\melee\types\misc.dm"
+#include "code\game\objects\items\melee\types\ninja_energy_blade.dm"
+#include "code\game\objects\items\melee\types\transforming.dm"
+#include "code\game\objects\items\melee\types\transforming\energy.dm"
+#include "code\game\objects\items\melee\types\transforming\hfmachete.dm"
+#include "code\game\objects\items\melee\types\transforming\energy\axe.dm"
+#include "code\game\objects\items\melee\types\transforming\energy\ionic_rapier.dm"
+#include "code\game\objects\items\melee\types\transforming\energy\saber.dm"
#include "code\game\objects\items\robot\gripper.dm"
#include "code\game\objects\items\robot\robot_items.dm"
#include "code\game\objects\items\robot\robot_parts.dm"
@@ -1654,6 +1676,14 @@
#include "code\game\objects\items\scanners\reagent.dm"
#include "code\game\objects\items\scanners\slime.dm"
#include "code\game\objects\items\scanners\spectrometer.dm"
+#include "code\game\objects\items\shield\shield.dm"
+#include "code\game\objects\items\shield\types\shields_legacy.dm"
+#include "code\game\objects\items\shield\types\shields_legacy_vr.dm"
+#include "code\game\objects\items\shield\types\transforming.dm"
+#include "code\game\objects\items\shield\types\transforming\energy.dm"
+#include "code\game\objects\items\shield\types\transforming\telescopic.dm"
+#include "code\game\objects\items\shield_projector\shield_matrix.dm"
+#include "code\game\objects\items\shield_projector\shield_projector.dm"
#include "code\game\objects\items\stacks\fifty_spawner.dm"
#include "code\game\objects\items\stacks\marker_beacons.dm"
#include "code\game\objects\items\stacks\matter_synth.dm"
@@ -1735,7 +1765,6 @@
#include "code\game\objects\items\weapons\RPD.dm"
#include "code\game\objects\items\weapons\RSF.dm"
#include "code\game\objects\items\weapons\scrolls.dm"
-#include "code\game\objects\items\weapons\shields.dm"
#include "code\game\objects\items\weapons\stunbaton.dm"
#include "code\game\objects\items\weapons\surgery_tools.dm"
#include "code\game\objects\items\weapons\swords_axes_etc.dm"
@@ -1787,10 +1816,6 @@
#include "code\game\objects\items\weapons\material\thrown.dm"
#include "code\game\objects\items\weapons\material\twohanded.dm"
#include "code\game\objects\items\weapons\material\whetstone.dm"
-#include "code\game\objects\items\weapons\melee\deflect.dm"
-#include "code\game\objects\items\weapons\melee\energy.dm"
-#include "code\game\objects\items\weapons\melee\melee.dm"
-#include "code\game\objects\items\weapons\melee\misc.dm"
#include "code\game\objects\items\weapons\tanks\jetpack.dm"
#include "code\game\objects\items\weapons\tanks\tank.dm"
#include "code\game\objects\items\weapons\tanks\tank_types.dm"
@@ -1981,8 +2006,8 @@
#include "code\game\rendering\plane_masters\plane_render.dm"
#include "code\game\turfs\baseturfs.dm"
#include "code\game\turfs\change_turf.dm"
-#include "code\game\turfs\defense.dm"
#include "code\game\turfs\simulated.dm"
+#include "code\game\turfs\turf-construction.dm"
#include "code\game\turfs\turf.dm"
#include "code\game\turfs\turf_ao.dm"
#include "code\game\turfs\turf_flick_animations.dm"
@@ -2016,9 +2041,11 @@
#include "code\game\turfs\simulated\flooring\flooring_traps.dm"
#include "code\game\turfs\simulated\flooring\shuttle_vr.dm"
#include "code\game\turfs\simulated\misc\fancy_shuttles.dm"
-#include "code\game\turfs\simulated\wall\defense.dm"
#include "code\game\turfs\simulated\wall\materials.dm"
#include "code\game\turfs\simulated\wall\rot.dm"
+#include "code\game\turfs\simulated\wall\wall-construction.dm"
+#include "code\game\turfs\simulated\wall\wall-damage.dm"
+#include "code\game\turfs\simulated\wall\wall-defense.dm"
#include "code\game\turfs\simulated\wall\wall.dm"
#include "code\game\turfs\simulated\wall\wall_attacks.dm"
#include "code\game\turfs\simulated\wall\wall_icon.dm"
@@ -2481,6 +2508,7 @@
#include "code\modules\clothing\masks\gasmask.dm"
#include "code\modules\clothing\masks\miscellaneous.dm"
#include "code\modules\clothing\masks\voice.dm"
+#include "code\modules\clothing\sets\armor\ablative.dm"
#include "code\modules\clothing\shoes\_shoes.dm"
#include "code\modules\clothing\shoes\boots.dm"
#include "code\modules\clothing\shoes\colour.dm"
@@ -3423,7 +3451,6 @@
#include "code\modules\mob\animations.dm"
#include "code\modules\mob\client.dm"
#include "code\modules\mob\death.dm"
-#include "code\modules\mob\defense.dm"
#include "code\modules\mob\emote.dm"
#include "code\modules\mob\floating_message.dm"
#include "code\modules\mob\gender.dm"
@@ -3435,6 +3462,8 @@
#include "code\modules\mob\life.dm"
#include "code\modules\mob\login.dm"
#include "code\modules\mob\logout.dm"
+#include "code\modules\mob\mob-damage.dm"
+#include "code\modules\mob\mob-defense.dm"
#include "code\modules\mob\mob-keybind-triggers.dm"
#include "code\modules\mob\mob.dm"
#include "code\modules\mob\mob_defines.dm"
@@ -3503,13 +3532,13 @@
#include "code\modules\mob\inventory\stripping.dm"
#include "code\modules\mob\living\autohiss.dm"
#include "code\modules\mob\living\butchering.dm"
-#include "code\modules\mob\living\damage_procs.dm"
#include "code\modules\mob\living\death.dm"
#include "code\modules\mob\living\default_language.dm"
-#include "code\modules\mob\living\defense.dm"
#include "code\modules\mob\living\health.dm"
#include "code\modules\mob\living\inventory.dm"
#include "code\modules\mob\living\life.dm"
+#include "code\modules\mob\living\living-damage.dm"
+#include "code\modules\mob\living\living-defense-legacy.dm"
#include "code\modules\mob\living\living-defense.dm"
#include "code\modules\mob\living\living.dm"
#include "code\modules\mob\living\living_defines.dm"
@@ -3595,11 +3624,12 @@
#include "code\modules\mob\living\carbon\human\emote.dm"
#include "code\modules\mob\living\carbon\human\examine.dm"
#include "code\modules\mob\living\carbon\human\health.dm"
+#include "code\modules\mob\living\carbon\human\human-damage-legacy.dm"
+#include "code\modules\mob\living\carbon\human\human-damage.dm"
+#include "code\modules\mob\living\carbon\human\human-defense-legacy.dm"
#include "code\modules\mob\living\carbon\human\human-defense.dm"
#include "code\modules\mob\living\carbon\human\human.dm"
#include "code\modules\mob\living\carbon\human\human_attackhand.dm"
-#include "code\modules\mob\living\carbon\human\human_damage.dm"
-#include "code\modules\mob\living\carbon\human\human_defense.dm"
#include "code\modules\mob\living\carbon\human\human_defines.dm"
#include "code\modules\mob\living\carbon\human\human_helpers.dm"
#include "code\modules\mob\living\carbon\human\human_modular_limbs.dm"
@@ -3645,6 +3675,8 @@
#include "code\modules\mob\living\silicon\offense.dm"
#include "code\modules\mob\living\silicon\perspective.dm"
#include "code\modules\mob\living\silicon\say.dm"
+#include "code\modules\mob\living\silicon\silicon-damage.dm"
+#include "code\modules\mob\living\silicon\silicon-defense-legacy.dm"
#include "code\modules\mob\living\silicon\silicon.dm"
#include "code\modules\mob\living\silicon\subystems.dm"
#include "code\modules\mob\living\silicon\translation.dm"
@@ -4228,7 +4260,6 @@
#include "code\modules\power\fission\rods.dm"
#include "code\modules\power\fusion\_setup.dm"
#include "code\modules\power\fusion\fusion_circuits.dm"
-#include "code\modules\power\fusion\fusion_particle_catcher.dm"
#include "code\modules\power\fusion\fusion_reactions.dm"
#include "code\modules\power\fusion\fusion_reagents.dm"
#include "code\modules\power\fusion\magpower.dm"
@@ -4326,8 +4357,6 @@
#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\projectile-tracing.dm"
-#include "code\modules\projectiles\projectile.dm"
#include "code\modules\projectiles\ammunition\ammo_caliber.dm"
#include "code\modules\projectiles\ammunition\ammo_casing.dm"
#include "code\modules\projectiles\ammunition\ammo_magazine.dm"
@@ -4425,25 +4454,32 @@
#include "code\modules\projectiles\guns\projectile\caseless\pellet.dm"
#include "code\modules\projectiles\guns\projectile\sniper\collapsible_sniper.dm"
#include "code\modules\projectiles\projectile\arc.dm"
-#include "code\modules\projectiles\projectile\blob.dm"
-#include "code\modules\projectiles\projectile\bullets.dm"
-#include "code\modules\projectiles\projectile\bullets_vr.dm"
-#include "code\modules\projectiles\projectile\change.dm"
-#include "code\modules\projectiles\projectile\energy.dm"
-#include "code\modules\projectiles\projectile\energy_vr.dm"
-#include "code\modules\projectiles\projectile\explosive.dm"
-#include "code\modules\projectiles\projectile\force.dm"
-#include "code\modules\projectiles\projectile\hook.dm"
-#include "code\modules\projectiles\projectile\magic.dm"
-#include "code\modules\projectiles\projectile\magnetic.dm"
-#include "code\modules\projectiles\projectile\pellets.dm"
-#include "code\modules\projectiles\projectile\reusable.dm"
-#include "code\modules\projectiles\projectile\scatter.dm"
-#include "code\modules\projectiles\projectile\special.dm"
-#include "code\modules\projectiles\projectile\trace.dm"
-#include "code\modules\projectiles\projectile\beam\beams.dm"
-#include "code\modules\projectiles\projectile\beam\beams_vr.dm"
-#include "code\modules\projectiles\projectile\beam\blaster.dm"
+#include "code\modules\projectiles\projectile\helpers.dm"
+#include "code\modules\projectiles\projectile\projectile-hitscan_visuals.dm"
+#include "code\modules\projectiles\projectile\projectile-physics.dm"
+#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\subtypes\arc.dm"
+#include "code\modules\projectiles\projectile\subtypes\blob.dm"
+#include "code\modules\projectiles\projectile\subtypes\bullets.dm"
+#include "code\modules\projectiles\projectile\subtypes\bullets_vr.dm"
+#include "code\modules\projectiles\projectile\subtypes\change.dm"
+#include "code\modules\projectiles\projectile\subtypes\energy.dm"
+#include "code\modules\projectiles\projectile\subtypes\energy_vr.dm"
+#include "code\modules\projectiles\projectile\subtypes\explosive.dm"
+#include "code\modules\projectiles\projectile\subtypes\force.dm"
+#include "code\modules\projectiles\projectile\subtypes\hook.dm"
+#include "code\modules\projectiles\projectile\subtypes\magic.dm"
+#include "code\modules\projectiles\projectile\subtypes\magnetic.dm"
+#include "code\modules\projectiles\projectile\subtypes\pellet.dm"
+#include "code\modules\projectiles\projectile\subtypes\reusable.dm"
+#include "code\modules\projectiles\projectile\subtypes\scatter.dm"
+#include "code\modules\projectiles\projectile\subtypes\special.dm"
+#include "code\modules\projectiles\projectile\subtypes\trace.dm"
+#include "code\modules\projectiles\projectile\subtypes\beam\beams.dm"
+#include "code\modules\projectiles\projectile\subtypes\beam\beams_vr.dm"
+#include "code\modules\projectiles\projectile\subtypes\beam\blaster.dm"
#include "code\modules\projectiles\targeting\targeting_client.dm"
#include "code\modules\projectiles\targeting\targeting_gun.dm"
#include "code\modules\projectiles\targeting\targeting_mob.dm"
@@ -4608,7 +4644,6 @@
#include "code\modules\sculpting\sculpting_block.dm"
#include "code\modules\security levels\keycard authentication.dm"
#include "code\modules\security levels\security levels.dm"
-#include "code\modules\shieldgen\directional_shield.dm"
#include "code\modules\shieldgen\emergency_shield.dm"
#include "code\modules\shieldgen\energy_field.dm"
#include "code\modules\shieldgen\energy_shield.dm"
diff --git a/code/__DEFINES/_core.dm b/code/__DEFINES/_core.dm
index 041c95757dcb..2a6a70722d89 100644
--- a/code/__DEFINES/_core.dm
+++ b/code/__DEFINES/_core.dm
@@ -12,5 +12,8 @@
/// * put the stack trace in stack trace storage
#define stack_trace(message) _stack_trace(message, __FILE__, __LINE__)
+/// get variable if not null or
+#define VALUE_OR_DEFAULT(VAL, DEFAULT) (isnull(VAL)? (DEFAULT) : (VAL))
+
/// byond bug https://secure.byond.com/forum/?post=2072419
#define BLOCK_BYOND_BUG_2072419
diff --git a/code/__DEFINES/_flags/atom_flags.dm b/code/__DEFINES/_flags/atom_flags.dm
index 8b10246acf6f..8a7ccb364e17 100644
--- a/code/__DEFINES/_flags/atom_flags.dm
+++ b/code/__DEFINES/_flags/atom_flags.dm
@@ -59,7 +59,7 @@ DEFINE_BITFIELD(atom_flags, list(
#define MOVABLE_NO_THROW_DAMAGE_SCALING (1<<1)
/// Do not spin when thrown.
#define MOVABLE_NO_THROW_SPIN (1<<2)
-/// We are currently about to be yanked by a Moved() triggering a Move()
+/// We are currently about to be yanked by a Moved(), Entered(), or Exited() triggering a Move()
///
/// * used so things like projectile hitscans know to yield
#define MOVABLE_IN_MOVED_YANK (1<<3)
@@ -85,11 +85,13 @@ DEFINE_BITFIELD(movable_flags, list(
#define ATOM_PASS_OVERHEAD_THROW (1<<7)
/// let buckled mobs pass always
#define ATOM_PASS_BUCKLED (1<<8)
+/// "please don't interact with us"
+#define ATOM_PASS_INCORPOREAL (1<<9)
/// all actual pass flags / maximum pass
#define ATOM_PASS_ALL (ATOM_PASS_TABLE | ATOM_PASS_GLASS | ATOM_PASS_GRILLE | \
ATOM_PASS_BLOB | ATOM_PASS_MOB | ATOM_PASS_THROWN | ATOM_PASS_CLICK | \
- ATOM_PASS_OVERHEAD_THROW | ATOM_PASS_BUCKLED)
+ ATOM_PASS_OVERHEAD_THROW | ATOM_PASS_BUCKLED | ATOM_PASS_INCORPOREAL)
DEFINE_BITFIELD(pass_flags, list(
BITFIELD(ATOM_PASS_TABLE),
@@ -101,6 +103,7 @@ DEFINE_BITFIELD(pass_flags, list(
BITFIELD(ATOM_PASS_CLICK),
BITFIELD(ATOM_PASS_OVERHEAD_THROW),
BITFIELD(ATOM_PASS_BUCKLED),
+ BITFIELD(ATOM_PASS_INCORPOREAL),
))
DEFINE_BITFIELD(pass_flags_self, list(
@@ -113,6 +116,7 @@ DEFINE_BITFIELD(pass_flags_self, list(
BITFIELD(ATOM_PASS_CLICK),
BITFIELD(ATOM_PASS_OVERHEAD_THROW),
BITFIELD(ATOM_PASS_BUCKLED),
+ BITFIELD(ATOM_PASS_INCORPOREAL),
))
//? /atom/movable movement_type - only one primary type should be on the atom at a time, but these are flags for quick checks.
diff --git a/code/__DEFINES/_planes+layers.dm b/code/__DEFINES/_planes+layers.dm
index 37eaaacbeb63..b38ab1a0fdd9 100644
--- a/code/__DEFINES/_planes+layers.dm
+++ b/code/__DEFINES/_planes+layers.dm
@@ -178,6 +178,7 @@
#define STAIRS_LAYER (TURF_LAYER+0.5) /// Layer for stairs.
#define DOOR_OPEN_LAYER (TURF_LAYER+0.7) /// Under all objects if opened. 2.7 due to tables being at 2.6.
#define TABLE_LAYER (TURF_LAYER+0.8) /// Just under stuff that wants to be slightly below common objects.
+/// Below this layer, projectiles won't collide with things unless it's a directly clicked target.
#define PROJECTILE_HIT_THRESHOLD_LAYER 2.8
#define UNDER_JUNK_LAYER (TURF_LAYER+0.9) /// Things that want to be slightly below common objects.
diff --git a/code/__DEFINES/callbacks.dm b/code/__DEFINES/callbacks.dm
index 33eda1bac26d..1c590425eb2a 100644
--- a/code/__DEFINES/callbacks.dm
+++ b/code/__DEFINES/callbacks.dm
@@ -1,10 +1,12 @@
/// Arbitrary sentinel value for global proc callbacks
#define GLOBAL_PROC "some_magic_bullshit"
+/// Arbitrary sentinel value for making sure a callback didn't sleep
+#define CALLBACK_SLEEP_SENTINEL "___THE PROC SLEPT___"
/// A shorthand for the callback datum, [documented here](datum/callback.html)
#define CALLBACK new /datum/callback
-///Per the DM reference, spawn(-1) will execute the spawned code immediately until a block is met.
+/// Per the DM reference, spawn(-1) will execute the spawned code immediately until a block is met.
#define MAKE_SPAWN_ACT_LIKE_WAITFOR -1
-///Create a codeblock that will not block the callstack if a block is met.
+/// Create a codeblock that will not block the callstack if a block is met.
#define ASYNC spawn(MAKE_SPAWN_ACT_LIKE_WAITFOR)
#define INVOKE_ASYNC(proc_owner, proc_path, proc_arguments...) \
diff --git a/code/__DEFINES/combat/armor.dm b/code/__DEFINES/combat/armor.dm
index f57d5e4f6f22..67ede87ad789 100644
--- a/code/__DEFINES/combat/armor.dm
+++ b/code/__DEFINES/combat/armor.dm
@@ -59,14 +59,26 @@ GLOBAL_REAL_LIST(armor_enums) = list(
ARMOR_ACID,
)
+GLOBAL_REAL_LIST(armor_types) = list(
+ ARMOR_MELEE,
+ ARMOR_BULLET,
+ ARMOR_LASER,
+ ARMOR_ENERGY,
+ ARMOR_BOMB,
+ ARMOR_BIO,
+ ARMOR_RAD,
+ ARMOR_FIRE,
+ ARMOR_ACID,
+)
+
//? --- armor tiers ---
-#define ARMOR_TIER_DEFAULT 0
+#define ARMOR_TIER_DEFAULT ARMOR_TIER_BASELINE
#define ARMOR_TIER_LAUGHABLE -3
#define ARMOR_TIER_LOW -2
#define ARMOR_TIER_BELOW -1
-#define ARMOR_TIER_NORMAL 0
+#define ARMOR_TIER_BASELINE 0
#define ARMOR_TIER_ABOVE 1
#define ARMOR_TIER_HIGH 2
#define ARMOR_TIER_OVERWHELMING 3
@@ -76,37 +88,49 @@ GLOBAL_REAL_LIST(armor_enums) = list(
//? melee
-#define MELEE_TIER_DEFAULT ARMOR_TIER_DEFAULT
+#define MELEE_TIER_DEFAULT MELEE_TIER_MEDIUM
#define MELEE_TIER_UNARMED_DEFAULT ARMOR_TIER_LOW
#define MELEE_TIER_UNARMED_FISTS ARMOR_TIER_LOW
#define MELEE_TIER_UNARMED_CLAW ARMOR_TIER_BELOW
-#define MELEE_TIER_LIGHT ARMOR_TIER_DEFAULT
+#define MELEE_TIER_LIGHT ARMOR_TIER_BASELINE
#define MELEE_TIER_MEDIUM ARMOR_TIER_ABOVE
#define MELEE_TIER_HEAVY ARMOR_TIER_HIGH
#define MELEE_TIER_EXTREME ARMOR_TIER_OVERWHELMING
//? bullet
-#define BULLET_TIER_DEFAULT ARMOR_TIER_DEFAULT
-
-#define BULLET_TIER_LAUGHABLE ARMOR_TIER_BELOW //! super improvised rounds / pistols / whatever.
-#define BULLET_TIER_LOW ARMOR_TIER_DEFAULT //! pistols
-#define BULLET_TIER_MEDIUM ARMOR_TIER_ABOVE //! smgs
-#define BULLET_TIER_HIGH ARMOR_TIER_HIGH //! rifles
-#define BULLET_TIER_EXTREME ARMOR_TIER_OVERWHELMING //! lmgs, light mech weapons
-#define BULLET_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS //! heavy mech weapons
+#define BULLET_TIER_DEFAULT BULLET_TIER_MEDIUM
+
+/// super improvised rounds / pistols / whatever.
+#define BULLET_TIER_LAUGHABLE ARMOR_TIER_BELOW
+/// pistols
+#define BULLET_TIER_LOW ARMOR_TIER_BASELINE
+/// smgs
+#define BULLET_TIER_MEDIUM ARMOR_TIER_ABOVE
+/// rifles
+#define BULLET_TIER_HIGH ARMOR_TIER_HIGH
+/// lmgs, light mech weapons
+#define BULLET_TIER_EXTREME ARMOR_TIER_OVERWHELMING
+/// heavy mech weapons
+#define BULLET_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS
//? laser
-#define LASER_TIER_DEFAULT ARMOR_TIER_DEFAULT
-
-#define LASER_TIER_LAUGHABLE ARMOR_TIER_BELOW //! improvised laser focis / etc
-#define LASER_TIER_LOW ARMOR_TIER_DEFAULT //! low tier lasers
-#define LASER_TIER_MEDIUM ARMOR_TIER_ABOVE //! laser carbines, energy guns, etc
-#define LASER_TIER_HIGH ARMOR_TIER_HIGH //! x-ray rifles, snipers
-#define LASER_TIER_EXTREME ARMOR_TIER_OVERWHELMING //! mech weapons, usualy
-#define LASER_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS //! power transmission laser?
+#define LASER_TIER_DEFAULT LASER_TIER_MEDIUM
+
+/// improvised laser focis / etc
+#define LASER_TIER_LAUGHABLE ARMOR_TIER_BELOW
+/// low tier lasers
+#define LASER_TIER_LOW ARMOR_TIER_BASELINE
+/// laser carbines, energy guns, etc
+#define LASER_TIER_MEDIUM ARMOR_TIER_ABOVE
+/// x-ray rifles, snipers
+#define LASER_TIER_HIGH ARMOR_TIER_HIGH
+/// mech weapons, usualy
+#define LASER_TIER_EXTREME ARMOR_TIER_OVERWHELMING
+/// power transmission laser?
+#define LASER_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS
//? --- armor calculations ---
diff --git a/code/__DEFINES/combat/attack_types.dm b/code/__DEFINES/combat/attack_types.dm
index 26989b0a23c8..a0b4176d80ea 100644
--- a/code/__DEFINES/combat/attack_types.dm
+++ b/code/__DEFINES/combat/attack_types.dm
@@ -9,3 +9,10 @@
#define ATTACK_TYPE_THROWN (1<<2)
/// damage source is /mob
#define ATTACK_TYPE_UNARMED (1<<3)
+/// we're being contacted by something
+///
+/// * used internally by parry frames, mostly
+/// * damage source is null
+#define ATTACK_TYPE_TOUCH (1<<4)
+/// a damage instance created by a block / parry frame transmuting damage and passing it to the user
+#define ATTACK_TYPE_DEFENSIVE_PASSTHROUGH (1<<5)
diff --git a/code/__DEFINES/combat/explosions.dm b/code/__DEFINES/combat/explosions.dm
index c8709ccfd8a3..a60badb34c03 100644
--- a/code/__DEFINES/combat/explosions.dm
+++ b/code/__DEFINES/combat/explosions.dm
@@ -22,10 +22,9 @@
#define LEGACY_EXPLOSION_SEVERE_POWER EXPLOSION_CONSTANT_SEVERE
#define LEGACY_EXPLOSION_MINOR_POWER EXPLOSION_CONSTANT_MINOR
-#define LEGACY_EXPLOSION_DEVASTATE_INTEGRITY 1000
+#define LEGACY_EXPLOSION_DEVASTATE_INTEGRITY 500
#define LEGACY_EXPLOSION_SEVERE_INTEGRITY 180
#define LEGACY_EXPLOSION_MINOR_INTEGRITY 50
-#define LEGACY_EXPLOSION_INTEGRITY_MULT (0.01 * rand(50, 200))
// why the extra numbers? so if someone does weird math we don't out of bounds
GLOBAL_REAL(_legacy_expowers, /list) = list(
@@ -47,7 +46,6 @@ GLOBAL_REAL(_legacy_ex_atom_damage, /list) = list(
0
)
-#define LEGACY_EXPLOSION_ATOM_DAMAGE(P) (global._legacy_ex_atom_damage[P] * LEGACY_EXPLOSION_INTEGRITY_MULT)
// this works out becuase epxlosions are 1-3 in legacy, so we can just use it as list indices
#define LEGACY_EX_ACT(ATOM, POWER, TARGET) ATOM.legacy_ex_act(POWER, TARGET); ATOM.ex_act(_legacy_expowers[POWER]);
diff --git a/code/__DEFINES/combat/shieldcall.dm b/code/__DEFINES/combat/shieldcall.dm
index 889d27cad26f..965076dd8566 100644
--- a/code/__DEFINES/combat/shieldcall.dm
+++ b/code/__DEFINES/combat/shieldcall.dm
@@ -1,44 +1,198 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
-//? keys for list/additional in atom_shieldcall
+//* shieldcall return status *//
-// None yet
+/// terminate; either fully mitigated or we're done here
+#define SHIELDCALL_FLAG_TERMINATE (1<<0)
+/// terminate attacker swing entirely
+///
+/// * usually you don't want this
+#define SHIELDCALL_FLAG_CANCEL_SWING (1<<1)
+/// stop attack effects
+///
+/// * this is basically the [PROJECTILE_IMPACT_BLOCKED] for shieldcalls
+/// * the thing hitting won't do direct damage but aftereffects like exploding rounds still explode
+#define SHIELDCALL_FLAG_ATTACK_BLOCKED (1<<2)
+/// attack redirected entirely
+#define SHIELDCALL_FLAG_ATTACK_REDIRECT (1<<3)
+/// attack goes through
+///
+/// * both this and REDIRECT should be used if the original attack should keep going
+/// * also use SHIELDCALL_FLAG_ATTACK_BLOCKED if original attack shouldn't process! otherwise it might be a pierce.
+/// * example: reflecting a bullet
+#define SHIELDCALL_FLAG_ATTACK_PASSTHROUGH (1<<4)
+/// this attack already invoked a 'specialized' shieldcall proc, and is now invoking
+/// the generalized atom_shieldcall() proc.
+#define SHIELDCALL_FLAG_SECOND_CALL (1<<5)
+/// asks the shieldcall nicely to not make a message
+#define SHIELDCALL_FLAG_SUPPRESS_MESSAGE (1<<6)
+/// asks the shieldcall nicely to not make a sound
+#define SHIELDCALL_FLAG_SUPPRESS_SOUND (1<<7)
+/// do not call armorcalls
+#define SHIELDCALL_FLAG_SKIP_ARMORCALLS (1<<8)
+/// do not call shieldcalls
+#define SHIELDCALL_FLAG_SKIP_SHIELDCALLS (1<<9)
+/// this is a passive / single parry
+#define SHIELDCALL_FLAG_SINGLE_PARRY (1<<10)
-//? shieldcall "struct" - this *must* match up with /atom/proc/run/check_shieldcall!
+/// these flags mean to stop processing the attack
+#define SHIELDCALL_FLAGS_BLOCK_ATTACK (SHIELDCALL_FLAG_ATTACK_BLOCKED)
+/// these flags means that the attack should keep going after us, regardless of if we're hit
+#define SHIELDCALL_FLAGS_PIERCE_ATTACK (SHIELDCALL_FLAG_ATTACK_PASSTHROUGH)
+/// stop shieldcall chain
+#define SHIELDCALL_FLAGS_SHOULD_TERMINATE (SHIELDCALL_FLAG_TERMINATE)
+/// these flags means something happens / should happen
+#define SHIELDCALL_FLAGS_SHOULD_PROCESS (SHIELDCALL_FLAGS_BLOCK_ATTACK | SHIELDCALL_FLAGS_PIERCE_ATTACK)
+
+/// flags set in a projectile reflect
+///
+/// * you should be using /datum/shieldcall's bullet intercept / bullet signals if possible but this works too
+#define SHIELDCALL_FLAGS_FOR_PROJECTILE_DEFLECT (SHIELDCALL_FLAG_TERMINATE | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_PASSTHROUGH)
+/// flags set in a full block
+#define SHIELDCALL_FLAGS_FOR_COMPLETE_BLOCK (SHIELDCALL_FLAG_TERMINATE | SHIELDCALL_FLAG_ATTACK_BLOCKED)
+
+//* Atom Shieldcall Args *//
+//* *//
+//* The shieldcall system is a very low-level 'damage instance' interception API. *//
+//* It's used by the shieldcalls list in atoms to perform low-level intercepts, *//
+//* as well as by default features like armor to perform their damage intercepts. *//
+//* *//
+//* For speed reasons, shieldcalls pass a single argument list down instead of *//
+//* returning new lists every call, as shieldcalls need to be able to edit many *//
+//* facets of a damage instance. *//
+//* *//
+//* Many of these are optional. *//
+//*
+//* Adding new arguments should be done sparingly.
+//* Removing arguments requires every single proc with SHIELDCALL_PROC_HEADER *//
+//* to be audited. *//
/// damage amount
#define SHIELDCALL_ARG_DAMAGE 1
/// damage type
-#define SHIELDCALL_ARG_DAMTYPE 2
+#define SHIELDCALL_ARG_DAMAGE_TYPE 2
/// damage tier
-#define SHIELDCALL_ARG_TIER 3
+#define SHIELDCALL_ARG_DAMAGE_TIER 3
/// armor flag
-#define SHIELDCALL_ARG_FLAG 4
+#define SHIELDCALL_ARG_DAMAGE_FLAG 4
/// damage mode
-#define SHIELDCALL_ARG_MODE 5
+#define SHIELDCALL_ARG_DAMAGE_MODE 5
/// attack type
-#define SHIELDCALL_ARG_TYPE 6
-/// attacking weapon datum - same as used in armor
+#define SHIELDCALL_ARG_ATTACK_TYPE 6
+/// attacking weapon datum
+///
+/// * /obj/projectile if projectile
+/// * /datum/unarmed_attack if unarmed melee
+/// * /obj/item if item melee
+/// * /datum/thrownthing if thrown
+/// * null if touch
#define SHIELDCALL_ARG_WEAPON 7
-/// list for additional data
-#define SHIELDCALL_ARG_ADDITIONAL 8
-/// flags returned
-#define SHIELDCALL_ARG_RETVAL 9
+/// flags returned from other shieldcalls
+#define SHIELDCALL_ARG_FLAGS 8
+/// hit zone; this is usually a bodypart but this is also optional
+#define SHIELDCALL_ARG_HIT_ZONE 9
+/// additional list returns; usually empty, but may exist
+#define SHIELDCALL_ARG_ADDITIONAL 10
+/// the clickchain of a melee attack
+///
+/// * this is passed in so you can grab data like who is doing it / who started the attack.
+/// * filled in sometimes but not always if unarmed or item melee.
+#define SHIELDCALL_ARG_CLICKCHAIN 11
+
+/// A proc header with all the shieldcall args.
+///
+/// * We use this so it's easy to check where shieldcall args are being used if shieldcalls need to be refactored.
+#define SHIELDCALL_PROC_HEADER damage, damage_type, damage_tier, damage_flag, damage_mode, attack_type, datum/weapon, shieldcall_flags, hit_zone, list/additional, datum/event_args/actor/clickchain/clickchain
-//? shieldcall additional data keys
+//* list keys for list/additional in atom shieldcalls *//
// none yet
-//? shieldcall return flags
-
-/// abort further shieldcalls - hit is totally blocked or mitigated
-#define SHIELDCALL_FULLY_MITIGATED (1<<0)
-/// hit was partially blocked or mitigated
-#define SHIELDCALL_PARTIALLY_MITIGATED (1<<1)
-/// attack was forcefully missed e.g. by reactive teleport armor
-#define SHIELDCALL_FORCED_MISS (1<<2)
-/// fake; this is a check
-#define SHIELDCALL_JUST_CHECKING (1<<3)
-/// terminate further shieldcalls
-#define SHIELDCALL_CEASE (1<<4)
+//* Helpers to manipulate shieldcall args *//
+
+#define RESOLVE_SHIELDCALL_ATTACK_TEXT(SHIELDCALL) resolve_shieldcall_attack_text(SHIELDCALL)
+
+/proc/resolve_shieldcall_attack_text(list/shieldcall_args)
+ switch(shieldcall_args[SHIELDCALL_ARG_ATTACK_TYPE])
+ if(ATTACK_TYPE_PROJECTILE)
+ . = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ if(ATTACK_TYPE_THROWN)
+ var/datum/thrownthing/thrown = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ if(thrown)
+ . = "the impact from [thrown.thrownthing]"
+ if(ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED)
+ . = "the force of the blow"
+
+ if(!.)
+ . = "the attack"
+
+#define RESOLVE_SHIELDCALL_WEAPON_DESCRIPTOR(SHIELDCALL) resolve_shieldcall_weapon_descriptor(SHIELDCALL)
+
+/proc/resolve_shieldcall_weapon_descriptor(list/shieldcall_args)
+ switch(shieldcall_args[SHIELDCALL_ARG_ATTACK_TYPE])
+ if(ATTACK_TYPE_MELEE)
+ var/obj/item/weapon = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ return "a [weapon]"
+ if(ATTACK_TYPE_PROJECTILE)
+ var/obj/projectile/proj = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ return "a [proj]"
+ if(ATTACK_TYPE_THROWN)
+ var/datum/thrownthing/thrown = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ return "a [thrown.thrownthing]"
+ if(ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED)
+ var/datum/unarmed_attack/style = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ return "a [style.attack_name]"
+ if(!.)
+ . = "an attack"
+
+//* handle_touch - contact_flags *//.
+
+// broad descriptors
+
+/// generally helpful actions (shake up, etc)
+#define SHIELDCALL_CONTACT_FLAG_HELPFUL (1<<0)
+/// potentially helpful but also potentially harmful actions
+#define SHIELDCALL_CONTACT_FLAG_NEUTRAL (1<<1)
+/// harmful actions
+#define SHIELDCALL_CONTACT_FLAG_HARMFUL (1<<2)
+
+// categories
+
+/// medical items/techniques
+#define SHIELDCALL_CONTACT_FLAG_MEDICAL (1<<23)
+
+//* handle_touch - contact_specific *//
+
+/// trying to inject someone
+#define SHIELDCALL_CONTACT_SPECIFIC_SYRINGE_INJECTION "inject"
+/// trying to shake someone up
+#define SHIELDCALL_CONTACT_SPECIFIC_SHAKE_UP "help"
+/// trying to drag someone
+#define SHIELDCALL_CONTACT_SPECIFIC_PULL "pull"
+/// trying to grab someone, **or** intensify a grab
+#define SHIELDCALL_CONTACT_SPECIFIC_GRAB "grab"
+/// trying to perform a surgery step - generic
+#define SHIELDCALL_CONTACT_SPECIFIC_SURGERY "surgery"
+/// being sprayed with chemicals
+#define SHIELDCALL_CONTACT_SPECIFIC_CHEMICAL_SPRAY "spray"
+
+//* handle_touch - helpers *//
+
+/**
+ * Gets text to put in say, "blocks \the [text]" when someone has a blocked touch.
+ */
+#define RESOLVE_SHIELDCALL_TOUCH_TEXT(CONTACT_FLAGS, CONTACT_SPECIFIC) resolve_shieldcall_touch_text(CONTACT_FLAGS, CONTACT_SPECIFIC)
+
+/proc/resolve_shieldcall_touch_text(flags, specific)
+ switch(specific)
+ if(SHIELDCALL_CONTACT_SPECIFIC_SYRINGE_INJECTION)
+ return "syringe"
+ if(SHIELDCALL_CONTACT_SPECIFIC_SHAKE_UP)
+ return "help-up"
+ if(SHIELDCALL_CONTACT_SPECIFIC_GRAB)
+ return "grab"
+ if(SHIELDCALL_CONTACT_SPECIFIC_SURGERY)
+ return "operation"
+ if(SHIELDCALL_CONTACT_SPECIFIC_CHEMICAL_SPRAY)
+ return "spray"
diff --git a/code/__DEFINES/datums/event_args.dm b/code/__DEFINES/datums/event_args.dm
new file mode 100644
index 000000000000..2fa589bf4e6f
--- /dev/null
+++ b/code/__DEFINES/datums/event_args.dm
@@ -0,0 +1,5 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+// make sure a var that is either event_args/actor or a single mob/user is event args; if it's not
+#define E_ARGS_WRAP_USER_TO_ACTOR(USER) USER = ismob(USER)? new /datum/event_args/actor(USER) : USER
diff --git a/code/__DEFINES/dcs/flags.dm b/code/__DEFINES/dcs/flags.dm
index 47695369dcd7..f69925f96982 100644
--- a/code/__DEFINES/dcs/flags.dm
+++ b/code/__DEFINES/dcs/flags.dm
@@ -1,12 +1,10 @@
/// Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type.
/// `parent` must not be modified if this is to be returned.
/// This will be noted in the runtime logs
-#define COMPONENT_INCOMPATIBLE (1<<0)
-/// Returned in PostTransfer to prevent transfer, similar to `COMPONENT_INCOMPATIBLE`
-#define COMPONENT_NOTRANSFER (1<<1)
+#define COMPONENT_INCOMPATIBLE -1
/// Return value to cancel attaching
-#define ELEMENT_INCOMPATIBLE (1<<0)
+#define ELEMENT_INCOMPATIBLE -1
// /datum/element flags
/// Causes the detach proc to be called when the host object is being deleted
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_buckling.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-buckling.dm
similarity index 60%
rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_buckling.dm
rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-buckling.dm
index 29d0bc4c9c04..640048265bb4 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_buckling.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-buckling.dm
@@ -1,16 +1,23 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
-////! buckling. flags are always buckling opflags, see __DEFINES/procs/buckling.dm
-/// called on mob buclked: (mob, flags, user, semantic)
+//* -- Buckling -- *//
+//* flags are always buckling opflags *//
+//* see __DEFINES/procs/buckling.dm *//
+
+//* These are called when the buckle/unbuckle op have already happened *//
+
+/// called on the atom the mob buckled to: (mob, flags, user, semantic)
#define COMSIG_MOVABLE_MOB_BUCKLED "mob_buckled"
-/// called on mob unbuckled: (mob, flags, user, semantic)
+/// called on the atom the mob unbuckled from: (mob, flags, user, semantic)
#define COMSIG_MOVABLE_MOB_UNBUCKLED "mob_unbuckled"
-//! weird names to be more distinct from movable signals
/// called on the mob that just got buckled: (mob, flags, user, semantic)
-#define COMSIG_MOB_BUCKLED "buckled"
+#define COMSIG_MOB_BUCKLED_TO "buckled"
/// called on the mob that just got unbuckled: (mob, flags, user, semantic)
-#define COMSIG_MOB_UNBUCKLED "unbuckled"
+#define COMSIG_MOB_UNBUCKLED_FROM "unbuckled"
+
+//* For the below, component_block/force_buckle_operation works to varying degrees on varying procs. *//
-//! For the below, component_block/force_buckle_operation works to varying degrees on varying procs.
/// called during mob buckling: (mob, flags, user, semantic)
#define COMSIG_MOVABLE_PRE_BUCKLE_MOB "pre_buckle_mob"
/// called during can buckle mob: (mob, flags, user, semantic)
@@ -24,15 +31,18 @@
/// called on mob resist buckle: (mob, semantic). Can't force, can only block.
#define COMSIG_MOVABLE_MOB_RESIST_BUCKLE "mob_resist_buckle"
/// called during can buckle on mob: (AM, flags, user, semantic, movable_opinion)
-#define COMSIG_MOB_CAN_BUCKLE "mob_can_buckle"
+#define COMSIG_MOB_CAN_BUCKLE_TO "mob_can_buckle"
/// called during can unbuckle on mob: (AM, flags, user, semantic, movable_opinion)
-#define COMSIG_MOB_CAN_UNBUCKLE "mob_can_unbuckle"
+#define COMSIG_MOB_CAN_UNBUCKLE_FROM "mob_can_unbuckle"
/// block mob buckle/unbuckle **silently**
- #define COMPONENT_BLOCK_BUCKLE_OPERATION (1<<0)
+ #define SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION (1<<0)
+ ///
+ /// * has priority over [SIGNAL_RAISE_FORCE_BUCKLE_OPERATION] where applicable
/// force allow buckle/unbuckled **silently**
- #define COMPONENT_FORCE_BUCKLE_OPERATION (1<<1)
+ #define SIGNAL_RAISE_FORCE_BUCKLE_OPERATION (1<<1)
+
+//* Interactions; only blocking default interactions work. *//
-//! Interactions; only blocking default interactions work.
/// called from drag_drop_buckle_interaction: (A, user)
#define COMSIG_MOVABLE_DRAG_DROP_BUCKLE_INTERACTION "drag_drop_buckle"
/// called from click_unbuckle_interaction: (user)
@@ -40,4 +50,4 @@
/// called from resist_unbuckle_interaction(M)
#define COMSIG_MOVABLE_RESIST_UNBUCKLE_INTERACTION "resist_unbuckle"
/// cancel rest of procs
- #define COMPONENT_HANDLED_BUCKLE_INTERACTION (1<<0)
+ #define SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_context_system.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-context_system.dm
similarity index 95%
rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_context_system.dm
rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-context_system.dm
index f6efa7cee461..a5fb0b6a8047 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_context_system.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-context_system.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
+//* Copyright (c) 2024 silicons *//
/// from base of /atom/proc/context_query: (list/options, datum/event_args/actor/e_args)
/// options list is the same format as /atom/proc/context_query, insert directly to it.
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm
new file mode 100644
index 000000000000..4d20c82cc267
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm
@@ -0,0 +1,37 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) Citadel Station Developers *//
+
+// todo: integrity signals?
+
+/// called from bullet_act(): (args)
+#define COMSIG_ATOM_BULLET_ACT "bullet_act"
+ #define BULLET_ACT_ARG_PROJECTILE 1
+ /// index of arg for PROJECTILE_IMPACT_* flags
+ #define BULLET_ACT_ARG_FLAGS 2
+ #define BULLET_ACT_ARG_ZONE 3
+ #define BULLET_ACT_ARG_EFFICIENCY 4
+
+/// called from run_armorcalls(): (list/shieldcall_args, fake_attack)
+///
+/// * This is an extremely low-level signal. Handle with care.
+#define COMSIG_ATOM_ARMORCALL "atom-armorcalls"
+/// called from run_shieldcalls(): (list/shieldcall_args, fake_attack)
+///
+/// * This is an extremely low-level signal. Handle with care.
+#define COMSIG_ATOM_SHIELDCALL "atom-shieldcalls"
+
+/// called from atom_shieldcall_handle_*l: (shieldcall_type)
+///
+/// * use this for stuff that should spin up a full shield when attacked but is usually inactive.
+/// * this is not used for base of /atom_shieldcall(), as it already has a signal!
+#define COMSIG_ATOM_SHIELDCALL_ITERATION "atom-shieldcall-iteration"
+ /// atom_shieldcall_handle_unarmed_melee()
+ #define ATOM_SHIELDCALL_ITERATING_UNARMED_MELEE (1<<1)
+ /// atom_shieldcall_handle_item_melee()
+ #define ATOM_SHIELDCALL_ITERATING_ITEM_MELEE (1<<2)
+ /// atom_shieldcall_handle_bullet_act()
+ #define ATOM_SHIELDCALL_ITERATING_BULLET_ACT (1<<3)
+ /// atom_shieldcall_handle_touch()
+ #define ATOM_SHIELDCALL_ITERATING_TOUCH (1<<4)
+ /// atom_shieldcall_handle_throw_impact()
+ #define ATOM_SHIELDCALL_ITERATING_THROW_IMPACT (1<<5)
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_throwing.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-throwing.dm
similarity index 79%
rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_throwing.dm
rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-throwing.dm
index 48c615ad3e2f..ae1ba120b00d 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_throwing.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-throwing.dm
@@ -1,8 +1,9 @@
-//! Wanna add hitpush signals? TOO BAD, DON'T. Modify the thrownthing datum these pass in!
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
-/// from base of /atom/proc/throw_impacted: (AM, thrownthing)
+/// from /atom/movable/proc/_throw_do_hit: (impactor, thrownthing)
#define COMSIG_ATOM_THROW_IMPACTED "throw_impacted"
-/// from base of /atom/movable/proc/throw_impact: (AM, thrownthing)
+/// from /atom/movable/proc/_throw_do_hit: (blocker, thrownthing)
#define COMSIG_MOVABLE_THROW_IMPACT "throw_impact"
// This set of returns can be for both of the above!
/// cancel further actions in this hit
@@ -12,9 +13,9 @@
/// completely terminate throw silently immediately. Use if you're deleting the atom.
#define COMPONENT_THROW_HIT_TERMINATE (1<<2)
-/// called on throws landing on something: (landed_on, thrownthing)
+/// from /atom/movable/proc/_throw_finalize: (landed_on, thrownthing)
#define COMSIG_MOVABLE_THROW_LAND "throw_land"
-/// called on something landing on us from a throw
+/// from /atom/movable/proc/_throw_finalize: (landing_movable, thrownthing)
#define COMSIG_ATOM_THROW_LANDED "throw_landed"
// This set of returns can be for both of the above!
/// cancel further actions
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_tool_system.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-tool_system.dm
similarity index 91%
rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_tool_system.dm
rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-tool_system.dm
index 5d9c7ab9c1de..3bb7af39fc40 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_tool_system.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-tool_system.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
+//* Copyright (c) 2024 silicons *//
/// from base of _tool_act: (I, user, function, flags, hint) where I = item, e_args = clickchain data, function = tool behaviour, flags = tool operation flags, hint = set by dynamic tool system
/// return CLICKCHAIN_COMPONENT_SIGNAL_HANDLED to abort normal tool_act handling.
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm
deleted file mode 100644
index 89d0a90cfe37..000000000000
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
-
-// todo: this file left intentionally empty since shieldcalls were moved to datums
-// todo: add signals for integrity
diff --git a/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm b/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm
index 070449036739..77773426f55c 100644
--- a/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm
+++ b/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm
@@ -1,10 +1,10 @@
-/// From base of obj/item/dropped: (mob/user, flags, atom/newLoc)
+/// From base of obj/item/dropped: (mob/user, inv_op_flags, atom/new_loc)
#define COMSIG_ITEM_DROPPED "item_drop"
#define COMPONENT_ITEM_DROPPED_RELOCATE (1<<0)
#define COMPONENT_ITEM_DROPPED_SUPPRESS_SOUND (1<<1)
-/// From base of obj/item/pickup: (mob/user, flags, atom/oldLoc)
+/// From base of obj/item/pickup: (mob/user, inv_op_flags, atom/old_loc)
#define COMSIG_ITEM_PICKUP "item_pickup"
-/// From base of obj/item/equipped(): (/mob/equipper, slot, accessory)
+/// From base of obj/item/equipped(): (/mob/equipper, slot, inv_op_flags)
#define COMSIG_ITEM_EQUIPPED "item_equip"
-/// From base of obj/item/unequipped(): (/mob/unequipped, slot, accessory)
+/// From base of obj/item/unequipped(): (/mob/unequipped, slot, inv_op_flags)
#define COMSIG_ITEM_UNEQUIPPED "item_unequip"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_inventory.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-inventory.dm
similarity index 85%
rename from code/__DEFINES/dcs/signals/signals_mob/signals_mob_inventory.dm
rename to code/__DEFINES/dcs/signals/signals_mob/signals_mob-inventory.dm
index 1aff02f288d3..ffdc89ec7470 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_inventory.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-inventory.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
/// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot, inv_op_flags)
#define COMSIG_MOB_ITEM_EQUIPPED "mob_equipped_item"
/// A mob has just unequipped an item. Called on [/mob] from base of [/obj/item/unequipped()]: (/obj/item/equipped_item, slot, inv_op_flags)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm
new file mode 100644
index 000000000000..971e9b9a00d6
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm
@@ -0,0 +1,7 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/// emitted by reset_perspective, but only if the perspective needed switching: (perspective)
+#define COMSIG_MOB_RESET_PERSPECTIVE "reset_perspective"
+/// emitted by update_perspective: ()
+#define COMSIG_MOB_UPDATE_PERSPECTIVE "update_perspective"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm
new file mode 100644
index 000000000000..7e43f572821d
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm
@@ -0,0 +1,13 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+// todo: burn all this shit with fire
+
+/// used by passive parry to detect
+/// the entire mob item attack system is a dumpster fire and needs rewritten
+/// for now, this is a signal with (item, user, hit_zone)
+#define COMSIG_MOB_LEGACY_RESOLVE_ITEM_ATTACK "legacy-mob-item-resolve-attack"
+/// used by passive parry to detect
+/// signal with (user, list/params)
+/// :skull:
+#define COMSIG_MOB_LEGACY_ATTACK_HAND_INTERCEPT "legacy-mob-legacy-attack-hand"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm
deleted file mode 100644
index ecaa7708fb5e..000000000000
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm
+++ /dev/null
@@ -1,4 +0,0 @@
-/// emitted by reset_perspective, but only if the perspective needed switching: (perspective)
-#define COMSIG_MOB_RESET_PERSPECTIVE "reset_perspective"
-/// emitted by update_perspective: ()
-#define COMSIG_MOB_UPDATE_PERSPECTIVE "update_perspective"
diff --git a/code/__DEFINES/event_args.dm b/code/__DEFINES/event_args.dm
deleted file mode 100644
index cfbbc03c37c3..000000000000
--- a/code/__DEFINES/event_args.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-//? for /datum/event_args/actor
-
-#define WRAP_MOB_TO_ACTOR_EVENT_ARGS(VARNAME) VARNAME = ismob(VARNAME)? new /datum/event_args/actor(VARNAME) : VARNAME
-
-//? for /datum/event_args/actor/clickchain
-
-#define WRAP_MOB_TO_CLICKCHAIN_EVENT_ARGS(VARNAME) VARNAME = ismob(VARNAME)? new /datum/event_args/actor/clickchain(VARNAME) : VARNAME
diff --git a/code/__DEFINES/loadout.dm b/code/__DEFINES/loadout.dm
index 97541ba9432d..e8fcbfcf7f4a 100644
--- a/code/__DEFINES/loadout.dm
+++ b/code/__DEFINES/loadout.dm
@@ -33,9 +33,9 @@ DEFINE_BITFIELD(loadout_customize_flags, list(
))
// *DO NOT RAISE THIS. This is for performance reasons.* //
-#define LOADOUT_MAX_SLOTS 10
+#define LOADOUT_MAX_SLOTS 16
#define LOADOUT_SLOT_NAME_LENGTH 32
-#define LOADOUT_MAX_ITEMS 30
+#define LOADOUT_MAX_ITEMS 32
/// Used in chargen for accessory loadout limit.
#define LOADOUT_MAX_COST 20
/// Used in chargen for accessory loadout limit on holidays.
diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm
index d23d6c4f7d88..4fe4d17dee5e 100644
--- a/code/__DEFINES/math.dm
+++ b/code/__DEFINES/math.dm
@@ -2,13 +2,22 @@
// This file is quadruple wrapped for your pleasure
// (
+/// The value of the mathematical constant 'e'
#define NUM_E 2.71828183
-
+/// The value of sqrt(2); defined for speed
#define SQRT_2 1.414214
-
-#define M_PI (3.14159265)
-///closer then enough
-#define INFINITY (1.#INF)
+/// The value of the mathematical constant 'Pi'
+#define M_PI 3.14159265
+
+/// A quick way to write (1.#INF)
+#define INFINITY (1.#INF)
+
+/// the highest number that does not lose precision when only using the one's place
+///
+/// * floating point has serious inaccuracies; after this limit, we can no longer track to one's place
+/// * you usually don't have to worry about this if you're not writing anything that requires accuracy
+/// * if you are, note that accuracy is lost even below this limit for fractionals.
+/// * please look up how IEEE single precision floats work for more details.
#define SHORT_REAL_LIMIT 16777216
//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks
@@ -28,13 +37,21 @@
#define SIGN(x) ( (x)!=0 ? (x) / abs(x) : 0 )
/// ceil()
+//
+// todo: get rid of this, this is native now
#define ROUND_UP(x) ( -round(-(x)))
/// floor()
+//
+// todo: get rid of this, this is native now
#define ROUND_DOWN(x) (round(x))
-// x to the nearest higher multiple of y
+/// x to the nearest higher multiple of y
+///
+/// * This is not replaced by native ceil(), as that is always CEILING(x, 1)!
#define CEILING(x, y) ( -round(-(x) / (y)) * (y) )
-// x to the nearest lower multiple of y
+/// x to the nearest lower multiple of y
+///
+/// * This is not replaced by native floor(), as that is always FLOOR(x, 1)!
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
// Similar to clamp but the bottom rolls around to the top and vice versa. min is inclusive, max is exclusive
@@ -43,16 +60,15 @@
// Real modulus that handles decimals
#define MODULUS_F(x, y) ( (x) - (y) * round((x) / (y)) )
-// Cotangent
+/// Cotangent
#define COT(x) (1 / tan(x))
-
-// Secant
+/// Secant
#define SEC(x) (1 / cos(x))
-
-// Cosecant
+/// Cosecant
#define CSC(x) (1 / sin(x))
// ArcTan2. Returns the degree between two points in an x and y system.
+// todo: get rid of this, this is native now
/proc/arctantwo(x1,y1,x2,y2)
var/dx = x2-x1
var/dy = y2-y1
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 34d76cb7f739..eb8d894025cf 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -192,14 +192,6 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#define NTOS_EMAIL_NOTIFALREADY 1
#define NTOS_EMAIL_NEWMESSAGE 2
-
-// Special return values from bullet_act(). Positive return values are already used to indicate the blocked level of the projectile.
-/// If the projectile should continue flying after calling bullet_act()
-#define PROJECTILE_CONTINUE -1
-/// If the projectile should treat the attack as a miss (suppresses attack and admin logs) - only applies to mobs.
-#define PROJECTILE_FORCE_MISS -2
-
-
// Vending stuff
#define CAT_NORMAL 1
#define CAT_HIDDEN 2
@@ -275,8 +267,6 @@ var/list/economy_station_departments = list(
///The number of deciseconds in a day
#define MIDNIGHT_ROLLOVER 864000
-///Needed for the R-UST port
-#define PIXEL_MULTIPLIER WORLD_ICON_SIZE/32
/// Maximum effective value of client.view (According to DM references)
#define MAX_CLIENT_VIEW 34
diff --git a/code/__DEFINES/procs/clickcode.dm b/code/__DEFINES/procs/clickcode.dm
index a064ae31258b..5eaa74894e1a 100644
--- a/code/__DEFINES/procs/clickcode.dm
+++ b/code/__DEFINES/procs/clickcode.dm
@@ -19,7 +19,9 @@
*/
/// stop the click chain from proceeding past this point; usually used if we're deleting or being inserted
-/// DO NOT ABUSE THIS PROC TO INTERRUPT AFTERATTACKS WITHOUT CARE; this is NOT what this is here for!
+///
+/// * This is an unconditional abort.
+/// * DO NOT ABUSE THIS PROC TO INTERRUPT AFTERATTACKS WITHOUT CARE; this is NOT what this is here for!
#define CLICKCHAIN_DO_NOT_PROPAGATE (1<<0)
/// person can reach us normally
#define CLICKCHAIN_HAS_PROXIMITY (1<<1)
@@ -38,6 +40,17 @@
#define CLICKCHAIN_DO_NOT_ATTACK (1<<7)
/// intercepted by component
#define CLICKCHAIN_COMPONENT_SIGNAL_HANDLED (1<<8)
+/// this is a reflex counterattack by something
+///
+/// * used to prevent loops where both parties reactively attack each other instantly.
+#define CLICKCHAIN_REFLEX_COUNTER (1<<9)
+/// put this in if we should entirely abort the attack
+#define CLICKCHAIN_FULL_BLOCKED (1<<10)
+
+/// check these for 'unconditional abort'
+#define CLICKCHAIN_FLAGS_UNCONDITIONAL_ABORT (CLICKCHAIN_DO_NOT_PROPAGATE)
+/// check these for 'abort attack'
+#define CLICKCHAIN_FLAGS_ATTACK_ABORT (CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_FULL_BLOCKED)
//! Reachability Depths - checked from level of DirectAccess and turf adjacency.
/// default reachability depth
diff --git a/code/__DEFINES/projectiles/projectile.dm b/code/__DEFINES/projectiles/projectile.dm
new file mode 100644
index 000000000000..f87cff191198
--- /dev/null
+++ b/code/__DEFINES/projectiles/projectile.dm
@@ -0,0 +1,119 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* pre_impact(), impact(), bullet_act(), on_impact() impact_flags *//
+/// pre_impact, bullet_act, on_impact are called in that order ///
+
+/// pointblank hit
+#define PROJECTILE_IMPACT_POINT_BLANK (1<<0)
+/// piercing hit; if returned, forces pierce for current impact
+///
+/// * projectile has the right to perform special behavior like reducing damage after the impact
+#define PROJECTILE_IMPACT_PIERCE (1<<1)
+/// was blocked from directly hitting target
+///
+/// * on impact probably shouldn't do direct damage, but explosive rounds will explode, etc
+#define PROJECTILE_IMPACT_BLOCKED (1<<2)
+/// if sensing this flag, **immediately** destroy the projectile without elaboration
+///
+/// * overrides everything else
+#define PROJECTILE_IMPACT_DELETE (1<<3)
+/// do not hit; if this is present, we phase through without interaction
+///
+/// * bullet_act(), and on_impact() will be cancelled by this.
+/// * on_phase() is called instead to allow for standard hooks to fire
+#define PROJECTILE_IMPACT_PHASE (1<<4)
+/// signifies that the projectile is reflected.
+///
+/// * projectile is not deleted like in PIERCING or PHASE
+/// * fires off on_reflect()
+#define PROJECTILE_IMPACT_REFLECT (1<<5)
+/// we should pass through without interaction
+///
+/// * bullet_act(), on_impact(), on_reflect(), and on_phase() will all be cancelled by this.
+#define PROJECTILE_IMPACT_PASSTHROUGH (1<<6)
+/// instructs piercing projectiles that support this
+/// to not reduce damage because the impact was so trivial
+/// compared to the force of the projectile
+#define PROJECTILE_IMPACT_TRIVIAL (1<<7)
+/// aborting duplicate impact due to already being in impacted list of projectile
+#define PROJECTILE_IMPACT_DUPLICATE (1<<8)
+/// passed from another bullet_act(),
+/// like from a target stake to the mounted target
+#define PROJECTILE_IMPACT_INDIRECTED (1<<9)
+/// used by /impact() on projectile to signal to impact_loop()
+/// that the projectile should keep impacting everything on the turf it was trying to hit
+#define PROJECTILE_IMPACT_CONTINUE_LOOP (1<<10)
+/// target was deleted, stop processing on target side but not projectile side
+#define PROJECTILE_IMPACT_TARGET_DELETED (1<<11)
+/// this is an impact (usually on the ground) from a projectile expiring
+#define PROJECTILE_IMPACT_IS_EXPIRING (1<<12)
+/// requests that no sound is made
+///
+/// * this is separate from suppression / silencing projectile-side!
+/// * notably, this is an absolute that should be always obeyed; while things like special round effects can ignore normal suppression.
+#define PROJECTILE_IMPACT_SUPPRESS_SOUND (1<<13)
+/// requests that no message is made
+///
+/// * this is separate from suppression / silencing projectile-side!
+/// * notably, this is an absolute that should be always obeyed; while things like special round effects can ignore normal suppression.
+#define PROJECTILE_IMPACT_SUPPRESS_MESSAGE (1<<14) // phasing?
+/// do not process damage normally
+///
+/// * stops generic 'inflict damage instance' from being procced automatically.
+#define PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE (1<<15)
+
+/// any of these means the projectile should delete immediately
+#define PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE (PROJECTILE_IMPACT_DELETE)
+/// any of these means the projectile should not impact
+#define PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT (PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH)
+/// any of these means don't just delete after hit
+#define PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH (PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH | PROJECTILE_IMPACT_PIERCE)
+/// any of these means the projectile should abort bullet_act
+#define PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT (PROJECTILE_IMPACT_DELETE | PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH)
+/// any of these means the projectile should abort bullet_act, but not on_impact() for the projectile
+#define PROJECTILE_IMPACT_FLAGS_TARGET_ABORT (PROJECTILE_IMPACT_DELETE | PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH | PROJECTILE_IMPACT_TARGET_DELETED)
+
+//* projectile_type bitfield *//
+
+//? base types; all projectiles should have one of these ?//
+
+/// kinetic matter, basically
+#define PROJECTILE_TYPE_KINETIC (1<<0)
+/// energy projectiles that aren't a beam
+#define PROJECTILE_TYPE_ENERGY (1<<1)
+/// particle beam, basically
+#define PROJECTILE_TYPE_BEAM (1<<2)
+
+//? specific types; projectiles may have one or more of these in addition to the above ?//
+
+/// photonic energy, basically (yes yes lasers are unrealistic i don't care)
+#define PROJECTILE_TYPE_PHOTONIC (1<<22)
+/// exotic energy or exotic matter
+#define PROJECTILE_TYPE_EXOTIC (1<<23)
+
+//? special types
+
+/// trace projectile, aka "always let this through shields so stuff knows to fire at it"
+#define PROJECTILE_TYPE_TRACE (1<<24)
+
+DEFINE_BITFIELD_NEW(projectile_types, list(
+ /obj/projectile = list(
+ "projectile_type",
+ ),
+ /obj/structure/prop/prism = list(
+ "projectile_type",
+ "projectile_type_cant",
+ ),
+), list(
+ BITFIELD_NEW("Base - Kinetic", PROJECTILE_TYPE_KINETIC),
+ BITFIELD_NEW("Base - Energy", PROJECTILE_TYPE_ENERGY),
+ BITFIELD_NEW("Base - Beam", PROJECTILE_TYPE_BEAM),
+ BITFIELD_NEW("Flag - Photonic Energy", PROJECTILE_TYPE_PHOTONIC),
+ BITFIELD_NEW("Flag - Exotic Energy / Matter", PROJECTILE_TYPE_EXOTIC),
+))
+
+//* helpers *//
+
+/// tiles per second to pixels per decisecond
+#define PROJECTILE_SPEED_FOR_TPS(tiles) (tiles * WORLD_ICON_SIZE)
diff --git a/code/__HELPERS/game/combat/arc.dm b/code/__HELPERS/game/combat/arc.dm
new file mode 100644
index 000000000000..e51718e2f66b
--- /dev/null
+++ b/code/__HELPERS/game/combat/arc.dm
@@ -0,0 +1,58 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/**
+ * Checks if an attacking atom is within the defensive arc of a defending atom.
+ *
+ * * This does not support pixel movement.
+ * * A null source is always inside defensive arc.
+ *
+ * todo: verify behavior.
+ *
+ * Attacking entity can be:
+ *
+ * * /obj/projectile - treated as projectile
+ * * /datum/thrownthing - treated as thrown
+ * * anything else - treated as an /atom-ish source.
+ *
+ * @params
+ * * defending - the defending atom
+ * * attacking - the attacking entity
+ * * arc - the arc to check
+ * * round_up_arc - if the attacking atom is not a projectile or something with angle sim, should we round their defensive angle up or down?
+ * * use_dir - use this dir, not the defending atom's dir
+ *
+ * @return TRUE if they're within arc, FALSE otherwise
+ */
+/proc/check_defensive_arc_tile(atom/defending, attacking, arc, round_up_arc, use_dir = defending.dir)
+ // clockwise from north
+ var/our_angle = dir2angle(use_dir)
+ // clockwise from north
+ var/their_angle
+ if(istype(attacking, /obj/projectile))
+ // projectile source
+ var/obj/projectile/proj = attacking
+ // projectile angle var is clockwise from north
+ // turn it around to get the angle from our PoV
+ their_angle = (proj.angle + 180) % 360
+ else
+ // atom-ish source
+ var/atom/atom_source
+ if(istype(attacking, /datum/thrownthing))
+ var/datum/thrownthing/thrown = attacking
+ atom_source = thrown.thrownthing
+ else if(isatom(attacking))
+ atom_source = attacking
+ else
+ return TRUE
+ their_angle = dir2angle(get_dir(defending, atom_source))
+ // if we're rounding up our arc, we boost our arc since it's an atom source to nearest 45
+ if(round_up_arc)
+ arc = CEILING(arc, 45)
+ // normalize it to +- of our angle
+ their_angle -= our_angle
+ if(their_angle > 180)
+ their_angle -= 360
+ return abs(their_angle) <= arc
+
+// todo: pixel movement variant for overmaps and others.
diff --git a/code/__HELPERS/lists/string.dm b/code/__HELPERS/lists/string.dm
index c6317712253e..2acdf2a131b7 100644
--- a/code/__HELPERS/lists/string.dm
+++ b/code/__HELPERS/lists/string.dm
@@ -1,8 +1,10 @@
/**
* Returns a list in plain english as a string.
+ *
+ * * input - (optional) list or null; if null, we use empty_text
*/
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
- var/total = input.len
+ var/total = length(input)
if (!total)
return "[nothing_text]"
else if (total == 1)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index f9f5a1e3a709..5851e9a63a31 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -2,7 +2,7 @@
return
/obj/vehicle/get_mob()
- return occupants
+ return SAFEPICK(occupants)
/obj/vehicle_old/train/get_mob()
return SAFEPICK(buckled_mobs)
diff --git a/code/__HELPERS/type2type/type2type.dm b/code/__HELPERS/type2type/type2type.dm
index ca1a90903354..5b82e5f88c12 100644
--- a/code/__HELPERS/type2type/type2type.dm
+++ b/code/__HELPERS/type2type/type2type.dm
@@ -133,7 +133,9 @@
return 10
/**
- * Converts an angle (degrees) into an ss13 direction.
+ * Gets the direction of an angle, in degrees, that is clockwise of north
+ *
+ * todo: verify math and do documentation
*
* @params
* * degree - angle clockwise of north
@@ -154,10 +156,12 @@
return SOUTHWEST
if (degree < 315)
return WEST
- return NORTH|WEST
+ return NORTHWEST
/**
- * Returns the north-zero clockwise angle in degrees, given a direction.
+ * Gets the angle, in degrees, clockwise of north of a direction
+ *
+ * @return angle
*/
/proc/dir2angle(direction)
switch (direction)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index cbfb0473ff91..20b933d326dc 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -997,7 +997,7 @@
return FALSE
if(/obj/item/pickaxe/plasmacutter)
return 3800
- if(/obj/item/melee/energy)
+ if(/obj/item/melee/transforming/energy)
return 3500
else
return FALSE
diff --git a/code/__global_init.dm b/code/__global_init.dm
index 97bb1f766de0..b9b52380fb0d 100644
--- a/code/__global_init.dm
+++ b/code/__global_init.dm
@@ -33,3 +33,15 @@ var/datum/world_debug_enabler/world_debug_enabler = new
if (debug_server)
LIBCALL(debug_server, "auxtools_init")()
enable_debugging()
+ debug_loop()
+
+/datum/world_debug_enabler/proc/debug_loop()
+ set waitfor = FALSE
+ debug_loop_impl()
+
+/**
+ * the sole job of this is keep ticking so the debug server can still do stuff while no clients are conencted
+ */
+/datum/world_debug_enabler/proc/debug_loop_impl()
+ while(TRUE)
+ sleep(world.tick_lag)
diff --git a/code/controllers/subsystem/ai_movement.dm b/code/controllers/subsystem/ai_movement.dm
index d9540ab26a03..619744ad82fb 100644
--- a/code/controllers/subsystem/ai_movement.dm
+++ b/code/controllers/subsystem/ai_movement.dm
@@ -83,15 +83,14 @@ SUBSYSTEM_DEF(ai_movement)
var/reschedule_delay = being_processed.move(++being_processed.movement_cycle)
// check if we are still ticking; if not, we got ejected, so we abort as we don't need to eject or insert again
if(buckets[bucket_offset] == being_processed)
- // eject; we don't change being_processed.ticking_(next|previous)
- if(being_processed.movement_bucket_next == being_processed)
- buckets[bucket_offset] = null
- else
- buckets[bucket_offset] = being_processed.movement_bucket_next
- being_processed.movement_bucket_next.movement_bucket_prev = being_processed.movement_bucket_prev
- being_processed.movement_bucket_prev.movement_bucket_next = being_processed.movement_bucket_next
-
if(reschedule_delay)
+ // eject; we don't change being_processed.ticking_(next|previous)
+ if(being_processed.movement_bucket_next == being_processed)
+ buckets[bucket_offset] = null
+ else
+ buckets[bucket_offset] = being_processed.movement_bucket_next
+ being_processed.movement_bucket_next.movement_bucket_prev = being_processed.movement_bucket_prev
+ being_processed.movement_bucket_prev.movement_bucket_next = being_processed.movement_bucket_next
// insert; we now set its ticking_(next|previous)
// note that we don't do catchup
var/inject_offset = ((now_index_raw + round(DS2TICKS(reschedule_delay))) % bucket_amount) + 1
diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm
deleted file mode 100644
index 9622e0214692..000000000000
--- a/code/controllers/subsystem/processing/fastprocess.dm
+++ /dev/null
@@ -1,6 +0,0 @@
-//Fires five times every second.
-
-PROCESSING_SUBSYSTEM_DEF(fastprocess)
- name = "Fast Processing"
- wait = 2
- stat_tag = "FP"
diff --git a/code/controllers/subsystem/processing/process_20fps.dm b/code/controllers/subsystem/processing/process_20fps.dm
new file mode 100644
index 000000000000..3afd5b018d4d
--- /dev/null
+++ b/code/controllers/subsystem/processing/process_20fps.dm
@@ -0,0 +1,7 @@
+/**
+ * Fires 20 times a second. Kind of on the nose, huh?
+ */
+PROCESSING_SUBSYSTEM_DEF(process_20fps)
+ name = "Processing - 20 fps"
+ wait = 0.5
+ stat_tag = "P20"
diff --git a/code/controllers/subsystem/processing/process_5fps.dm b/code/controllers/subsystem/processing/process_5fps.dm
new file mode 100644
index 000000000000..05dc2d48d895
--- /dev/null
+++ b/code/controllers/subsystem/processing/process_5fps.dm
@@ -0,0 +1,7 @@
+/**
+ * Fires 5 times a second. Kind of on the nose, huh?
+ */
+PROCESSING_SUBSYSTEM_DEF(process_5fps)
+ name = "Processing - 5 FPS"
+ wait = 2
+ stat_tag = "P5"
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index 007def420e51..edab5fc5e61f 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -390,6 +390,9 @@ SUBSYSTEM_DEF(throwing)
/**
* get damage scaling - default handling
+ *
+ * @params
+ * * target - (optional) thing being hit
*/
/datum/thrownthing/proc/get_damage_multiplier(atom/target)
if(!resist)
diff --git a/code/__HELPERS/datastructs/armor.dm b/code/datums/armor/armor.dm
similarity index 96%
rename from code/__HELPERS/datastructs/armor.dm
rename to code/datums/armor/armor.dm
index 929eb064b56a..7465bc2fb1e4 100644
--- a/code/__HELPERS/datastructs/armor.dm
+++ b/code/datums/armor/armor.dm
@@ -184,6 +184,16 @@
else
return 0
+/**
+ * The big, bad proc that deals with inbound shieldcalls.
+ */
+/datum/armor/proc/handle_shieldcall(list/shieldcall_args, fake_attack)
+ shieldcall_args[SHIELDCALL_ARG_DAMAGE] = resultant_damage(
+ shieldcall_args[SHIELDCALL_ARG_DAMAGE],
+ shieldcall_args[SHIELDCALL_ARG_DAMAGE_TIER],
+ shieldcall_args[SHIELDCALL_ARG_DAMAGE_FLAG],
+ )
+
/datum/armor/proc/resultant_damage(damage, tier, flag)
switch(flag)
if(ARMOR_MELEE)
diff --git a/code/datums/callback.dm b/code/datums/callback.dm
index 721d050b5c39..2087fd6ef448 100644
--- a/code/datums/callback.dm
+++ b/code/datums/callback.dm
@@ -70,6 +70,9 @@
if(usr)
user = WEAKREF(usr)
+/datum/callback/proc/operator""()
+ return "callback [object] ([ref(object)])[isdatum(object) ? " ([object.type])" : ""] args: \[[english_list(arguments)]\]"
+
/**
* Invoke this callback
*
@@ -103,6 +106,19 @@
return call(delegate)(arglist(calling_arguments))
return call(object, delegate)(arglist(calling_arguments))
+/**
+ * Invoke this callback and crash if it sleeps.
+ *
+ * * Use when a callback should never sleep, as call() cannot be verified by static analysis.
+ */
+/datum/callback/proc/invoke_no_sleep(...)
+ . = CALLBACK_SLEEP_SENTINEL
+ ASYNC
+ . = Invoke(arglist(args))
+ if(. == CALLBACK_SLEEP_SENTINEL)
+ . = null
+ CRASH("Callback [src] slept on a no-sleeping invoke.")
+
/**
* Invoke this callback async (waitfor=false)
*
diff --git a/code/datums/components/atoms/fishing_spot.dm b/code/datums/components/atoms/fishing_spot.dm
index 77e800f7d878..3e43fd412dc4 100644
--- a/code/datums/components/atoms/fishing_spot.dm
+++ b/code/datums/components/atoms/fishing_spot.dm
@@ -1,12 +1,12 @@
// A thing you can fish in
/datum/component/fishing_spot
registered_type = /datum/component/fishing_spot
-
+
/// Defines the probabilities and fish availibilty
var/datum/fish_source/fish_source
/datum/component/fishing_spot/Initialize(configuration)
- if(!isatom(parent) || ((. = ..()) & COMPONENT_INCOMPATIBLE))
+ if(!isatom(parent) || ((. = ..()) == COMPONENT_INCOMPATIBLE))
return COMPONENT_INCOMPATIBLE
if(ispath(configuration, /datum/fish_source))
// Create new one of the given type
diff --git a/code/datums/components/gps_signal.dm b/code/datums/components/gps_signal.dm
index 5171dce10212..e54553a884b4 100644
--- a/code/datums/components/gps_signal.dm
+++ b/code/datums/components/gps_signal.dm
@@ -43,7 +43,7 @@ GLOBAL_LIST_EMPTY(gps_transmitters)
var/registered = FALSE
/datum/component/gps_signal/Initialize(gps_tag = "COM0", disabled = FALSE)
- if(!isatom(parent) || ((. = ..()) & COMPONENT_INCOMPATIBLE))
+ if(!isatom(parent) || ((. = ..()) == COMPONENT_INCOMPATIBLE))
return COMPONENT_INCOMPATIBLE
src.gps_tag = gps_tag
src.disabled = disabled
diff --git a/code/datums/components/horror_aura.dm b/code/datums/components/horror_aura.dm
index ea835d879743..45581a602425 100644
--- a/code/datums/components/horror_aura.dm
+++ b/code/datums/components/horror_aura.dm
@@ -11,7 +11,7 @@ It also serves the purposes of portraying the Lore accurate effect of "Acausal L
/datum/component/horror_aura/Initialize(radius)
if(radius)
src.radius = radius
- if(. & COMPONENT_INCOMPATIBLE)
+ if(. == COMPONENT_INCOMPATIBLE)
return
else if(!istype(parent))
return COMPONENT_INCOMPATIBLE
diff --git a/code/datums/components/items/active_parry.dm b/code/datums/components/items/active_parry.dm
new file mode 100644
index 000000000000..1a5dc7dadb9e
--- /dev/null
+++ b/code/datums/components/items/active_parry.dm
@@ -0,0 +1,10 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) Citadel Station Developers *//
+
+/**
+ * generic parry provider on items
+ */
+/datum/component/active_parry
+ registered_type = /datum/component/active_parry
+
+// todo: default implementation via active defensive hotkey
diff --git a/code/datums/components/items/passive_parry.dm b/code/datums/components/items/passive_parry.dm
new file mode 100644
index 000000000000..678aa914dfca
--- /dev/null
+++ b/code/datums/components/items/passive_parry.dm
@@ -0,0 +1,348 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) Citadel Station Developers *//
+
+/**
+ * Shieldcall used as a listener for [/datum/component/passive_parry]
+ */
+/datum/shieldcall/bound/passive_parry
+ expected_type = /datum/component/passive_parry
+
+/**
+ * generic parry provider on items
+ *
+ * this is effectively the old rng block system
+ *
+ * also known as: autoparry.
+ */
+/datum/component/passive_parry
+ registered_type = /datum/component/passive_parry
+
+ /// passive parry data
+ var/datum/passive_parry/parry_data
+ /// callback to invoke before the parry is initiated
+ ///
+ /// * must return /datum/parry_frame or null.
+ /// * if it returns a parry frame, it'll override the frame provided by the passive parry datum
+ /// * if it returns null, it'll cancel the parry
+ /// * invoked, if existing, with (obj/item/parent, mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ /// * this allows you to construct a custom parry frame
+ /// * this will be null'd, not qdel'd, when the component is qdel'd
+ var/datum/callback/parry_intercept
+ /// our registered shieldcall
+ var/datum/shieldcall/bound/passive_parry/hooked_shieldcall
+ /// the mob we're registered with right now
+ var/mob/hooked
+
+/datum/component/passive_parry/Initialize(datum/passive_parry/data, datum/callback/intercept)
+ . = ..()
+ if(. == COMPONENT_INCOMPATIBLE)
+ return
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+ data = fetch_data(data)
+ if(!data)
+ stack_trace("invalid data")
+ return COMPONENT_INCOMPATIBLE
+ src.parry_data = data
+ src.parry_intercept = intercept
+
+/datum/component/passive_parry/Destroy()
+ parry_data = null // parry data holds no refs
+ parry_intercept = null // i'd hope this doesn't hold a ref to us.
+ return ..()
+
+/datum/component/passive_parry/proc/fetch_data(datum/passive_parry/datalike)
+ if(IS_ANONYMOUS_TYPEPATH(datalike))
+ return new datalike
+ if(ispath(datalike))
+ return resolve_passive_parry_data(datalike)
+ if(istype(datalike))
+ return datalike
+
+/**
+ * About to start a parry. Resolve parry_frame datum.
+ */
+/datum/component/passive_parry/proc/ignite(atom/defending, attack_type, datum/weapon)
+ RETURN_TYPE(/datum/parry_frame)
+ if(parry_intercept)
+ return parry_intercept.invoke_no_sleep(parent, defending, attack_type, weapon, parry_data)
+ else
+ var/obj/item/item = parent
+ return item.passive_parry_intercept(defending, attack_type, weapon, parry_data)
+
+/datum/component/passive_parry/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped))
+ 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)
+
+/datum/component/passive_parry/UnregisterFromParent()
+ . = ..()
+ UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED)
+ if(hooked)
+ var/obj/item/item = parent
+ on_unequipped(item, hooked)
+
+/datum/component/passive_parry/proc/on_dropped(obj/item/source, inv_op_flags, atom/new_loc)
+ // delete on drop to save memory
+ qdel(src)
+
+/datum/component/passive_parry/proc/on_equipped(obj/item/source, mob/user, slot)
+ if(!check_slot(slot))
+ return
+ if(hooked)
+ return
+ ASSERT(user)
+ hooked = user
+ hooked_shieldcall = new(src)
+ user.register_shieldcall(hooked_shieldcall)
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL_ITERATION, PROC_REF(shieldcall_iterating))
+
+/datum/component/passive_parry/proc/on_unequipped(obj/item/source, mob/user)
+ ASSERT(user == hooked)
+ hooked = null
+ user.unregister_shieldcall(hooked_shieldcall)
+ QDEL_NULL(hooked_shieldcall)
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL_ITERATION, PROC_REF(shieldcall_iterating))
+
+/datum/component/passive_parry/proc/check_slot(slot_id)
+ return islist(parry_data.parry_slot_id)? (slot_id in parry_data.parry_slot_id) : (!parry_data.parry_slot_id || (parry_data.parry_slot_id == slot_id))
+
+/datum/component/passive_parry/proc/shieldcall_iterating(mob/source, shieldcall_type)
+ SIGNAL_HANDLER
+ ASSERT(source == hooked)
+ var/datum/passive_parry/data = fetch_data(parry_data)
+ // normal shieldcall handlers handle it
+ if(!data.parry_frame_simulated)
+ return
+ var/datum/parry_frame/resolved = ignite(source)
+ if(!resolved)
+ return
+ // for now, we only care about if they already have a frame
+ // in the future, maybe this can fire as long as we aren't the source of a parry frame on them.
+ // todo: cooldown enforcement
+ // todo: mobility enforcement
+ // todo: full parry swing cycle?
+ if(!source.GetComponent(/datum/component/parry_frame))
+ source.AddComponent(/datum/component/parry_frame, resolved, data.parry_frame_timing)
+
+//* Bindings - Bullet *//
+
+/datum/shieldcall/bound/passive_parry/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args)
+ // todo: no support for fake attacks yet
+ if(fake_attack)
+ return
+ // this is a definite 'do as i say, not as i do' moment
+ // this works because the proc names and args and types are **exactly** matching
+ // this is why the procs are all together
+ // do NOT try this at home.
+ return bound:handle_bullet(arglist(args))
+
+/datum/component/passive_parry/proc/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args)
+ var/datum/passive_parry/data = parry_data
+ if(data.parry_frame_simulated)
+ return
+ if(!prob(isnull(data.parry_chance_projectile) ? data.parry_chance_default : data.parry_chance_projectile))
+ return
+ if(!check_defensive_arc_tile(defending, bullet_act_args[BULLET_ACT_ARG_PROJECTILE], data.parry_arc, !data.parry_arc_round_down))
+ return
+ // - Projectile-specific -
+ var/obj/projectile/proj = bullet_act_args[BULLET_ACT_ARG_PROJECTILE]
+ if(!(proj.projectile_type & data.parry_projectile_types))
+ return
+ // - End -
+ var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_PROJECTILE, bullet_act_args[BULLET_ACT_ARG_PROJECTILE])
+ if(!resolved)
+ return
+ return resolved.handle_bullet(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, bullet_act_args, parent)
+
+//* Bindings - Melee *//
+
+/datum/shieldcall/bound/passive_parry/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args)
+ // todo: no support for fake attacks yet
+ if(fake_attack)
+ return
+ // this is a definite 'do as i say, not as i do' moment
+ // this works because the proc names and args and types are **exactly** matching
+ // this is why the procs are all together
+ // do NOT try this at home.
+ return bound:handle_item_melee(arglist(args))
+
+/datum/component/passive_parry/proc/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args)
+ var/datum/passive_parry/data = parry_data
+ if(data.parry_frame_simulated)
+ return
+ if(!prob(isnull(data.parry_chance_melee) ? data.parry_chance_default : data.parry_chance_melee))
+ return
+ if(!check_defensive_arc_tile(defending, e_args.performer, data.parry_arc, !data.parry_arc_round_down))
+ return
+ var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_MELEE, weapon)
+ if(!resolved)
+ return
+ return resolved.handle_item_melee(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, weapon, e_args, parent)
+
+/datum/shieldcall/bound/passive_parry/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args)
+ // todo: no support for fake attacks yet
+ if(fake_attack)
+ return
+ // this is a definite 'do as i say, not as i do' moment
+ // this works because the proc names and args and types are **exactly** matching
+ // this is why the procs are all together
+ // do NOT try this at home.
+ return bound:handle_unarmed_melee(arglist(args))
+
+/datum/component/passive_parry/proc/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args)
+ var/datum/passive_parry/data = parry_data
+ if(data.parry_frame_simulated)
+ return
+ if(!prob(isnull(data.parry_chance_melee) ? data.parry_chance_default : data.parry_chance_melee))
+ return
+ if(!check_defensive_arc_tile(defending, e_args.performer, data.parry_arc, !data.parry_arc_round_down))
+ return
+ var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_UNARMED, style)
+ if(!resolved)
+ return
+ return resolved.handle_unarmed_melee(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, style, e_args, parent)
+
+/datum/shieldcall/bound/passive_parry/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific)
+ // todo: no support for fake attacks yet
+ if(fake_attack)
+ return
+ // this is a definite 'do as i say, not as i do' moment
+ // this works because the proc names and args and types are **exactly** matching
+ // this is why the procs are all together
+ // do NOT try this at home.
+ return bound:handle_touch(arglist(args))
+
+/datum/component/passive_parry/proc/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific)
+ var/datum/passive_parry/data = parry_data
+ if(data.parry_frame_simulated)
+ return
+ if(!prob(isnull(data.parry_chance_touch) ? data.parry_chance_default : data.parry_chance_touch))
+ return
+ if(!check_defensive_arc_tile(defending, e_args.performer, data.parry_arc, !data.parry_arc_round_down))
+ return
+ var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_TOUCH, null)
+ if(!resolved)
+ return
+ return resolved.handle_touch(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, e_args, contact_flags, contact_specific, parent)
+
+//* Bindings - Thrown *//
+
+/datum/shieldcall/bound/passive_parry/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown)
+ // todo: no support for fake attacks yet
+ if(fake_attack)
+ return
+ // this is a definite 'do as i say, not as i do' moment
+ // this works because the proc names and args and types are **exactly** matching
+ // this is why the procs are all together
+ // do NOT try this at home.
+ return bound:handle_throw_impact(arglist(args))
+
+/datum/component/passive_parry/proc/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown)
+ var/datum/passive_parry/data = parry_data
+ if(data.parry_frame_simulated)
+ return
+ if(!prob(isnull(data.parry_chance_thrown) ? data.parry_chance_default : data.parry_chance_thrown))
+ return
+ if(!check_defensive_arc_tile(defending, thrown, data.parry_arc, !data.parry_arc_round_down))
+ return
+ var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_THROWN, thrown)
+ if(!resolved)
+ return
+ return resolved.handle_throw_impact(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, thrown, parent)
+
+//* Item *//
+
+/**
+ * Called by /datum/component/passive_parry when we're about to start up the parry frame
+ * Called if parry intercept callback isn't set.
+ *
+ * @params
+ * * defending - mob being defended
+ * * attack_type - (optional) attack type
+ * * weapon - (optional) the weapon
+ * * parry_data - (optional) the existing parry data
+ *
+ * @return parry frame datum to use, or null to cancel
+ */
+/obj/item/proc/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ return parry_data.parry_frame
+
+//* Data *//
+
+GLOBAL_LIST_EMPTY(passive_parry_data)
+/**
+ * get a cached version of a passive paary datum
+ */
+/proc/resolve_passive_parry_data(datum/passive_parry/datalike)
+ if(isnull(datalike))
+ return
+ if(IS_ANONYMOUS_TYPEPATH(datalike))
+ return new datalike
+ if(istype(datalike))
+ return datalike
+ if(!GLOB.passive_parry_data[datalike])
+ GLOB.passive_parry_data[datalike] = new datalike
+ return GLOB.passive_parry_data[datalike]
+
+/**
+ * datum for holding data on passive parrying
+ */
+/datum/passive_parry
+ /// parry chance for harmful melee: [0, 100]
+ var/parry_chance_melee
+ /// parry chance for (seemingly) benign melee: [0, 100]
+ var/parry_chance_touch
+ /// parry chance for inbound projectile: [0, 100]
+ var/parry_chance_projectile
+ /// parry chance for inbound throw
+ var/parry_chance_thrown
+ /// default parry chance if one of the above is null
+ var/parry_chance_default = 0
+
+ /// passive parry arc
+ var/parry_arc = 180
+ /// passive parry arc should round down for non-projectiles
+ ///
+ /// * at 136 (1 more than 135), having this TRUE means non-projectiles can hit them from behind as behind is 180.
+ /// * at 136 (1 more than 135), having this be FALSE means non-projectiles **cannot** hit them from behind as behind is 180.
+ var/parry_arc_round_down = TRUE
+
+ /// valid slot ids; null for all, list for multiple, singular for single
+ var/parry_slot_id = SLOT_ID_HANDS
+
+ /// projectile types we autoparry on
+ var/parry_projectile_types = ALL
+
+ /// parry frame data to use by default
+ ///
+ /// * can be a typepath
+ /// * can be an anonymous typepath
+ /// * can be a datum
+ var/parry_frame = /datum/parry_frame/passive_block
+ /// simulate a full parry frame with carry-through and duration, or just run the frame once
+ var/parry_frame_simulated = FALSE
+ /// if not simulated, what's our efficiency? [0, 1]
+ ///
+ /// * only used if not simulated
+ var/parry_frame_efficiency = 1
+ /// if simulated, how far in do we start? [0, infinity]
+ ///
+ /// * only used if simulated
+ var/parry_frame_timing = 0
+
+/datum/passive_parry/New()
+ if(IS_ANONYMOUS_TYPEPATH(parry_frame))
+ parry_frame = new parry_frame
+ else if(ispath(parry_frame))
+ parry_frame = new parry_frame
+ else if(istype(parry_frame, /datum/parry_frame))
+ else
+ CRASH("invalid parry frame")
+
+/datum/parry_frame/passive_block
+ parry_can_prevent_contact = TRUE
diff --git a/code/datums/components/items/shield_block.dm b/code/datums/components/items/shield_block.dm
new file mode 100644
index 000000000000..9e1adc211689
--- /dev/null
+++ b/code/datums/components/items/shield_block.dm
@@ -0,0 +1,11 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) Citadel Station Developers *//
+
+/**
+ * generic shield-like block provider on items
+ */
+/datum/component/shield_block
+ registered_type = /datum/component/shield_block
+
+// todo: default implementation via toggled defensive hotkey
+// todo: default 'passive' mode for when you have it held but haven't toggled defensive mode.
diff --git a/code/datums/components/items/wielding.dm b/code/datums/components/items/wielding.dm
index 1294b37e5b26..96fc1e4bfe77 100644
--- a/code/datums/components/items/wielding.dm
+++ b/code/datums/components/items/wielding.dm
@@ -1,7 +1,7 @@
// todo: can element this by usign 3 signals instead of 2, one to receive a keybind signal.
/datum/component/wielding
registered_type = /datum/component/wielding
-
+
/// hands needed
var/hands
/// lazylist
@@ -16,7 +16,7 @@
/datum/component/wielding/Initialize(hands = 2, datum/callback/on_wield, datum/callback/on_unwield)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
- if((. = ..()) & COMPONENT_INCOMPATIBLE)
+ if((. = ..()) == COMPONENT_INCOMPATIBLE)
return
src.hands = hands
src.on_wield = on_wield
@@ -91,6 +91,9 @@
/datum/component/wielding/proc/offhand_destroyed(obj/item/offhand/wielding/I)
unwield()
+
+//* Offhands *//
+
/obj/item/offhand/wielding
name = "wielding offhand"
desc = "You shouldn't be able to see this."
@@ -103,7 +106,8 @@
host = null
return ..()
-// item procs
+//* Item Hooks *//
+
/obj/item/proc/on_wield(mob/user, hands)
return
diff --git a/code/datums/components/mobs/block_frame.dm b/code/datums/components/mobs/block_frame.dm
new file mode 100644
index 000000000000..bbbbedd27b3d
--- /dev/null
+++ b/code/datums/components/mobs/block_frame.dm
@@ -0,0 +1,75 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) Citadel Station Developers *//
+
+/**
+ * ## Active Defensives
+ *
+ * Datastructure for active block on mobs.
+ *
+ * * One, or more, may exist at a time; all of them will be shieldcall-registered, be very careful when doing this.
+ * * Items should generally not allow adding another packet while one is active
+ */
+/datum/component/block_frame
+ registered_type = /datum/component/block_frame
+
+ /// active defensive data
+ var/datum/block_frame/active_block
+ /// current number of processed hits
+ var/hit_count = 0
+ /// world.time of start
+ var/start_time
+
+/datum/shieldcall/bound/block_frame
+ expected_type = /datum/component/parry_frame
+
+// todo: default implementation of a hold-down blocking system.
+
+/**
+ * Datastructure for block data, now far more simplified.
+ *
+ * * Please avoid anonymous typing this where possible, this is a heavy datum and caching helps a lot.
+ * * The reason this is separate from parrying is because block system is far more focused on exact damage simulation, while parrying is focused on deflecting a hit and handling the effects of that.
+ *
+ * todo: this should be a serializable prototype
+ */
+/datum/block_frame
+ /// shield arc, in both CW/CCW from user facing direction
+ ///
+ /// * given RP doesn't have combat mode, you should really just keep this at 180
+ /// * realistically the cutoffs are 45, 90, 135, and 180 for anything that's not a projectile as only those sim physics
+ var/block_arc = 180
+ /// maximum block per attack instance
+ var/block_damage_max = INFINITY
+ /// damage block % above minimum
+ var/block_damage_ratio = 0
+ /// damage block minimum
+ var/block_damage_min = 0
+ /// if set, use this armor datum for processing how much damage to block
+ ///
+ /// * use this for tiered simulation
+ /// * [block_damage_max] is the only other variable used for calculations if this is set, all others are on armor already
+ /// * set to typepath or instance
+ var/datum/armor/block_via_armor
+
+ /// attack types we are allowed to parry
+ var/block_attack_types = NONE
+
+ /// if 100% of damage is blocked, do we set SHIELDCALL_BLOCKED and similar flags?
+ ///
+ /// * this means things like syringes would be blocked from injecting.
+ var/block_can_prevent_contact = FALSE
+ /// always add BLOCKED, even if not 100% mitigated / transmuted
+ var/block_always_prevents_contact = FALSE
+
+ /// ratio [0, INFINITY] of inbound damage to convert to another type
+ var/block_transmute = 0
+ /// damage type to transmute to
+ var/block_transmute_type = HALLOSS
+ /// damage flag the transmuted damage counts as; null = inherit from attack
+ ///
+ /// * only used if block_transmute_simulation is on
+ var/block_transmute_flag = null
+ /// the transmuted damage is directly applied with full melee sim, instead of just a damage instance
+ ///
+ /// * DO NOT TURN THIS ON WITHOUT GOOD REASON. Melee sim is several times more expensive than armor / low-level intercepts for damage instances.
+ var/block_transmute_simulation = FALSE
diff --git a/code/datums/components/mobs/parry_frame.dm b/code/datums/components/mobs/parry_frame.dm
new file mode 100644
index 000000000000..d3fd3c85a772
--- /dev/null
+++ b/code/datums/components/mobs/parry_frame.dm
@@ -0,0 +1,561 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) Citadel Station Developers *//
+
+/**
+ * ## Active Parry
+ *
+ * Datastructure for active parry on mobs.
+ *
+ * * One, or more, may exist at a time; all of them will be shieldcall-registered, be very careful when doing this.
+ * * Items should generally not allow adding another parry frame while one is active
+ */
+/datum/component/parry_frame
+ registered_type = /datum/component/parry_frame
+
+ /// active defensive data
+ var/datum/parry_frame/active_parry
+ /// current number of processed hits
+ var/hit_count = 0
+ /// world.time of start
+ var/start_time
+ /// world time of drop
+ var/drop_time
+ /// registered shieldcall
+ var/datum/shieldcall/bound/parry_frame/shieldcall
+
+/**
+ * * frame - the parry frame
+ * * kick_time_forwards - start this many deciseconds into the frame.
+ */
+/datum/component/parry_frame/Initialize(datum/parry_frame/frame, kick_time_forwards)
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+ . = ..()
+ if(. == COMPONENT_INCOMPATIBLE)
+ return
+ src.active_parry = frame
+ src.start_time = world.time - kick_time_forwards
+ src.drop_time = src.start_time + max(frame.parry_timing_active, frame.parry_timing_perfect) + frame.parry_timing_stop
+ if(src.drop_time < world.time)
+ . = COMPONENT_INCOMPATIBLE
+ CRASH("attempted to start a parry that ended in the past.")
+ shieldcall = new(src)
+ shieldcall.tool_text = parent
+ var/delete_in = src.drop_time - world.time
+ QDEL_IN(src, delete_in)
+ // this is non-transferable, duh
+ new frame.parry_vfx(null, parent, frame)
+
+/datum/component/parry_frame/Destroy()
+ . = ..()
+ // shieldcall must be deleted after unregister
+ QDEL_NULL(shieldcall)
+
+/datum/component/parry_frame/RegisterWithParent()
+ var/atom/movable/AM = parent
+ AM.register_shieldcall(shieldcall)
+
+/datum/component/parry_frame/UnregisterFromParent()
+ var/atom/movable/AM = parent
+ AM.unregister_shieldcall(shieldcall)
+
+/datum/component/parry_frame/proc/on_parry(attack_type, datum/weapon, shieldcall_returns, efficiency)
+ ++hit_count
+ // check drop
+ if(hit_count > active_parry.parry_drop_after_hits)
+ qdel(src)
+ return
+
+//* -- Shieldcall -- *//
+
+/**
+ * Shieldcall used as a listener for [/datum/component/parry_frame]
+ */
+/datum/shieldcall/bound/parry_frame
+ expected_type = /datum/component/parry_frame
+
+ /// text to describe the used tool, if any
+ var/tool_text
+
+//* -- Parry Frame -- *//
+
+/**
+ * Datastructure for parry data, now far more simplified.
+ *
+ * * Please avoid anonymous typing this where possible, this is a heavy datum and caching helps a lot.
+ * * While this is very close to /datum/block_frame, it is different in separate major ways.
+ * * Parrying tends to be more powerful and complex, as it's meant to simulate a very dynamic action.
+ * * Parrying is more expensive to deal with than blocking.
+ *
+ * todo: this should be a serializable prototype
+ * todo: estimated_severity for audiovisuals needs a revisit; sound is not linear.
+ */
+/datum/parry_frame
+ //* Arc *//
+ /// shield arc, in both CW/CCW from user facing direction
+ ///
+ /// * given RP doesn't have combat mode, you should really just keep this at 180
+ /// * realistically the cutoffs are 45, 90, 135, and 180 for anything that's not a projectile as only those sim physics
+ var/parry_arc = 180
+ /// should we round parry arc down for non-projectiles?
+ ///
+ /// * this means 179 can't cover behind us
+ /// * this is needed because non-projectiles don't have exact angles
+ var/parry_arc_round_down = TRUE
+
+ //* Timing *//
+ /// spinup time
+ ///
+ /// * keep this at 0 in most cases
+ /// * the parry does nothing while it's spinning up
+ /// * parries landing on the tick this ends count as having spun up
+ /// * overrules both perfect and active timing
+ var/parry_timing_start = 0 SECONDS
+ /// perfect time
+ ///
+ /// * this is the amount of time we are a perfect parry after tick 0
+ /// * this means that this overlaps with [parry_timing_start]!
+ /// * this is done for performance reasons.
+ /// * parries landing on the tick this ends still count as perfect
+ /// * overrules active timing
+ /// * overruled by start timing
+ var/parry_timing_perfect = 0 SECONDS
+ /// no-falloff time
+ ///
+ /// * this is the amount of time we are at full [parry_efficiency_active] after tick 0
+ /// * this means that this overlaps with both [parry_timing_start] and [parry_timing_perfect].
+ /// * this is done for performance reasons.
+ /// * parries landing on the tick this ends still count as fully active
+ /// * overruled by start and perfect timing
+ var/parry_timing_active = 0 SECONDS
+ /// end time
+ ///
+ /// * this is the amount of time we are still active after [parry_timing_active] or [parry_timing_perfect], whatever is longer
+ /// * efficiency linearly drops from active efficiency to 0 during this time
+ /// * the parry immediately drops after
+ /// * parries landing on the tick this ends are dropped
+ var/parry_timing_stop = 0 SECONDS
+
+ //* Attack Types *//
+ /// attack types we are allowed to parry
+ var/parry_attack_types = NONE
+
+ //* Efficiency *//
+ /// parry efficiency at perfect; [0, 1]
+ ///
+ /// * parry efficiency is ratio of damage to block
+ var/parry_efficiency_perfect = 1
+ /// parry efficiency at active; [0, 1]
+ ///
+ /// * parry efficiency is ratio of damage to block
+ var/parry_efficiency_active = 1
+ /// minimum efficiency to drop to
+ var/parry_efficiency_floor = 0
+ /// parry efficiency at which we count as a full block
+ var/parry_efficiency_blocked = 1
+ /// parry efficiency at which redirection occurs
+ var/parry_efficiency_redirection = 1
+
+ //* Defender Effects *//
+ /// action-lock the defender while parrying
+ // todo: implement
+ var/parry_lock_defender = TRUE
+ /// drop action-lock on defender when a parry succeeds
+ // todo: implement
+ var/parry_free_defender_on_success = TRUE
+
+ //* Configuration *//
+ /// immediately drop the parry after this many hits
+ var/parry_drop_after_hits = 1
+
+ //* Counter Effects *//
+ /// counterattack on hit
+ ///
+ /// * keep this off, this is a good exercise in 'just because you can doesn't mean you should'
+ // todo: implement
+ var/parry_counter_attack = FALSE
+ /// status effects to apply on hit to attacker
+ ///
+ /// supports:
+ /// * /datum/status_effect status effects; associate to duration
+ ///
+ /// does not support:
+ /// * any status effect supertype that isn't listed above. right now, that's grouped and stacking.
+ var/list/parry_counter_effects
+
+ //* Counter Effects - Projectiles / Vector *//
+ /// default handling: reflect attack types
+ ///
+ /// * yeah you probably shouldn't put anything other than ATTACK_TYPE_PROJECTILE in here.
+ var/parry_redirect_attack_types = NONE
+ /// default handling: reflect attack back at attacker
+ ///
+ /// * yeah you probably should leave this off
+ var/parry_redirect_return_to_sender = FALSE
+ /// redirection arc CW/CCW of angle of incidence
+ ///
+ /// * if return_to_sender is off, this is the valid arc from attack source it can be reflected to
+ /// * if return_to_sender is on, this is the arc in error from attack source we can reflect to
+ var/parry_redirect_arc = 45
+
+ //* Defense - Damage *//
+ /// if 100% of damage is blocked, do we set SHIELDCALL_BLOCKED and similar flags?
+ ///
+ /// * this means things like syringes would be blocked from injecting.
+ var/parry_can_prevent_contact = FALSE
+ /// always add BLOCKED, even if not 100% mitigated / transmuted
+ var/parry_always_prevents_contact = FALSE
+ /// maximum damage blocked per attack instance
+ // todo: implement
+ var/parry_damage_max = INFINITY
+
+ //* Defense - Transmute *//
+ // todo: implement
+ /// ratio [0, INFINITY] of **blocked** damage to convert to another type
+ var/parry_transmute = 0
+ /// damage type to transmute to; null to default to attacking damage type
+ var/parry_transmute_type = null
+ /// damage tier used for transmuted damage; null to default to attacking tier
+ var/parry_transmute_tier = null
+ /// damage mode used for transmuted damage; null to default to attacking mode
+ var/parry_transmute_mode = null
+ /// damage flag the transmuted damage counts as; null = inherit from attack
+ var/parry_transmute_flag = null
+ /// the transmuted damage should be simulated as close to a proper melee hit as possible,
+ /// instead of just going through run_damage_instance()
+ ///
+ /// * DO NOT TURN THIS ON WITHOUT GOOD REASON. Melee sim is several times more expensive than armor / low-level intercepts for damage instances.
+ /// * Currently does nothing, as we do not have a way to simulate a standard melee hit arbitrarily without side effects.
+ var/parry_transmute_simulation = FALSE
+
+ //* Defender Cooldown *//
+ /// hard cooldown to apply to parrying with the thing parrying wth
+ // todo: implement
+ var/parry_cooldown_tool = 2 SECONDS
+ /// hard cooldown to apply to parrying at all as the mob
+ // todo: implement
+ var/parry_cooldown_user = 0 SECONDS
+ /// is the parry cooldown ignored if a successful parry was made
+ // todo: implement
+ var/parry_cooldown_on_success = FALSE
+
+ //* Audiovisuals & Feedback *//
+ /// a sound, or a list of sounds that can be played when we're hit
+ /// list can be weighted by associated number for relative chance
+ ///
+ /// * sound can be a file
+ /// * sound can be a /datum/soundbyte typepath or instance
+ var/list/parry_sfx = /datum/soundbyte/grouped/metal_parry
+ /// a typepath of /atom/movable/parry_frame to use as our visual; this is placed in the defending atom's vis_contents
+ var/parry_vfx = /atom/movable/render/parry_frame/default
+ /// "[person] [start_verb] with [item]"
+ // todo: implement
+ var/start_verb = "shifts into a defensive stance"
+ /// "[person] [block_verb] [attack_source_descriptor] with [item]"
+ var/block_verb = "parries"
+ /// "[person] [deflect_verb] [attack_source_descriptor] with [item]"
+ ///
+ /// * used if an attack was redirected and not just blocked
+ var/deflect_verb = "deflects"
+
+/**
+ * * 0 if in spinup
+ * * perfect efficiency if in perfect (from 0)
+ * * active efficiency if in active (from 0)
+ * * linear falloff to 0 if in spindown (from active time end)
+ */
+/datum/parry_frame/proc/calculate_parry_efficiency(time_into_parry)
+ if(time_into_parry < parry_timing_start)
+ return 0
+ if(time_into_parry <= parry_timing_perfect)
+ return parry_timing_perfect
+ if(time_into_parry <= parry_timing_active)
+ return parry_timing_active
+ var/drop_time = parry_timing_active + parry_timing_stop
+ if(time_into_parry >= drop_time)
+ return 0
+ var/time_into_drop = time_into_parry - parry_timing_active
+ return parry_efficiency_active - (parry_efficiency_active - parry_efficiency_floor) * ((time_into_drop) / parry_timing_stop)
+
+/**
+ * @params
+ * * time_into_parry - ds elapsed since parry start
+ *
+ * @return TRUE / FALSE
+ */
+/datum/parry_frame/proc/is_parry_perfect(time_into_parry)
+ return (time_into_parry >= parry_timing_start) && (time_into_parry <= parry_timing_perfect)
+
+/**
+ * Called when parrying something
+ *
+ * @params
+ * * defending - thing being defended against an attack
+ * * attack_type - (optional) type of attack
+ * * efficiency - (optional) parry efficiency
+ * * weapon - (optional) incoming weapon, depends on ATTACK_TYPE
+ * * shieldcall_flags - (optional) the attack's shieldcall flags
+ * * severity - (optional) arbitrary 0 to 100 severity of how bad the hit is estimated to be
+ * * attack_source_descriptor - (optional) text, or an entity to describe the attack. entities will be automatically handled.
+ * * tool_text - (optional) text to describe the parry tool
+ */
+/datum/parry_frame/proc/perform_audiovisuals(atom/defending, attack_type, efficiency, datum/weapon, shieldcall_flags, severity = 75, attack_source_descriptor, tool_text)
+ playsound(defending, (islist(parry_sfx) && length(parry_sfx)) ? pick(parry_sfx) : parry_sfx , severity, TRUE)
+ new parry_vfx(null, defending, src, shieldcall_flags & SHIELDCALL_FLAG_SINGLE_PARRY)
+
+ var/parry_verb
+ if((attack_type & parry_redirect_attack_types) && (efficiency >= parry_efficiency_redirection))
+ parry_verb = deflect_verb
+ else
+ parry_verb = block_verb
+
+ var/attack_descriptor = "the attack"
+ if(attack_source_descriptor)
+ // generic processing
+ attack_descriptor = "\the [attack_source_descriptor]"
+ // 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()
+ if(mob_holding_item)
+ attack_descriptor = "[mob_holding_item]'s [item_source_descriptor]"
+
+ defending.visible_message(
+ SPAN_DANGER("[defending] [parry_verb] [attack_descriptor][tool_text && " with \the [tool_text]"]!"),
+ )
+
+/**
+ * Called when something is parried
+ *
+ * @params
+ * * defending - thing being defended against an attack
+ * * attack_type - (optional) type of attack
+ * * efficiency - (optional) parry efficiency
+ * * weapon - (optional) incoming weapon, depends on ATTACK_TYPE
+ * * shieldcall_flags - (optional) the attack's shieldcall flags
+ * * e_args - (optional) for melee, the event args of the attack
+ *
+ * @return SHIELDCALL_* flags; these override the caller's!
+ */
+/datum/parry_frame/proc/perform_aftereffects(atom/defending, attack_type, efficiency, datum/weapon, shieldcall_flags, datum/event_args/actor/clickchain/e_args)
+ . = shieldcall_flags
+
+ var/atom/movable/aggressor
+
+ // detect aggressor
+ if(istype(weapon, /obj/projectile))
+ var/obj/projectile/weapon_proj = weapon
+ aggressor = weapon_proj.firer
+ else if(e_args)
+ aggressor = e_args.performer
+ else if(istype(weapon, /datum/thrownthing))
+ var/datum/thrownthing/weapon_thrown = weapon
+ aggressor = weapon_thrown.thrower
+
+ switch(attack_type)
+ if(ATTACK_TYPE_PROJECTILE)
+ var/obj/projectile/proj = weapon
+ if((parry_redirect_attack_types & ATTACK_TYPE_PROJECTILE) && (efficiency >= parry_efficiency_redirection))
+ var/outgoing_angle
+ if(parry_redirect_return_to_sender && aggressor)
+ outgoing_angle = arctan(aggressor.y - defending.y, aggressor.x - defending.x)
+ else
+ // todo: this should be angle of incidence maybe?
+ outgoing_angle = turn(proj.angle, 180) + rand(-parry_redirect_arc, parry_redirect_arc)
+ proj.set_angle(outgoing_angle)
+ . |= SHIELDCALL_FLAG_ATTACK_REDIRECT
+
+ // effects that are only valid if we have a retaliation target
+ if(aggressor)
+ if(ismob(aggressor))
+ var/mob/aggressor_mob = aggressor
+ for(var/key in parry_counter_effects)
+ var/value = parry_counter_effects[key]
+ if(ispath(key, /datum/status_effect))
+ aggressor_mob.apply_status_effect(key, value)
+
+ if(parry_counter_attack)
+ // RIP AND TEAR
+ // todo: counterattacks are offline due to clickchain not being entirely nailed down
+ // we need clickchain flags to be actually checked in active block/parry so that REFLEX_COUNTER flagged clicks don't get re-parried.
+ pass()
+
+/**
+ * Called to transmute an instance of damage into another instance of damage and apply it to the defender.
+ */
+/datum/parry_frame/proc/perform_transmuted_damage(atom/defending, damage, damage_tier, damage_type, damage_mode, damage_flag, hit_zone, shieldcall_flags)
+ // todo: parry_transmute_simulation
+ defending.run_damage_instance(
+ parry_transmute * damage,
+ isnull(parry_transmute_type) ? damage_type : parry_transmute_type,
+ isnull(parry_transmute_tier) ? damage_tier : parry_transmute_tier,
+ isnull(parry_transmute_flag) ? damage_flag : parry_transmute_flag,
+ isnull(parry_transmute_mode) ? damage_mode : parry_transmute_mode,
+ ATTACK_TYPE_DEFENSIVE_PASSTHROUGH,
+ null,
+ SHIELDCALL_FLAG_SECOND_CALL,
+ hit_zone,
+ )
+
+//* Bindings - Bullet *//
+
+/datum/shieldcall/bound/parry_frame/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args)
+ var/datum/component/parry_frame/frame = bound
+ if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_PROJECTILE))
+ return
+ if(!check_defensive_arc_tile(defending, bullet_act_args[BULLET_ACT_ARG_PROJECTILE], frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down))
+ return
+ var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time)
+ . = frame.active_parry.handle_bullet(defending, shieldcall_returns, fake_attack, efficiency, bullet_act_args, tool_text)
+ frame.on_parry(ATTACK_TYPE_PROJECTILE, bullet_act_args[BULLET_ACT_ARG_PROJECTILE], ., efficiency)
+
+/datum/parry_frame/proc/handle_bullet(atom/defending, shieldcall_returns, fake_attack, efficiency, list/bullet_act_args, tool_text)
+ . = shieldcall_returns
+ // todo: doesn't take into account any damage randomization
+ var/obj/projectile/proj = bullet_act_args[BULLET_ACT_ARG_PROJECTILE]
+ var/estimated_severity = clamp(proj.damage / 20 * 75, 0, 100)
+ bullet_act_args[BULLET_ACT_ARG_EFFICIENCY] = bullet_act_args[BULLET_ACT_ARG_EFFICIENCY] * clamp(1 - efficiency, 0, 1)
+ . = perform_aftereffects(defending, ATTACK_TYPE_PROJECTILE, efficiency, proj, .)
+ perform_audiovisuals(defending, ATTACK_TYPE_PROJECTILE, efficiency, proj, ., estimated_severity, proj, tool_text)
+ if(. & (SHIELDCALL_FLAG_ATTACK_PASSTHROUGH | SHIELDCALL_FLAG_ATTACK_REDIRECT))
+ bullet_act_args[BULLET_ACT_ARG_FLAGS] |= PROJECTILE_IMPACT_REFLECT
+ if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked)))
+ bullet_act_args[BULLET_ACT_ARG_FLAGS] |= PROJECTILE_IMPACT_BLOCKED
+
+//* Bindings - Melee *//
+
+/datum/shieldcall/bound/parry_frame/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args)
+ var/datum/component/parry_frame/frame = bound
+ if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_MELEE))
+ return
+ if(e_args && !check_defensive_arc_tile(defending, e_args.performer, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down))
+ return
+ var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time)
+ . = frame.active_parry.handle_item_melee(defending, shieldcall_returns, fake_attack, efficiency, weapon, e_args, tool_text)
+ frame.on_parry(ATTACK_TYPE_MELEE, weapon, ., efficiency)
+
+/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)
+ . = 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)))
+ . |= SHIELDCALL_FLAG_ATTACK_BLOCKED
+
+/datum/shieldcall/bound/parry_frame/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args)
+ var/datum/component/parry_frame/frame = bound
+ if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_UNARMED))
+ return
+ if(e_args && !check_defensive_arc_tile(defending, e_args.performer, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down))
+ return
+ var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time)
+ . = frame.active_parry.handle_unarmed_melee(defending, shieldcall_returns, fake_attack, efficiency, style, e_args, tool_text)
+ frame.on_parry(ATTACK_TYPE_UNARMED, style, ., efficiency)
+
+/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)
+ . = 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)))
+ . |= SHIELDCALL_FLAG_ATTACK_BLOCKED
+
+/datum/shieldcall/bound/parry_frame/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific)
+ var/datum/component/parry_frame/frame = bound
+ if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_TOUCH))
+ return
+ if(e_args && !check_defensive_arc_tile(defending, e_args.performer, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down))
+ return
+ var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time)
+ . = frame.active_parry.handle_touch(defending, shieldcall_returns, fake_attack, efficiency, e_args, contact_flags, contact_specific, tool_text)
+ frame.on_parry(ATTACK_TYPE_TOUCH, null, ., efficiency)
+
+/datum/parry_frame/proc/handle_touch(atom/defending, shieldcall_returns, fake_attack, efficiency, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific, tool_text)
+ . = shieldcall_returns
+ // todo: doesn't take into account any damage randomization
+ var/estimated_severity = 50
+ e_args.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)))
+ . |= SHIELDCALL_FLAG_ATTACK_BLOCKED
+
+//* Bindings - Thrown *//
+
+/datum/shieldcall/bound/parry_frame/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown)
+ var/datum/component/parry_frame/frame = bound
+ if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_THROWN))
+ return
+ if(!check_defensive_arc_tile(defending, thrown, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down))
+ return
+ var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time)
+ . = frame.active_parry.handle_throw_impact(defending, shieldcall_returns, fake_attack, efficiency, thrown, tool_text)
+ frame.on_parry(ATTACK_TYPE_THROWN, thrown, ., efficiency)
+
+/datum/parry_frame/proc/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, efficiency, datum/thrownthing/thrown, tool_text)
+ . = shieldcall_returns
+ // todo: doesn't take into account any damage randomization
+ // todo: why isn't thrownthing just with a get_damage() or a better inflict_damage() and get_damage_tuple() idfk man
+ var/estimated_severity = clamp(thrown.thrownthing.throw_force * thrown.get_damage_multiplier() / 20 * 75, 0, 100)
+ thrown.damage_multiplier *= clamp(1 - efficiency, 0, 1)
+ . = perform_aftereffects(defending, ATTACK_TYPE_THROWN, efficiency, thrown, ., thrown.thrownthing, tool_text)
+ perform_audiovisuals(defending, ATTACK_TYPE_THROWN, efficiency, thrown, ., estimated_severity)
+ if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked)))
+ . |= SHIELDCALL_FLAG_ATTACK_BLOCKED
+
+//* -- VFX Render -- *//
+
+INITIALIZE_IMMEDIATE(/atom/movable/render/parry_frame)
+/**
+ * A visualizer for a parry frame.
+ */
+/atom/movable/render/parry_frame
+ var/atom/movable/bound
+ /// set this in your custom procs for cycle and single/spinup/spindown
+ var/qdel_time = 1 SECONDS
+
+/atom/movable/render/parry_frame/Initialize(mapload, atom/movable/bind_to, datum/parry_frame/frame, single_deflect)
+ SHOULD_CALL_PARENT(FALSE)
+ if(!istype(frame))
+ . = INITIALIZE_HINT_QDEL
+ CRASH("no valid frame, this is bad")
+ src.bound = bind_to
+ bind_to.vis_contents += src
+ cycle(frame, single_deflect)
+ QDEL_IN(src, qdel_time)
+ return INITIALIZE_HINT_NORMAL
+
+/atom/movable/render/parry_frame/Destroy()
+ bound.vis_contents -= src
+ bound = null
+ return ..()
+
+/atom/movable/render/parry_frame/proc/cycle(datum/parry_frame/frame, single_deflect)
+ if(single_deflect)
+ single()
+ return
+ spinup(frame.parry_timing_start)
+ addtimer(CALLBACK(src, PROC_REF(spindown), frame.parry_timing_stop), max(frame.parry_timing_active, frame.parry_timing_perfect))
+
+/atom/movable/render/parry_frame/proc/single()
+ return
+
+/atom/movable/render/parry_frame/proc/spinup(start_time)
+ return
+
+/atom/movable/render/parry_frame/proc/spindown(stop_time)
+ return
+
+//* -- VFX Default -- *//
+
+/atom/movable/render/parry_frame/default
+ icon = 'icons/effects/defensive/main_parry.dmi'
+ icon_state = "hold"
+
+/atom/movable/render/parry_frame/default/single()
+ animate(src, time = 0.3 SECONDS, alpha = 0)
+ qdel_time = 0.3 SECONDS
diff --git a/code/datums/components/movable/spatial_grid.dm b/code/datums/components/movable/spatial_grid.dm
index a549c642d1ae..ca650de950ce 100644
--- a/code/datums/components/movable/spatial_grid.dm
+++ b/code/datums/components/movable/spatial_grid.dm
@@ -16,7 +16,7 @@
/datum/component/spatial_grid/Initialize(datum/spatial_grid/grid)
. = ..()
- if(. & COMPONENT_INCOMPATIBLE)
+ if(. == COMPONENT_INCOMPATIBLE)
return
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
diff --git a/code/datums/components/riding/riding_filter.dm b/code/datums/components/riding/riding_filter.dm
index 464e7c9f7fab..8ab7fcbf0116 100644
--- a/code/datums/components/riding/riding_filter.dm
+++ b/code/datums/components/riding/riding_filter.dm
@@ -43,7 +43,7 @@
/datum/component/riding_filter/Initialize(handler_typepath)
. = ..()
- if(. & COMPONENT_INCOMPATIBLE)
+ if(. == COMPONENT_INCOMPATIBLE)
return
if(!istype(parent, expected_typepath))
return COMPONENT_INCOMPATIBLE
@@ -80,11 +80,11 @@
/datum/component/riding_filter/proc/signal_hook_user_buckle(atom/movable/source, mob/M, flags, mob/user, semantic)
SIGNAL_HANDLER_DOES_SLEEP
- return check_user_mount(M, flags, user, semantic)? COMPONENT_FORCE_BUCKLE_OPERATION : COMPONENT_BLOCK_BUCKLE_OPERATION
+ return check_user_mount(M, flags, user, semantic)? SIGNAL_RAISE_FORCE_BUCKLE_OPERATION : SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION
/datum/component/riding_filter/proc/signal_hook_pre_buckle(atom/movable/source, mob/M, flags, mob/user, semantic)
SIGNAL_HANDLER
- return on_mount_attempt(M, flags, user, semantic)? COMPONENT_FORCE_BUCKLE_OPERATION : COMPONENT_BLOCK_BUCKLE_OPERATION
+ return on_mount_attempt(M, flags, user, semantic)? SIGNAL_RAISE_FORCE_BUCKLE_OPERATION : SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION
/datum/component/riding_filter/proc/signal_hook_post_buckle(atom/movable/source, mob/M, flags, mob/user, semantic)
SIGNAL_HANDLER
@@ -106,7 +106,7 @@
*/
/datum/component/riding_filter/proc/signal_hook_can_buckle(atom/movable/source, mob/M, flags, mob/user, semantic)
SIGNAL_HANDLER
- return check_mount_attempt(M, flags, user, semantic)? COMPONENT_FORCE_BUCKLE_OPERATION : COMPONENT_BLOCK_BUCKLE_OPERATION
+ return check_mount_attempt(M, flags, user, semantic)? SIGNAL_RAISE_FORCE_BUCKLE_OPERATION : SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION
/**
* called on buckling process right before point of no return
diff --git a/code/datums/components/riding/riding_handler.dm b/code/datums/components/riding/riding_handler.dm
index 10230638e7fd..0b260810dbbe 100644
--- a/code/datums/components/riding/riding_handler.dm
+++ b/code/datums/components/riding/riding_handler.dm
@@ -77,7 +77,7 @@
/datum/component/riding_handler/Initialize()
. = ..()
- if(. & COMPONENT_INCOMPATIBLE)
+ if(. == COMPONENT_INCOMPATIBLE)
return
if(!istype(parent, expected_typepath))
return COMPONENT_INCOMPATIBLE
@@ -127,7 +127,7 @@
/datum/component/riding_handler/proc/signal_hook_pre_buckle_mob(atom/movable/source, mob/M, flags, mob/user, semantic)
SIGNAL_HANDLER_DOES_SLEEP
if(!check_rider(M, semantic, TRUE, user = user))
- return COMPONENT_BLOCK_BUCKLE_OPERATION
+ return SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION
/datum/component/riding_handler/proc/signal_hook_pixel_offset_changed(atom/movable/source)
full_update_riders(null, TRUE)
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 6909f279a409..c93c2373e7b9 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -11,7 +11,12 @@
datum_flags |= DF_VAR_EDITED
return TRUE
-/datum/proc/vv_get_var(var_name)
+/**
+ * @params
+ * * var_name - name of the variable
+ * * resolve - automatically resolve the variable if it's lazy loaded?
+ */
+/datum/proc/vv_get_var(var_name, resolve)
switch(var_name)
if ("vars")
return debug_variable(var_name, list(), 0, src)
diff --git a/code/datums/elements/conflict_checking.dm b/code/datums/elements/conflict_checking.dm
index f298d184f017..8c8e35db6982 100644
--- a/code/datums/elements/conflict_checking.dm
+++ b/code/datums/elements/conflict_checking.dm
@@ -1,5 +1,7 @@
/**
* Simple conflict checking for getting number of conflicting things on someone with the same ID.
+ *
+ * todo: this is a bit slow innit.
*/
/datum/element/conflict_checking
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm
index 50858fa0553c..66fc14937bdf 100644
--- a/code/datums/event_args/clickchain.dm
+++ b/code/datums/event_args/clickchain.dm
@@ -2,6 +2,8 @@
* used to hold data about a click (melee/ranged/other) action
*
* the click may be real or fake.
+ *
+ * * This is required for item swings / interaction, usually, not just base /event_args/actor.
*/
/datum/event_args/actor/clickchain
/// optional: attack intent
@@ -11,6 +13,15 @@
/// optional: target atom
var/atom/target
+ //* Attack Data *//
+
+ /// Overall damage multiplier
+ ///
+ /// todo: implement; needs slight clickchain/melee overhaul
+ ///
+ /// * Allowed to be changed by shieldcalls and other intercepts
+ var/damage_multiplier = 1
+
/datum/event_args/actor/clickchain/New(mob/performer, mob/initiator, atom/target, list/params, intent)
..()
src.target = target
diff --git a/code/datums/outfits/ghostrole.dm b/code/datums/outfits/ghostrole.dm
index 896dc9c57267..7ab69512758a 100644
--- a/code/datums/outfits/ghostrole.dm
+++ b/code/datums/outfits/ghostrole.dm
@@ -69,13 +69,13 @@
/datum/outfit/pirate/immigrant
name = "Pirate - Immigrant"
belt = /obj/item/gun/ballistic/pirate
- r_pocket = /obj/item/melee/energy/sword/pirate
+ r_pocket = /obj/item/melee/transforming/energy/sword/cutlass
/datum/outfit/pirate/dilettante
name = "Pirate - Dilettante"
uniform = /obj/item/clothing/under/surplus
shoes = /obj/item/clothing/shoes/boots/jackboots
- belt = /obj/item/melee/energy/sword/pirate
+ belt = /obj/item/melee/transforming/energy/sword/cutlass
l_hand = /obj/item/shield/makeshift
/datum/outfit/pirate/professional
@@ -85,5 +85,5 @@
shoes = /obj/item/clothing/shoes/boots/jackboots
mask = /obj/item/clothing/mask/balaclava
belt = /obj/item/gun/energy/zip
- r_pocket = /obj/item/melee/energy/sword/pirate
+ r_pocket = /obj/item/melee/transforming/energy/sword/cutlass
r_hand = /obj/item/shield/makeshift
diff --git a/code/datums/outfits/horror_killers.dm b/code/datums/outfits/horror_killers.dm
index 87ce4e992701..a1f7e22b70c8 100644
--- a/code/datums/outfits/horror_killers.dm
+++ b/code/datums/outfits/horror_killers.dm
@@ -41,7 +41,7 @@
gloves = /obj/item/clothing/gloves/black
l_ear = /obj/item/radio/headset
glasses = /obj/item/clothing/glasses/sunglasses
- l_pocket = /obj/item/melee/energy/sword
+ l_pocket = /obj/item/melee/transforming/energy/sword
mask = /obj/item/clothing/mask/gas/clown_hat
id_slot = SLOT_ID_WORN_ID
diff --git a/code/datums/outfits/outfit.dm b/code/datums/outfits/outfit.dm
index e9f5223c5e08..85823242f516 100644
--- a/code/datums/outfits/outfit.dm
+++ b/code/datums/outfits/outfit.dm
@@ -263,8 +263,8 @@
belt = /obj/item/storage/belt/security/tactical/bandolier
l_pocket = /obj/item/cell/device/weapon
r_pocket = /obj/item/cell/device/weapon
- r_hand = /obj/item/melee/energy/sword/imperial
- l_hand = /obj/item/shield/energy/imperial
+ r_hand = /obj/item/melee/transforming/energy/sword/imperial
+ l_hand = /obj/item/shield/transforming/energy/imperial
suit_store = /obj/item/gun/energy/imperial
/datum/outfit/imperial/officer
@@ -279,6 +279,6 @@
belt = /obj/item/storage/belt/security/tactical/bandolier
l_pocket = /obj/item/cell/device/weapon
r_pocket = /obj/item/cell/device/weapon
- r_hand = /obj/item/melee/energy/sword/imperial
- l_hand = /obj/item/shield/energy/imperial
+ r_hand = /obj/item/melee/transforming/energy/sword/imperial
+ l_hand = /obj/item/shield/transforming/energy/imperial
suit_store = /obj/item/gun/energy/imperial
diff --git a/code/datums/outfits/pirates.dm b/code/datums/outfits/pirates.dm
index 3208e284a182..c2a061eb2772 100644
--- a/code/datums/outfits/pirates.dm
+++ b/code/datums/outfits/pirates.dm
@@ -6,7 +6,7 @@
shoes = /obj/item/clothing/shoes/brown
head = /obj/item/clothing/head/bandana
glasses = /obj/item/clothing/glasses/eyepatch
- l_hand = /obj/item/melee/energy/sword/pirate
+ l_hand = /obj/item/melee/transforming/energy/sword/cutlass
/datum/outfit/pirate/norm
@@ -25,7 +25,7 @@
gloves = /obj/item/clothing/gloves/light_brown
mask = /obj/item/clothing/mask/breath
back = /obj/item/tank/vox
- l_hand = /obj/item/melee/energy/sword/pirate
+ l_hand = /obj/item/melee/transforming/energy/sword/cutlass
r_hand = /obj/item/gun/ballistic/shotgun/pump/rifle/vox_hunting
l_pocket = /obj/item/ammo_magazine/a7_62mm/clip
r_pocket = /obj/item/ammo_magazine/a7_62mm/clip
diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm
index 6f9121469b92..3babfe0fb1df 100644
--- a/code/datums/position_point_vector.dm
+++ b/code/datums/position_point_vector.dm
@@ -119,10 +119,13 @@
/**
* angle is clockwise from north
+ *
+ * @return self
*/
/datum/point/proc/shift_in_projectile_angle(angle, distance)
x += sin(angle) * distance
y += cos(angle) * distance
+ return src
/**
* doesn't use set base pixel x/y
diff --git a/code/datums/shieldcall.dm b/code/datums/shieldcall.dm
index 8f66b776107d..37f612450fe7 100644
--- a/code/datums/shieldcall.dm
+++ b/code/datums/shieldcall.dm
@@ -1,15 +1,43 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
+//* Copyright (c) 2024 silicons *//
+
+GLOBAL_LIST_EMPTY(cached_shieldcall_datums)
+
+/**
+ * return a globally cached shieldcall
+ * mostly for hardcoded ones that always work a certain way regardless of what it's actually representing
+ */
+/proc/fetch_cached_shieldcall(path)
+ if(GLOB.cached_shieldcall_datums[path])
+ return GLOB.cached_shieldcall_datums[path]
+ GLOB.cached_shieldcall_datums[path] = new path
+ return GLOB.cached_shieldcall_datums[path]
/**
* Shieldcall handling datums
+ *
+ * These are semi-expensive datums that act as hooks
+ * to intercept arbitrary attacks at the /atom level.
+ *
+ * Please be careful with their usage.
+ * They hook most attacks, and as such are pretty expensive to have on an atom, compute-wise.
+ * This is pretty much only efficient for single-target; if it's possible, any suppression hook for attacks
+ * is better than using this on a large number of targets.
*/
/datum/shieldcall
/// priority ; should never change once we're registered on something. lower has higher priority.
var/priority = 0
/// goes to mob when in inventory - whether equipped to slot or in hand
/// do not modify while applied. it will not un/register properly.
+ ///
+ /// * Turn this off if you're doing your own handling.
var/shields_in_inventory = TRUE
+ /// Allow interception of `atom_shieldcall`.
+ ///
+ /// * "Yes, I read and understand the terms and conditions"
+ /// * "Yes, I will handle SHIELDCALL_FLAG_SECOND_CALL to prevent a double-block scenario"
+ /// * "Yes, I understand that atom_shieldcall is low level and is called in addition to other shieldcall handling procs"
+ var/low_level_intercept = FALSE
/**
* sent over from the atom
@@ -17,6 +45,127 @@
* @params
* * defending - the atom in question
* * shieldcall_args - indexed list of shieldcall args.
+ * * fake_attack - just checking!
+ *
+ * @return nothing, because you should be modifying the return flags via shieldcall_args list!
+ */
+/datum/shieldcall/proc/handle_shieldcall(atom/defending, list/shieldcall_args, fake_attack)
+ return
+
+//* Melee Handling *//
+
+/**
+ * sent over from the atom
+ *
+ * * this is generic pre-intercept for melee; please keep this cheap
+ * * for stuff like reactive teleport armor, use this because it will stop the hit entirely.
+ * * for damage modification, inject directly into e_args
+ *
+ * @params
+ * * defending - the atom being attacked
+ * * shieldcall_returns - existing returns from other shieldcalls
+ * * fake_attack - just checking!
+ * * weapon - the item being used to swing with
+ * * e_args - (optional) the clickchain event, if any; **This is mutable.**
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/datum/shieldcall/proc/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args)
+ return NONE
+
+/**
+ * sent over from the atom
+ *
+ * * this is generic pre-intercept for melee; please keep this cheap
+ * * for stuff like reactive teleport armor, use this because it will stop the hit entirely.
+ * * for damage modification, inject directly into e_args
+ *
+ * @params
+ * * defending - the atom in question
+ * * shieldcall_returns - existing returns from other shieldcalls
+ * * fake_attack - just checking!
+ * * style - the unarmed_attack datum being used
+ * * e_args (optional) the clickchain event, if any; **This is mutable.**
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/datum/shieldcall/proc/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args)
+ return NONE
+
+//* Interaction Handling *//
+
+/**
+ * sent over from the atom
+ *
+ * * for generic 'tried to touch' things
+ * * this is really funny because it lets us do things like teleport the RD on a hug
+ *
+ * @params
+ * * defending - the atom in question
+ * * shieldcall_returns - existing returns from other shieldcalls
+ * * fake_attack - just checking!
+ * * e_args (optional) the clickchain event, if any; **This is mutable.**
+ * * contact_flags - SHIELDCALL_CONTACT_FLAG_*
+ * * contact_specific - SHIELDCALL_CONTACT_SPECIFIC_*
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/datum/shieldcall/proc/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific)
+ return NONE
+
+//* Projectile Handling *//
+
+/**
+ * sent over from the atom
+ *
+ * * this is pre-intercept for projectiles; please keep this cheap.
+ * * for stuff like reactive teleport armor, use this because it will stop the hit entirely.
+ * * passed in bullet act args is mutable.
+ * * we DO NOT process SHIELDCALL_FLAG flags other than _TERMINATE, because we have direct access to impact_flags of the bullet!
+ *
+ * @params
+ * * defending - the atom in question
+ * * shieldcall_returns - existing returns from other shieldcalls
+ * * fake_attack - just checking!
+ * * bullet_act_args - indexed list of bullet_act args.
+ *
+ * @return SHIELDCALL_FLAG_TERMINATE or NONE
*/
-/datum/shieldcall/proc/handle_shieldcall(atom/defending, list/shieldcall_args)
+/datum/shieldcall/proc/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args)
+ return NONE
+
+//* Throw Handling *//
+
+/**
+ * sent over from the atom
+ *
+ * * this is pre-intercept for throwns
+ * * for stuff like reactive teleport armor, use this because it will stop the hit entirely
+ *
+ * todo: implement for turf
+ * todo: implement for obj
+ *
+ * @params
+ * * defending - the thing being hit
+ * * shieldcall_returns - existing returns from other shieldcalls
+ * * fake_attack - just checking!
+ * * thrown - the thrown object's data
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/datum/shieldcall/proc/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown)
return
+
+//* Bound Variant *//
+
+/datum/shieldcall/bound
+ var/expected_type
+ var/datum/bound
+
+/datum/shieldcall/bound/New(datum/binding)
+ ASSERT(expected_type && istype(binding, expected_type))
+ src.bound = binding
+
+/datum/shieldcall/bound/Destroy()
+ src.bound = null
+ return ..()
diff --git a/code/datums/soundbytes/effects/combat.dm b/code/datums/soundbytes/effects/combat.dm
new file mode 100644
index 000000000000..dd1dd5ef5dd9
--- /dev/null
+++ b/code/datums/soundbytes/effects/combat.dm
@@ -0,0 +1,47 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/soundbyte/parry_start
+ name = "Active Parry (Start)"
+ is_sfx = TRUE
+ path = 'sound/soundbytes/effects/combat/parry-cycle.ogg'
+
+/datum/soundbyte/grouped/metal_parry
+ name = "Parry (Metal-Metal)"
+ is_sfx = TRUE
+ path = list(
+ 'sound/soundbytes/effects/combat/parry-metal1.ogg',
+ 'sound/soundbytes/effects/combat/parry-metal2.ogg',
+ )
+
+/datum/soundbyte/grouped/wood_parry
+ name = "Parry (Wood-Wood)"
+ is_sfx = TRUE
+ path = list(
+ 'sound/soundbytes/effects/combat/parry-wood1.ogg',
+ 'sound/soundbytes/effects/combat/parry-wood2.ogg',
+ )
+
+/datum/soundbyte/grouped/block_metal_with_wood
+ name = "Block (Metal-Wood)"
+ is_sfx = TRUE
+ path = list(
+ 'sound/soundbytes/effects/combat/block-metal-on-wood-1.ogg',
+ 'sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg',
+ )
+
+/datum/soundbyte/grouped/block_metal_with_metal
+ name = "Block (Metal-Metal)"
+ is_sfx = TRUE
+ path = list(
+ 'sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg',
+ 'sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg',
+ )
+
+/datum/soundbyte/grouped/block_wood_with_wood
+ name = "Block (Wood-Wood)"
+ is_sfx = TRUE
+ path = list(
+ 'sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg',
+ 'sound/soundbytes/effects/combat/block-wood-on-wood-2.ogg',
+ )
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index e0980c2b8def..010e8d88d0c2 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -6,6 +6,9 @@
*
* each effect potentially has its own amount of variable arguments that
* can be passed into apply_status_effect. they will be detailed per-file.
+ *
+ * todo: /datum/prototype/status_effect
+ * todo: /datum/prototype/status_effect/simple for normal ones.
*/
/datum/status_effect
abstract_type = /datum/status_effect
@@ -196,9 +199,11 @@
/**
* remove a status effect
*
+ * * will remove grouped effects entirely.
+ *
* @params
* * path - path to effect
- * * stacks - stacks to remove for grouped and stacking, default is all.
+ * * stacks - stacks to remove for stacking, default is all.
*
* @return stacks **left**. for single effects this is probably 0.
*/
diff --git a/code/datums/unarmed_attack.dm b/code/datums/unarmed_attack.dm
index 6179ef081fe7..ff6e629e7b91 100644
--- a/code/datums/unarmed_attack.dm
+++ b/code/datums/unarmed_attack.dm
@@ -52,6 +52,9 @@ GLOBAL_LIST_EMPTY(unarmed_attack_cache)
var/eye_attack_text
var/eye_attack_text_victim
+/datum/unarmed_attack/proc/operator""()
+ return pick(attack_verb_legacy)
+
//* Feedback
/datum/unarmed_attack/proc/get_sparring_variant()
diff --git a/code/datums/uplink/visible_weapons.dm b/code/datums/uplink/visible_weapons.dm
index 04a3acad2112..776ebc499b9e 100644
--- a/code/datums/uplink/visible_weapons.dm
+++ b/code/datums/uplink/visible_weapons.dm
@@ -17,17 +17,17 @@
/datum/uplink_item/item/visible_weapons/energy_sword
name = "Energy Sword, Colorable"
item_cost = 40
- path = /obj/item/melee/energy/sword
+ path = /obj/item/melee/transforming/energy/sword
/datum/uplink_item/item/visible_weapons/energy_sword_pirate
name = "Energy Cutlass, Colorable"
item_cost = 40
- path = /obj/item/melee/energy/sword/pirate
+ path = /obj/item/melee/transforming/energy/sword/cutlass
-/datum/uplink_item/item/visible_weapons/energy_spear
- name = "Energy Spear, Colorable"
- item_cost = 50
- path = /obj/item/melee/energy/spear
+// /datum/uplink_item/item/visible_weapons/energy_spear
+// name = "Energy Spear, Colorable"
+// item_cost = 50
+// path = /obj/item/melee/transforming/energy/spear
/datum/uplink_item/item/visible_weapons/claymore
name = "Claymore"
diff --git a/code/game/antagonist/outsider/deathsquad.dm b/code/game/antagonist/outsider/deathsquad.dm
index 931ae8626d41..26517f966973 100644
--- a/code/game/antagonist/outsider/deathsquad.dm
+++ b/code/game/antagonist/outsider/deathsquad.dm
@@ -49,7 +49,7 @@ var/datum/antagonist/deathsquad/deathsquad
player.equip_to_slot_or_del(new /obj/item/gun/ballistic/revolver/combat(player), SLOT_ID_BELT)
player.equip_to_slot_or_del(new /obj/item/gun/energy/pulse_rifle(player), /datum/inventory_slot/abstract/hand/right)
player.equip_to_slot_or_del(new /obj/item/hardsuit/ert/assetprotection(player), SLOT_ID_BACK)
- player.equip_to_slot_or_del(new /obj/item/melee/energy/sword(player), SLOT_ID_SUIT_STORAGE)
+ player.equip_to_slot_or_del(new /obj/item/melee/transforming/energy/sword(player), SLOT_ID_SUIT_STORAGE)
// player.implant_loyalty()
var/obj/item/card/id/id = create_id("Asset Protection", player)
diff --git a/code/game/atoms/atom-construction.dm b/code/game/atoms/atom-construction.dm
new file mode 100644
index 000000000000..94d85815a35e
--- /dev/null
+++ b/code/game/atoms/atom-construction.dm
@@ -0,0 +1,60 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Deconstruction *//
+
+/**
+ * called to semantically deconstruct an atom
+ *
+ * @params
+ * * method - how we were deconstructed
+ */
+/atom/proc/deconstruct(method = ATOM_DECONSTRUCT_DISASSEMBLED)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ // send signal
+ // todo: signal
+ // do da funny logic
+ deconstructed(method)
+ // drop things after so things that rely on having objects don't break
+ drop_products(method, drop_location())
+ // goodbye, cruel world
+ break_apart(method)
+
+/**
+ * called to actually destroy ourselves
+ */
+/atom/proc/break_apart(method)
+ qdel(src)
+
+/**
+ * called when we are deconstructed
+ *
+ * **do not drop products in here**
+ *
+ * @params
+ * - method - how we were deconstructed
+ */
+/atom/proc/deconstructed(method)
+ return
+
+/**
+ * called to drop the products of deconstruction
+ *
+ * @params
+ * * method - how we were deconstructed
+ * * where - where to drop products; set in base if null to drop_location().
+ */
+/atom/proc/drop_products(method, atom/where = drop_location())
+ return
+
+/**
+ * called to move a product to a place
+ *
+ * @params
+ * * method - how we were deconstructed
+ * * dropping - movable in question
+ * * where - where to move to
+ */
+/atom/proc/drop_product(method, atom/movable/dropping, atom/where)
+ dropping.forceMove(where || drop_location())
diff --git a/code/game/atoms/atom-damage.dm b/code/game/atoms/atom-damage.dm
new file mode 100644
index 000000000000..61ebc475db1c
--- /dev/null
+++ b/code/game/atoms/atom-damage.dm
@@ -0,0 +1,333 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Damage Instance Handling *//
+
+/**
+ * standard damage handling - process an instance of damage
+ *
+ * args are the same as shieldcall args, because this directly invokes shield/armorcalls
+ *
+ * * additional args past the normal shieldcall args are allowed, but not before!
+ * * the entire args list is extracted via return, allowing for handling by caller.
+ * * damage is assumed to be zone'd if def_zone is set; otherwise it's overall
+ * * please note that overall damage generally doesn't check armor properly for speed reasons!
+ *
+ * @return modified args
+ */
+/atom/proc/run_damage_instance(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ process_damage_instance(args, hit_zone)
+ if(shieldcall_flags & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ return args.Copy() // args are only valid during a call; it's destroyed after.
+ inflict_damage_instance(arglist(args))
+ return args.Copy() // args are only valid during a call; it's destroyed after.
+
+/**
+ * [/atom/proc/run_damage_instance()], but doesn't actually do damage.
+ *
+ * @return modified args
+ */
+/atom/proc/check_damage_instance(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ process_damage_instance(args, hit_zone, TRUE)
+ return args.Copy() // args are only valid during a call; it's destroyed after.
+
+/**
+ * process an instance of damage through defense handling.
+ */
+/atom/proc/process_damage_instance(list/shieldcall_args, filter_zone, fake_attack)
+ if(!(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_SKIP_SHIELDCALLS))
+ run_shieldcalls(shieldcall_args, fake_attack)
+ if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & (SHIELDCALL_FLAGS_SHOULD_TERMINATE | SHIELDCALL_FLAGS_BLOCK_ATTACK))
+ return
+ if(!(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_SKIP_ARMORCALLS))
+ run_armorcalls(shieldcall_args, fake_attack, filter_zone)
+
+/**
+ * inflict an instance of damage.
+ *
+ * * this happens after shieldcalls, armor checks, etc, all resolve.
+ * * at this point, nothing should modify damage
+ * * for things like limb damage and armor handling, check the armor/etc in process_damage_instance
+ * * for this reason, we do not allow any returns.
+ * * if hit_zone is not specified, this is considered overall damage.
+ * * overall damage is implementation-defined, so it's recommended to, ironically, not try to standardize that too much.
+ * * this is pretty much the hand-off proc where damage goes from hit processing / defense checks to the damage system for an entity
+ * * the damage system can be a medical system or just the atom integrity system.
+ */
+/atom/proc/inflict_damage_instance(SHIELDCALL_PROC_HEADER)
+ if(!integrity_enabled)
+ return
+ if(inflict_damage_type_special(args))
+ return
+ switch(damage_type)
+ if(BRUTE)
+ if(BURN)
+ else
+ return // normal atoms can't take non brute-burn damage
+ // default atom damage handling
+ inflict_atom_damage(
+ damage,
+ damage_type,
+ damage_tier,
+ damage_flag,
+ damage_mode,
+ hit_zone,
+ attack_type,
+ weapon,
+ )
+
+/**
+ * decodes damage type to what it should actually do
+ *
+ * * this is for hybrid / semantic damage types like bio-acid and searing damage to work
+ *
+ * @return TRUE to handle the damage type.
+ */
+/atom/proc/inflict_damage_type_special(list/shieldcall_args)
+ return FALSE
+
+//* Damage Processing API *//
+
+/**
+ * takes damage from a generic attack, taking into account armor but not shields.
+ * this does not handle playing sounds / anything, this is strictly generic damage handling
+ * usable by anything.
+ *
+ * * This does **not** invoke the shieldcall API!
+ * * This does **not** invoke the armor API!
+ * * This is because damage instance processing should be processing that.
+ *
+ * @params
+ * * damage - raw damage
+ * * damage_type - (optional) damage type to inflict
+ * * damage_tier - (optional) resulting damage tier
+ * * damage_flag - (optional) resulting damage armor flag from [code/__DEFINES/combat/armor.dm]
+ * * damage_mode - (optional) DAMAGE_MODE_* flags
+ * * hit_zone - (optional) the zone being hit
+ * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
+ * * weapon - (optional) attacking datum; same format as shieldcall API. See shieldcalls for more information.
+ *
+ * @return raw damage taken
+ */
+/atom/proc/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon)
+ if(!integrity_enabled)
+ return 0
+ if(integrity_flags & INTEGRITY_INDESTRUCTIBLE)
+ return 0
+ if(!damage)
+ return 0
+ . = integrity
+ damage_integrity(damage)
+ . = . - integrity
+
+
+//* Integrity - Direct Manipulation *//
+
+/**
+ * damages integrity directly, ignoring armor / shields
+ *
+ * @params
+ * * amount - how much
+ * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this.
+ * * do_not_break - skip calling atom_break
+ */
+/atom/proc/damage_integrity(amount, gradual, do_not_break)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ var/was_working = integrity > integrity_failure
+ integrity = max(0, integrity - amount)
+ if(was_working && integrity <= integrity_failure && !do_not_break)
+ atom_break()
+ if(integrity <= 0)
+ atom_destruction()
+
+/**
+ * heals integrity directly
+ *
+ * @params
+ * * amount - how much
+ * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this.
+ * * do_not_fix - skip calling atom_fix
+ *
+ * @return amount healed
+ */
+/atom/proc/heal_integrity(amount, gradual, do_not_fix)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ var/was_failing = integrity <= integrity_failure
+ . = integrity
+ integrity = min(integrity_max, integrity + amount)
+ . = integrity - .
+ if(was_failing && integrity > integrity_failure && !do_not_fix)
+ atom_fix()
+
+/**
+ * directly sets integrity - ignores armor / sihelds
+ *
+ * will not call [damage_integrity] or [heal_integrity]
+ * will call [atom_break], [atom_fix], [atom_destruction]
+ *
+ * @params
+ * * amount - how much to set to?
+ */
+/atom/proc/set_integrity(amount)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ var/was_failing = integrity <= integrity_failure
+ integrity = clamp(amount, 0, integrity_max)
+ if(!was_failing && integrity <= integrity_failure)
+ atom_break()
+ else if(was_failing && integrity > integrity_failure)
+ atom_fix()
+ if(integrity <= 0)
+ atom_destruction()
+
+/**
+ * sets max integrity - will automatically reduce integrity if it's above max.
+ *
+ * will not call [damage_integrity]
+ * will call [atom_break], [atom_fix], [atom_destruction]
+ *
+ * @params
+ * * amount - how much to set to
+ */
+/atom/proc/set_max_integrity(amount)
+ integrity_max = max(amount, 0)
+ if(integrity < integrity_max)
+ return
+ var/was_broken = integrity <= integrity_failure
+ integrity = integrity_max
+ if(!was_broken && (integrity <= integrity_failure))
+ atom_break()
+ if(integrity <= 0)
+ atom_destruction()
+
+/**
+ * sets integrity and max integrity - will automatically reduce integrity if it's above max.
+ *
+ * will not call [damage_integrity]
+ * will call [atom_break], [atom_fix], [atom_destruction]
+ *
+ * @params
+ * * integrity - how much to set integrity to
+ * * integrity_max - how much to set integrity_max to
+ */
+/atom/proc/set_full_integrity(integrity, integrity_max)
+ src.integrity_max = max(integrity_max, 0)
+ var/was_broken = src.integrity <= integrity_failure
+ src.integrity = clamp(integrity, 0, integrity_max)
+ var/now_broken = integrity <= integrity_failure
+ if(!was_broken && now_broken)
+ atom_break()
+ else if(was_broken && !now_broken)
+ atom_fix()
+ if(integrity <= 0)
+ atom_destruction()
+
+/**
+ * Set integrity to a multiple of initial.
+ *
+ * And fully restore it if specified.
+ * Otherwise, will retain the last percentage.
+ */
+/atom/proc/set_multiplied_integrity(factor, restore)
+ var/was_broken = src.integrity <= integrity_failure
+ if(restore)
+ integrity = integrity_max = initial(integrity_max) * factor
+ if(was_broken && integrity > integrity_failure)
+ atom_fix()
+ return
+ var/ratio = integrity / integrity_max
+ integrity_max = initial(integrity_max) * factor
+ integrity = integrity_max * ratio
+ var/now_broken = integrity <= integrity_failure
+ if(!was_broken && now_broken)
+ atom_break()
+ else if(was_broken && !now_broken)
+ atom_fix()
+ if(integrity <= 0)
+ atom_destruction()
+
+/**
+ * adjusts integrity - routes directly to [damage_integrity] and [heal_integrity]
+ *
+ * will call [damage_integrity]
+ * will call [atom_break], [atom_fix], [atom_destruction]
+ *
+ * @params
+ * * amount - how much
+ * * gradual - burst or gradual?
+ * * no_checks - do not call fix/break
+ */
+/atom/proc/adjust_integrity(amount, gradual, no_checks)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ if(amount > 0)
+ return heal_integrity(amount, gradual, no_checks)
+ else
+ return damage_integrity(amount, gradual, no_checks)
+
+/**
+ * adjusts max integrity - will automatically reduce integrity if it's above max.
+ *
+ * will call [damage_integrity]
+ * will call [atom_break], [atom_fix], [atom_destruction]
+ *
+ * @params
+ * * amount - how much to adjust by
+ * * gradual - burst or gradual?
+ */
+/atom/proc/adjust_max_integrity(amount, gradual)
+ // lazy route lmao
+ set_max_integrity(integrity_max + amount)
+
+//* Integrity - Check / Getters *//
+
+/**
+ * percent integrity, rounded.
+ */
+/atom/proc/percent_integrity(round_to = 0.1)
+ return integrity_max? round(integrity / integrity_max, round_to) : 0
+
+/atom/proc/is_integrity_broken()
+ return atom_flags & ATOM_BROKEN
+
+/atom/proc/is_integrity_damaged()
+ return integrity < integrity_max
+
+//* Integrity - Events *//
+
+/**
+ * called when integrity reaches 0 from a non 0 value
+ */
+/atom/proc/atom_destruction()
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ if(!(integrity_flags & INTEGRITY_NO_DECONSTRUCT))
+ deconstruct(ATOM_DECONSTRUCT_DESTROYED)
+
+/**
+ * called when integrity drops below or at integrity_failure
+ *
+ * if integrity_failure is 0, this is called before destruction.
+ */
+/atom/proc/atom_break()
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ if(integrity > integrity_failure)
+ damage_integrity(integrity - integrity_failure, do_not_break = TRUE)
+ atom_flags |= ATOM_BROKEN
+
+/**
+ * called when integrity rises above integrity_failure
+ *
+ * if integrity_failure is 0, this still works.
+ */
+/atom/proc/atom_fix()
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ if(integrity < integrity_failure)
+ heal_integrity(integrity_failure - integrity + 1, do_not_fix = TRUE)
+ atom_flags &= ~ATOM_BROKEN
diff --git a/code/game/atoms/atom-defense.dm b/code/game/atoms/atom-defense.dm
new file mode 100644
index 000000000000..3f2f7141d7be
--- /dev/null
+++ b/code/game/atoms/atom-defense.dm
@@ -0,0 +1,532 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//! Welcome to hell.
+
+// todo: everything needs comsigs comsigs comsigs
+
+//* External API / Damage Receiving *//
+
+/**
+ * todo: implement on most atoms/generic damage system
+ * todo: replace legacy_ex_act entirely with this
+ *
+ * React to being hit by an explosive shockwave
+ *
+ * ? Tip for overrides: . = ..() when you want signal to be sent, mdify power before if you need to; to ignore parent
+ * ? block power, just `return power` in your proc after . = ..().
+ *
+ * @params
+ * - power - power our turf was hit with
+ * - direction - DIR_BIT bits; can bwe null if it wasn't a wave explosion!!
+ * - explosion - explosion automata datum; can be null
+ *
+ * @return power after falloff (e.g. hit with 30 power, return 20 to apply 10 falloff)
+ */
+/atom/proc/ex_act(power, dir, datum/automata/wave/explosion/E)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, power, dir, E)
+ return power
+
+/**
+ * called on melee hit
+ *
+ * todo: add clickchain datum, instead of multiplier
+ *
+ * * check CLICKCHAIN_FLAGS_* as needed, especially UNCONDITIONAL_ABORT and ATTACK_ABORT
+ * * clickchain flags are sent down through parent calls.
+ *
+ * @params
+ * * user - person attacking
+ * * weapon - weapon used
+ * * target_zone - zone targeted
+ * * mult - damage multiplier
+ *
+ * @return clickchain flags to append
+ */
+/atom/proc/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain)
+ return CLICKCHAIN_DO_NOT_ATTACK
+
+/**
+ * called on unarmed melee hit
+ *
+ * todo: add clickchain datum, instead of multiplier
+ *
+ * @params
+ * * user - person attacking
+ * * style - unarmed attack datum
+ * * target_zone - zone targeted
+ * * mult - damage multiplier
+ *
+ * @return clickchain flags to append
+ */
+/atom/proc/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain)
+ return CLICKCHAIN_DO_NOT_ATTACK
+
+/**
+ * Because this is the proc that calls on_impact(), handling is necessarily
+ * done in here for a lot of things.
+ *
+ * * Overrides should modify proc args directly (e.g. impact_flags) and then call ..()
+ * if it needs to be taken account by default handling.
+ * * Overrides should not edit args after ..(), as args are only passed up, not down.
+ * * Overrides should, for that reason, always call ..() last.
+ * * This semantically means 'we are **about** to be hit, do anything for special processing'.
+ * * If you need to delete a projectile on impact, use `on_bullet_act()`; that's called after the contact actually happens.
+ *
+ * Things to keep in mind
+ * * 'efficiency' arg is **extremely** powerful. Please don't lower it to dismal values for no reason.
+ * * use PROJECTILE_IMPACT_BLOCKED instead of setting efficiency to 0 if an impact is entirely blocked
+ * * semantically, efficiency 0 means shield from all damages, IMPACT_BLOCKED means it hit something else
+ * * bullet_act is this way because we don't make a `/datum/event_args/actor/clickchain` with every call, so it needs a way of propagating blocking behavior and impact flags up/down the chain.
+ *
+ * @params
+ * * proj - the projectile
+ * * impact_flags - PROJECTILE_IMPACT_* flags
+ * * def_zone - impacting zone; calculated by projectile side, usually
+ * * efficiency - 0 to 1, inclusive. ratio of effects, including damage, to pass through.
+ *
+ * todo: add PROJECTILE_IMPACT_DELETE_AFTER as opposed to DELETE? so rest of effects can still run
+ * todo: shieldcalls still fire if target aborts without unconditional abort, they should not do that.
+ *
+ * @return new impact_flags
+ */
+/atom/proc/bullet_act(obj/projectile/proj, impact_flags, def_zone, efficiency = 1)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ // lower calls can change flags before we trigger
+ // check if we're still hitting
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return impact_flags
+ // 0. fire signal
+ SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, args)
+ // check if we're still hitting
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return impact_flags
+ // 1. fire shieldcalls
+ atom_shieldcall_handle_bullet(args, FALSE, NONE)
+ // check if we're still hitting
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return impact_flags
+ // 2. fire on_bullet_act
+ impact_flags |= on_bullet_act(proj, impact_flags, args)
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return impact_flags
+ // 3. fire projectile-side on_impact
+ return proj.on_impact(src, impact_flags, def_zone, efficiency)
+
+/**
+ * So, turns out, BYOND passes `args` list **down** on a ..() via arglist(args) equivalent but not back up.
+ *
+ * This means that modified bullet act args can't actually propagate through.
+ * To handle this, we have this function to actually perform the effects of said bullet act.
+ *
+ * * This basically semantically means 'we are being hit, do effects for it'.
+ * * This is called before the projectile-side impact, which is where the damage is usually inflicted.
+ * * Modify `impact_flags` directly before ..(), and `.` after ..()
+ * * Check `.` after ..() if it isn't the last call so you know when to abort the processing as needed.
+ * * Args will propagate **up** (closer to /atom) `..()` calls, but not back down (if base `/atom` changes something you won't get it on your sub-type)
+ * * For this reason `bullet_act_args` is provided so you can mutably edit it. Do **not** edit the projectile or the impact flags; return the impact flags for automatic addition.
+ *
+ * @params
+ * * proj - hitting projectile; immutable
+ * * impact_flags - impact flags; immutable. edit directly before ..() call, return edited values after.
+ * * bullet_act_args - access to the rest of the args in `bullet_act`. Mutable, except for the projectile and the impact flags.
+ */
+/atom/proc/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ return impact_flags
+
+//* Hitsound API *//
+
+// todo: stuff like metal limbs punching walls making special sounds
+// todo: this probably needs a rework
+
+/**
+ * gets hitsound override. return a value to be fed into playsound, or null for default.
+ *
+ * @params
+ * * damage_type - damage type like brute / burn / etc
+ * * damage_mode - damage mode for piercing / whatnot
+ * * attack_type - attack type enum like melee / projectile / thrown / unarmed / etc
+ * * weapon - attacking /obj/item for melee / thrown, /obj/projectile for ranged, /mob for unarmed
+ */
+/atom/proc/hitsound_override(damage_type, damage_mode, attack_type, datum/weapon)
+ return // default is null
+
+/atom/proc/hitsound_melee(obj/item/I)
+ . = I.attacksound_override(src, ATTACK_TYPE_MELEE)
+ if(!isnull(.))
+ return
+ . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_MELEE, I)
+ if(.)
+ return
+ . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound
+ if(.)
+ return
+ switch(I.damtype)
+ if(BRUTE)
+ return "swing_hit"
+ if(BURN)
+ return "'sound/items/welder.ogg"
+ else
+ return "swing_hit"
+
+/atom/proc/hitsound_projectile(obj/projectile/P)
+ //? todo: projectile gets final say
+ . = hitsound_override(P.damtype, P.damage_mode, ATTACK_TYPE_PROJECTILE, P)
+ if(.)
+ return
+ return islist(P.impact_sounds)? pick(P.impact_sounds) : P.impact_sounds
+
+/atom/proc/hitsound_throwhit(obj/item/I)
+ . = I.attacksound_override(src, ATTACK_TYPE_THROWN)
+ if(!isnull(.))
+ return
+ . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_THROWN, I)
+ if(.)
+ return
+ . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound
+ if(.)
+ return
+ switch(I.damtype)
+ if(BRUTE)
+ return "swing_hit"
+ if(BURN)
+ return 'sound/items/welder.ogg'
+ else
+ return "swing_hit"
+
+/atom/proc/hitsound_unarmed(mob/M, datum/unarmed_attack/style)
+ //? todo: style gets final say
+ . = hitsound_override(M, style.damage_mode, ATTACK_TYPE_UNARMED, style)
+ if(.)
+ return
+ // todo: way to override this from style side? we don't just want hitsound brute/burn.
+ . = (style.damage_type == BURN? hit_sound_burn : hit_sound_brute) || style.attack_sound
+
+
+//* Armor *//
+
+/**
+ * resets our armor to initial values
+ */
+/atom/proc/reset_armor()
+ set_armor(initial(armor_type))
+
+/**
+ * sets our armor
+ *
+ * @params
+ * * what - list of armor values or a /datum/armor path
+ */
+/atom/proc/set_armor(what)
+ armor = fetch_armor_struct(what)
+
+/**
+ * gets our armor datum or otherwise make sure it exists
+ */
+/atom/proc/fetch_armor()
+ RETURN_TYPE(/datum/armor)
+ return armor || (armor = generate_armor())
+
+/**
+ * get default armor datum
+ */
+/atom/proc/generate_armor()
+ return fetch_armor_struct(armor_type)
+
+/**
+ * runs an attack against armor
+ *
+ * * side effects are **not** allowed
+ * * this is the 'just checking' version.
+ *
+ * params are modified and then returned as a list
+ *
+ * * See [atom_shieldcall()] for what is going on here.
+ * * SHIELDCALL_ARG_* are used as the return list's indices.
+ *
+ * @params
+ * * damage - raw damage
+ * * damtype - damage type
+ * * tier - penetration / attack tier
+ * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
+ * * mode - damage_mode
+ * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
+ * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm]
+ * * flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm]
+ * * hit_zone - where were they hit?
+ * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain - the clickchain for melee attacks.
+ *
+ * @return args, modified, as list.
+ */
+/atom/proc/check_armor(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_SLEEP(TRUE)
+ run_armorcalls(args, TRUE)
+ return args.Copy()
+
+/**
+ * runs an attack against armor
+ *
+ * * side effects are allowed
+ *
+ * params are modified and then returned as a list
+ *
+ * * See [atom_shieldcall()] for what is going on here.
+ * * SHIELDCALL_ARG_* are used as the return list's indices.
+ *
+ * @params
+ * * damage - raw damage
+ * * damtype - damage type
+ * * tier - penetration / attack tier
+ * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
+ * * mode - damage_mode
+ * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
+ * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm]
+ * * flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm]
+ * * hit_zone - where were they hit?
+ * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain - the clickchain for melee attacks.
+ *
+ * @return args, modified, as list.
+ */
+/atom/proc/run_armor(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_SLEEP(TRUE)
+ run_armorcalls(args, FALSE)
+ return args.Copy()
+
+//* Shieldcalls *//
+
+/**
+ * runs an attack against shields
+ *
+ * * side effects are **not** allowed
+ * * this is the 'just checking' version.
+ *
+ * params are modified and then returned as a list
+ *
+ * * This is the dynamic shieldcall system. It's best to do what you want in specific shieldcall hooks if possible.
+ * * What this means is that this can't, say, redirect or delete a projectile, because bullet act handling is where that happens.
+ * * This more or less just lets you modify incoming damage instances sometimes.
+ * * The args are not copied! They're passed back directly. This has implications.
+ * * Make sure you pass in SHIELDCALL_FLAG_SECOND_CALL if **any** kind of shieldcall invocation has happened during this attack.
+ * * SECOND_CALL is required to tell things that something is not the first time, so you don't get doubled blocking efficiency.
+ *
+ * @params
+ * * damage - raw damage
+ * * damtype - damage type
+ * * damage_tier - penetration / attack tier
+ * * damage_flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
+ * * damage_mode - damage_mode
+ * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
+ * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm]
+ * * shieldcall_flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm]
+ * * hit_zone - where were they hit?
+ * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain - the clickchain for melee attacks.
+ *
+ * @return args, modified, as list.
+ */
+/atom/proc/atom_shieldcheck(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_SLEEP(TRUE)
+ run_shieldcalls(args, TRUE)
+ return args.Copy()
+
+/**
+ * runs an attack against shields
+ *
+ * * side effects are allowed
+ * * this is run during an actual attack
+ *
+ * params are modified and then returned as a list
+ *
+ * * This is the dynamic shieldcall system. It's best to do what you want in specific shieldcall hooks if possible.
+ * * What this means is that this can't, say, redirect or delete a projectile, because bullet act handling is where that happens.
+ * * This more or less just lets you modify incoming damage instances sometimes.
+ * * The args are not copied! They're passed back directly. This has implications.
+ * * Make sure you pass in SHIELDCALL_FLAG_SECOND_CALL if **any** kind of shieldcall invocation has happened during this attack.
+ * * SECOND_CALL is required to tell things that something is not the first time, so you don't get doubled blocking efficiency.
+ *
+ * @params
+ * * damage - raw damage
+ * * damtype - damage type
+ * * damage_tier - penetration / attack tier
+ * * damage_flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
+ * * damage_mode - damage_mode
+ * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
+ * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm]
+ * * shieldcall_flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm]
+ * * hit_zone - where were they hit?
+ * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain - the clickchain for melee attacks.
+ *
+ * @return args, modified, as list.
+ */
+/atom/proc/atom_shieldcall(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_SLEEP(TRUE)
+ run_shieldcalls(args, FALSE)
+ return args.Copy()
+
+/**
+ * Runs a damage instance against shieldcalls
+ *
+ * * This is a low level proc. Make sure you undersatnd how shieldcalls work [__DEFINES/combat/shieldcall.dm].
+ */
+/atom/proc/run_shieldcalls(list/shieldcall_args, fake_attack)
+ SHOULD_NOT_SLEEP(TRUE)
+ SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL, shieldcall_args, fake_attack)
+ if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE)
+ return
+ for(var/datum/shieldcall/calling as anything in shieldcalls)
+ if(!calling.low_level_intercept)
+ continue
+ calling.handle_shieldcall(src, args, fake_attack)
+ if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE)
+ break
+
+/**
+ * Runs a damage instance against armor
+ *
+ * * This is a low level proc. Make sure you undersatnd how shieldcalls work [__DEFINES/combat/shieldcall.dm].
+ */
+/atom/proc/run_armorcalls(list/shieldcall_args, fake_attack)
+ SHOULD_NOT_SLEEP(TRUE)
+ SEND_SIGNAL(src, COMSIG_ATOM_ARMORCALL, shieldcall_args, fake_attack)
+ if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE)
+ return
+ var/datum/armor/our_armor = fetch_armor()
+ our_armor.handle_shieldcall(shieldcall_args, fake_attack)
+
+/atom/proc/register_shieldcall(datum/shieldcall/delegate)
+ SHOULD_NOT_SLEEP(TRUE)
+ LAZYINITLIST(shieldcalls)
+ BINARY_INSERT(delegate, shieldcalls, /datum/shieldcall, delegate, priority, COMPARE_KEY)
+
+/atom/proc/unregister_shieldcall(datum/shieldcall/delegate)
+ SHOULD_NOT_SLEEP(TRUE)
+ LAZYREMOVE(shieldcalls, delegate)
+
+//* Shieldcalls - Focused / High-Level *//
+
+/**
+ * Runs shieldcalls for handle_touch
+ *
+ * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up.
+ *
+ * @params
+ * * e_args (optional) the clickchain event, if any; **This is mutable.**
+ * * contact_flags - SHIELDCALL_CONTACT_FLAG_*
+ * * contact_specific - SHIELDCALL_CONTACT_SPECIFIC_*
+ * * fake_attack - just checking!
+ * * shieldcall_flags - shieldcall flags. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain_flags - clickchain flags. [code/__DEFINES/procs/clickcode.dm]
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/atom/proc/atom_shieldcall_handle_touch(datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific, fake_attack, shieldcall_flags)
+ SHOULD_NOT_SLEEP(TRUE)
+ // cannot parry yourself
+ if(e_args.performer == src)
+ return shieldcall_flags
+ // send query signal
+ SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_TOUCH)
+ . = shieldcall_flags
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ . |= shieldcall.handle_touch(src, ., fake_attack, e_args, contact_flags, contact_specific)
+
+/**
+ * Runs shieldcalls for handle_unarmed_melee
+ *
+ * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up.
+ *
+ * @params
+ * * style - the unarmed_attack datum being used
+ * * e_args (optional) the clickchain event, if any; **This is mutable.**
+ * * fake_attack - (optional) just checking!
+ * * shieldcall_flags - (optional) shieldcall flags. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain_flags - (optional) clickchain flags. [code/__DEFINES/procs/clickcode.dm]
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/atom/proc/atom_shieldcall_handle_unarmed_melee(datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args, fake_attack, shieldcall_flags, clickchain_flags)
+ SHOULD_NOT_SLEEP(TRUE)
+ // cannot parry yourself
+ if(e_args.performer == src)
+ return shieldcall_flags
+ // send query signal
+ SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_UNARMED_MELEE)
+ . = shieldcall_flags
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ . |= shieldcall.handle_unarmed_melee(src, ., fake_attack, style, e_args)
+
+/**
+ * Runs shieldcalls for handle_item_melee
+ *
+ * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up.
+ *
+ * @params
+ * * weapon - the item being used to swing with
+ * * e_args - (optional) the clickchain event, if any; **This is mutable.**
+ * * fake_attack - (optional) just checking!
+ * * shieldcall_flags - (optional) shieldcall flags. [code/__DEFINES/combat/shieldcall.dm]
+ * * clickchain_flags - (optional) clickchain flags. [code/__DEFINES/procs/clickcode.dm]
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/atom/proc/atom_shieldcall_handle_item_melee(obj/item/weapon, datum/event_args/actor/clickchain/e_args, fake_attack, shieldcall_flags, clickchain_flags)
+ SHOULD_NOT_SLEEP(TRUE)
+ // cannot parry yourself
+ if(e_args.performer == src)
+ return shieldcall_flags
+ // send query signal
+ SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_ITEM_MELEE)
+ . = shieldcall_flags
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ . |= shieldcall.handle_item_melee(src, ., fake_attack, weapon, e_args)
+
+/**
+ * Runs shieldcalls for handle_bullet
+ *
+ * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up.
+ *
+ * @params
+ * * bullet_act_args - indexed list of bullet_act args.
+ * * shieldcall_returns - existing returns from other shieldcalls
+ * * fake_attack - just checking!
+ * * shieldcall_flags - shieldcall flags. [code/__DEFINES/combat/shieldcall.dm]
+ *
+ * @return SHIELDCALL_FLAG_TERMINATE or NONE
+ */
+/atom/proc/atom_shieldcall_handle_bullet(list/bullet_act_args, fake_attack, shieldcall_flags)
+ SHOULD_NOT_SLEEP(TRUE)
+ // cannot parry yourself
+ var/obj/projectile/proj = bullet_act_args[BULLET_ACT_ARG_PROJECTILE]
+ if(proj.firer == src && (bullet_act_args[BULLET_ACT_ARG_FLAGS] & PROJECTILE_IMPACT_POINT_BLANK))
+ return shieldcall_flags
+ // send query signal
+ SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_BULLET_ACT)
+ . = shieldcall_flags
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ . |= shieldcall.handle_bullet(src, ., fake_attack, bullet_act_args)
+
+/**
+ * Runs shieldcalls for handle_throw_impact
+ *
+ * @params
+ * * thrown - the thrown object's data
+ * * fake_attack - just checking!
+ * * shieldcall_flags - shieldcall flags. [code/__DEFINES/combat/shieldcall.dm]
+ *
+ * @return SHIELDCALL_FLAG_* flags
+ */
+/atom/proc/atom_shieldcall_handle_throw_impact(datum/thrownthing/thrown, fake_attack, shieldcall_flags)
+ SHOULD_NOT_SLEEP(TRUE)
+ // cannot parry yourself
+ if(thrown.thrower == src && thrown.dist_travelled <= 1)
+ return shieldcall_flags
+ // send query signal
+ SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_THROW_IMPACT)
+ . = shieldcall_flags
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ . |= shieldcall.handle_throw_impact(src, ., fake_attack, thrown)
diff --git a/code/game/atoms/atom.dm b/code/game/atoms/atom.dm
index b9bcee5fe40d..95403d73eb59 100644
--- a/code/game/atoms/atom.dm
+++ b/code/game/atoms/atom.dm
@@ -882,6 +882,7 @@
return T.has_gravity()
+// todo: annihilate this in favor of ATOM_PASS_INCORPOREAL
/atom/proc/is_incorporeal()
return FALSE
diff --git a/code/game/atoms/buckling.dm b/code/game/atoms/buckling.dm
index 45e68574979c..b286d57f88ef 100644
--- a/code/game/atoms/buckling.dm
+++ b/code/game/atoms/buckling.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
+//* Copyright (c) 2024 silicons *//
/atom/movable/MouseDroppedOn(atom/dropping, mob/user, proximity, params)
if(drag_drop_buckle_interaction(dropping, user))
@@ -40,7 +40,7 @@
return FALSE
if(!user.Adjacent(src) || !A.Adjacent(src))
return FALSE
- if(SEND_SIGNAL(src, COMSIG_MOVABLE_DRAG_DROP_BUCKLE_INTERACTION, A, user) & COMPONENT_HANDLED_BUCKLE_INTERACTION)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_DRAG_DROP_BUCKLE_INTERACTION, A, user) & SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED)
return TRUE
if(!buckle_allowed || (buckle_flags & BUCKLING_NO_DEFAULT_BUCKLE))
return FALSE
@@ -69,7 +69,7 @@
// todo: refactor below
if(user.incapacitated())
return TRUE
- if(SEND_SIGNAL(src, COMSIG_MOVABLE_CLICK_UNBUCKLE_INTERACTION, user) & COMPONENT_HANDLED_BUCKLE_INTERACTION)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_CLICK_UNBUCKLE_INTERACTION, user) & SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED)
return TRUE
if(!buckle_allowed || (buckle_flags & BUCKLING_NO_DEFAULT_UNBUCKLE))
return FALSE
@@ -92,7 +92,7 @@
/atom/movable/proc/user_unbuckle_mob(mob/M, flags, mob/user, semantic)
SHOULD_CALL_PARENT(TRUE)
. = SEND_SIGNAL(src, COMSIG_MOVABLE_USER_UNBUCKLE_MOB, M, flags, user, semantic)
- if(. & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
. = unbuckle_mob(M, flags, user, semantic)
if(!.)
@@ -126,7 +126,7 @@
/atom/movable/proc/user_buckle_mob(mob/M, flags, mob/user, semantic)
SHOULD_CALL_PARENT(TRUE)
. = SEND_SIGNAL(src, COMSIG_MOVABLE_USER_BUCKLE_MOB, M, flags, user, semantic)
- if(. & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
if((buckle_flags & BUCKLING_NO_USER_BUCKLE_OTHER_TO_SELF) && (user == src))
return FALSE
@@ -166,7 +166,7 @@
if(M == src)
return FALSE
- if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_BUCKLE_MOB, M, flags, user, semantic) & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_BUCKLE_MOB, M, flags, user, semantic) & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
if(!(flags & BUCKLE_OP_FORCE) && !can_buckle_mob(M, flags, user, semantic))
@@ -244,9 +244,9 @@
/atom/movable/proc/can_buckle_mob(mob/M, flags, mob/user, semantic)
SHOULD_CALL_PARENT(TRUE)
. = SEND_SIGNAL(src, COMSIG_MOVABLE_CAN_BUCKLE_MOB, M, flags, user, semantic)
- if(. & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
- else if(. & COMPONENT_FORCE_BUCKLE_OPERATION)
+ else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION)
return TRUE
if(!(flags & BUCKLE_OP_IGNORE_LOC) && !M.Adjacent(src))
return FALSE
@@ -276,9 +276,9 @@
/atom/movable/proc/can_unbuckle_mob(mob/M, flags, mob/user, semantic)
SHOULD_CALL_PARENT(TRUE)
. = SEND_SIGNAL(src, COMSIG_MOVABLE_CAN_UNBUCKLE_MOB, M, flags, user, semantic)
- if(. & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
- else if(. & COMPONENT_FORCE_BUCKLE_OPERATION)
+ else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION)
return TRUE
return TRUE
/**
@@ -329,7 +329,7 @@
/atom/movable/proc/resist_unbuckle_interaction(mob/M)
set waitfor = FALSE
ASSERT(M in buckled_mobs)
- if(SEND_SIGNAL(src, COMSIG_MOVABLE_RESIST_UNBUCKLE_INTERACTION, M) & COMPONENT_HANDLED_BUCKLE_INTERACTION)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_RESIST_UNBUCKLE_INTERACTION, M) & SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED)
return
if(!buckle_allowed || (buckle_flags & BUCKLING_NO_DEFAULT_RESIST))
return FALSE
@@ -410,14 +410,14 @@
*/
/mob/proc/buckled(atom/movable/AM, flags, mob/user, semantic)
SHOULD_CALL_PARENT(TRUE)
- SEND_SIGNAL(src, COMSIG_MOB_BUCKLED, AM, flags, user, semantic)
+ SEND_SIGNAL(src, COMSIG_MOB_BUCKLED_TO, AM, flags, user, semantic)
/**
* called when we're unbuckled from something
*/
/mob/proc/unbuckled(atom/movable/AM, flags, mob/user, semantic)
SHOULD_CALL_PARENT(TRUE)
- SEND_SIGNAL(src, COMSIG_MOB_UNBUCKLED, AM, flags, user, semantic)
+ SEND_SIGNAL(src, COMSIG_MOB_UNBUCKLED_FROM, AM, flags, user, semantic)
/**
* can we buckle to something?
@@ -426,10 +426,10 @@
*/
/mob/proc/can_buckle(atom/movable/AM, flags, mob/user, semantic, movable_opinion)
SHOULD_CALL_PARENT(TRUE)
- . = SEND_SIGNAL(src, COMSIG_MOB_CAN_BUCKLE, AM, flags, user, semantic, movable_opinion)
- if(. & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ . = SEND_SIGNAL(src, COMSIG_MOB_CAN_BUCKLE_TO, AM, flags, user, semantic, movable_opinion)
+ if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
- else if(. & COMPONENT_FORCE_BUCKLE_OPERATION)
+ else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION)
return TRUE
return movable_opinion
@@ -440,10 +440,10 @@
*/
/mob/proc/can_unbuckle(atom/movable/AM, flags, mob/user, semantic, movable_opinion)
SHOULD_CALL_PARENT(TRUE)
- . = SEND_SIGNAL(src, COMSIG_MOB_CAN_UNBUCKLE, AM, flags, user, semantic, movable_opinion)
- if(. & COMPONENT_BLOCK_BUCKLE_OPERATION)
+ . = SEND_SIGNAL(src, COMSIG_MOB_CAN_UNBUCKLE_FROM, AM, flags, user, semantic, movable_opinion)
+ if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION)
return FALSE
- else if(. & COMPONENT_FORCE_BUCKLE_OPERATION)
+ else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION)
return TRUE
return movable_opinion
diff --git a/code/game/atoms/defense.dm b/code/game/atoms/defense.dm
deleted file mode 100644
index 6fbf61c69c72..000000000000
--- a/code/game/atoms/defense.dm
+++ /dev/null
@@ -1,547 +0,0 @@
-//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
-
-//! Welcome to the atom damage module.
-//! Enjoy the bitfield and #define vomit.
-
-// todo: everything needs comsigs comsigs comsigs
-
-//? Hooks / External
-
-/**
- * todo: implement on most atoms/generic damage system
- * todo: replace legacy_ex_act entirely with this
- *
- * React to being hit by an explosive shockwave
- *
- * ? Tip for overrides: . = ..() when you want signal to be sent, mdify power before if you need to; to ignore parent
- * ? block power, just `return power` in your proc after . = ..().
- *
- * @params
- * - power - power our turf was hit with
- * - direction - DIR_BIT bits; can bwe null if it wasn't a wave explosion!!
- * - explosion - explosion automata datum; can be null
- *
- * @return power after falloff (e.g. hit with 30 power, return 20 to apply 10 falloff)
- */
-/atom/proc/ex_act(power, dir, datum/automata/wave/explosion/E)
- SHOULD_CALL_PARENT(TRUE)
- SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, power, dir, E)
- return power
-
-/**
- * called on melee hit
- *
- * @params
- * * user - person attacking
- * * weapon - weapon used
- * * target_zone - zone targeted
- * * mult - damage multiplier
- *
- * @return clickchain flags to append
- */
-/atom/proc/melee_act(mob/user, obj/item/weapon, target_zone, mult = 1)
- return CLICKCHAIN_DO_NOT_ATTACK
-
-/**
- * called on unarmed melee hit
- *
- * @params
- * * user - person attacking
- * * style - unarmed attack datum
- * * target_zone - zone targeted
- * * mult - damage multiplier
- *
- * @return clickchain flags to append
- */
-/atom/proc/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult = 1)
- return CLICKCHAIN_DO_NOT_ATTACK
-
-//? Damage API
-
-/**
- * takes damage from a generic attack, taking into account armor but not shields.
- * this does not handle playing sounds / anything, this is strictly generic damage handling
- * usable by anything.
- *
- * @params
- * * damage - raw damage
- * * tier - (optional) penetration / attack tier
- * * flag - (optional) armor flag as seen in [code/__DEFINES/combat/armor.dm]; leave out to not run armor.
- * * mode - (optional) damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * gradual - loud effects like glass clanging should not happen. use for stuff like acid and fire.
- *
- * @return raw damage taken
- */
-/atom/proc/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual)
- if(!integrity_enabled)
- return 0
- if(integrity_flags & INTEGRITY_INDESTRUCTIBLE)
- return 0
- if(flag)
- var/list/returned = run_armor(arglist(args))
- damage = returned[1]
- mode = returned[4]
- if(!damage)
- return
- . = integrity
- damage_integrity(damage)
- . = . - integrity
-
-//? Hitsound API
-
-// todo: stuff like metal limbs punching walls making special sounds
-// todo: this probably needs a rework
-
-/**
- * gets hitsound override. return a value to be fed into playsound, or null for default.
- *
- * @params
- * * damage_type - damage type like brute / burn / etc
- * * damage_mode - damage mode for piercing / whatnot
- * * attack_type - attack type enum like melee / projectile / thrown / unarmed / etc
- * * weapon - attacking /obj/item for melee / thrown, /obj/projectile for ranged, /mob for unarmed
- */
-/atom/proc/hitsound_override(damage_type, damage_mode, attack_type, datum/weapon)
- return // default is null
-
-/atom/proc/hitsound_melee(obj/item/I)
- . = I.attacksound_override(src, ATTACK_TYPE_MELEE)
- if(!isnull(.))
- return
- . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_MELEE, I)
- if(.)
- return
- . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound
- if(.)
- return
- switch(I.damtype)
- if(BRUTE)
- return "swing_hit"
- if(BURN)
- return "'sound/items/welder.ogg"
- else
- return "swing_hit"
-
-/atom/proc/hitsound_projectile(obj/projectile/P)
- //? todo: projectile gets final say
- . = hitsound_override(P.damtype, P.damage_mode, ATTACK_TYPE_PROJECTILE, P)
- if(.)
- return
- return islist(P.impact_sounds)? pick(P.impact_sounds) : P.impact_sounds
-
-/atom/proc/hitsound_throwhit(obj/item/I)
- . = I.attacksound_override(src, ATTACK_TYPE_THROWN)
- if(!isnull(.))
- return
- . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_THROWN, I)
- if(.)
- return
- . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound
- if(.)
- return
- switch(I.damtype)
- if(BRUTE)
- return "swing_hit"
- if(BURN)
- return 'sound/items/welder.ogg'
- else
- return "swing_hit"
-
-/atom/proc/hitsound_unarmed(mob/M, datum/unarmed_attack/style)
- //? todo: style gets final say
- . = hitsound_override(M, style.damage_mode, ATTACK_TYPE_UNARMED, style)
- if(.)
- return
- // todo: way to override this from style side? we don't just want hitsound brute/burn.
- . = (style.damage_type == BURN? hit_sound_burn : hit_sound_brute) || style.attack_sound
-
-//? Direct Integrity
-
-/**
- * damages integrity directly, ignoring armor / shields
- *
- * @params
- * * amount - how much
- * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this.
- * * do_not_break - skip calling atom_break
- */
-/atom/proc/damage_integrity(amount, gradual, do_not_break)
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- var/was_working = integrity > integrity_failure
- integrity = max(0, integrity - amount)
- if(was_working && integrity <= integrity_failure && !do_not_break)
- atom_break()
- if(integrity <= 0)
- atom_destruction()
-
-/**
- * heals integrity directly
- *
- * @params
- * * amount - how much
- * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this.
- * * do_not_fix - skip calling atom_fix
- *
- * @return amount healed
- */
-/atom/proc/heal_integrity(amount, gradual, do_not_fix)
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- var/was_failing = integrity <= integrity_failure
- . = integrity
- integrity = min(integrity_max, integrity + amount)
- . = integrity - .
- if(was_failing && integrity > integrity_failure && !do_not_fix)
- atom_fix()
-
-/**
- * directly sets integrity - ignores armor / sihelds
- *
- * will not call [damage_integrity] or [heal_integrity]
- * will call [atom_break], [atom_fix], [atom_destruction]
- *
- * @params
- * * amount - how much to set to?
- */
-/atom/proc/set_integrity(amount)
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- var/was_failing = integrity <= integrity_failure
- integrity = clamp(amount, 0, integrity_max)
- if(!was_failing && integrity <= integrity_failure)
- atom_break()
- else if(was_failing && integrity > integrity_failure)
- atom_fix()
- if(integrity <= 0)
- atom_destruction()
-
-/**
- * sets max integrity - will automatically reduce integrity if it's above max.
- *
- * will not call [damage_integrity]
- * will call [atom_break], [atom_fix], [atom_destruction]
- *
- * @params
- * * amount - how much to set to
- */
-/atom/proc/set_max_integrity(amount)
- integrity_max = max(amount, 0)
- if(integrity < integrity_max)
- return
- var/was_broken = integrity <= integrity_failure
- integrity = integrity_max
- if(!was_broken && (integrity <= integrity_failure))
- atom_break()
- if(integrity <= 0)
- atom_destruction()
-
-/**
- * sets integrity and max integrity - will automatically reduce integrity if it's above max.
- *
- * will not call [damage_integrity]
- * will call [atom_break], [atom_fix], [atom_destruction]
- *
- * @params
- * * integrity - how much to set integrity to
- * * integrity_max - how much to set integrity_max to
- */
-/atom/proc/set_full_integrity(integrity, integrity_max)
- src.integrity_max = max(integrity_max, 0)
- var/was_broken = src.integrity <= integrity_failure
- src.integrity = clamp(integrity, 0, integrity_max)
- var/now_broken = integrity <= integrity_failure
- if(!was_broken && now_broken)
- atom_break()
- else if(was_broken && !now_broken)
- atom_fix()
- if(integrity <= 0)
- atom_destruction()
-
-/**
- * Set integrity to a multiple of initial.
- *
- * And fully restore it if specified.
- * Otherwise, will retain the last percentage.
- */
-/atom/proc/set_multiplied_integrity(factor, restore)
- var/was_broken = src.integrity <= integrity_failure
- if(restore)
- integrity = integrity_max = initial(integrity_max) * factor
- if(was_broken && integrity > integrity_failure)
- atom_fix()
- return
- var/ratio = integrity / integrity_max
- integrity_max = initial(integrity_max) * factor
- integrity = integrity_max * ratio
- var/now_broken = integrity <= integrity_failure
- if(!was_broken && now_broken)
- atom_break()
- else if(was_broken && !now_broken)
- atom_fix()
- if(integrity <= 0)
- atom_destruction()
-
-/**
- * adjusts integrity - routes directly to [damage_integrity] and [heal_integrity]
- *
- * will call [damage_integrity]
- * will call [atom_break], [atom_fix], [atom_destruction]
- *
- * @params
- * * amount - how much
- * * gradual - burst or gradual?
- * * no_checks - do not call fix/break
- */
-/atom/proc/adjust_integrity(amount, gradual, no_checks)
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- if(amount > 0)
- return heal_integrity(amount, gradual, no_checks)
- else
- return damage_integrity(amount, gradual, no_checks)
-
-/**
- * adjusts max integrity - will automatically reduce integrity if it's above max.
- *
- * will call [damage_integrity]
- * will call [atom_break], [atom_fix], [atom_destruction]
- *
- * @params
- * * amount - how much to adjust by
- * * gradual - burst or gradual?
- */
-/atom/proc/adjust_max_integrity(amount, gradual)
- // lazy route lmao
- set_max_integrity(integrity_max + amount)
-
-/**
- * percent integrity, rounded.
- */
-/atom/proc/percent_integrity(round_to = 0.1)
- return integrity_max? round(integrity / integrity_max, round_to) : 0
-
-/atom/proc/is_integrity_broken()
- return atom_flags & ATOM_BROKEN
-
-/atom/proc/is_integrity_damaged()
- return integrity < integrity_max
-
-//? Thresholds & Events
-
-/**
- * called when integrity reaches 0 from a non 0 value
- */
-/atom/proc/atom_destruction()
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- if(!(integrity_flags & INTEGRITY_NO_DECONSTRUCT))
- deconstruct(ATOM_DECONSTRUCT_DESTROYED)
-
-/**
- * called when integrity drops below or at integrity_failure
- *
- * if integrity_failure is 0, this is called before destruction.
- */
-/atom/proc/atom_break()
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- if(integrity > integrity_failure)
- damage_integrity(integrity - integrity_failure, do_not_break = TRUE)
- atom_flags |= ATOM_BROKEN
-
-/**
- * called when integrity rises above integrity_failure
- *
- * if integrity_failure is 0, this still works.
- */
-/atom/proc/atom_fix()
- SHOULD_CALL_PARENT(TRUE)
- SHOULD_NOT_SLEEP(TRUE)
- if(integrity < integrity_failure)
- heal_integrity(integrity_failure - integrity + 1, do_not_fix = TRUE)
- atom_flags &= ~ATOM_BROKEN
-
-//? Deconstruction
-
-/**
- * called to semantically deconstruct an atom
- *
- * @params
- * * method - how we were deconstructed
- */
-/atom/proc/deconstruct(method = ATOM_DECONSTRUCT_DISASSEMBLED)
- SHOULD_NOT_OVERRIDE(TRUE)
-
- // send signal
- // todo: signal
- // do da funny logic
- deconstructed(method)
- // drop things after so things that rely on having objects don't break
- drop_products(method, drop_location())
- // goodbye, cruel world
- break_apart(method)
-
-/**
- * called to actually destroy ourselves
- */
-/atom/proc/break_apart(method)
- qdel(src)
-
-/**
- * called when we are deconstructed
- *
- * **do not drop products in here**
- *
- * @params
- * - method - how we were deconstructed
- */
-/atom/proc/deconstructed(method)
- return
-
-/**
- * called to drop the products of deconstruction
- *
- * @params
- * * method - how we were deconstructed
- * * where - where to drop products; set in base if null to drop_location().
- */
-/atom/proc/drop_products(method, atom/where = drop_location())
- return
-
-/**
- * called to move a product to a place
- *
- * @params
- * * method - how we were deconstructed
- * * dropping - movable in question
- * * where - where to move to
- */
-/atom/proc/drop_product(method, atom/movable/dropping, atom/where)
- dropping.forceMove(where || drop_location())
-
-//? Armor
-
-/**
- * resets our armor to initial values
- */
-/atom/proc/reset_armor()
- set_armor(initial(armor_type))
-
-/**
- * sets our armor
- *
- * @params
- * * what - list of armor values or a /datum/armor path
- */
-/atom/proc/set_armor(what)
- armor = fetch_armor_struct(what)
-
-/**
- * gets our armor datum or otherwise make sure it exists
- */
-/atom/proc/fetch_armor()
- RETURN_TYPE(/datum/armor)
- return armor || (armor = generate_armor())
-
-/**
- * get default armor datum
- */
-/atom/proc/generate_armor()
- return fetch_armor_struct(armor_type)
-
-/**
- * calculates the resulting damage from an attack, taking into account our armor and soak
- *
- * @params
- * * damage - raw damage
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- *
- * @return args as list.
- */
-/atom/proc/check_armor(damage, tier, flag, mode, attack_type, datum/weapon)
- damage = fetch_armor().resultant_damage(damage, tier, flag)
- return args.Copy()
-
-/**
- * runs armor against an incoming attack
- * this proc can have side effects
- *
- * @params
- * * damage - raw damage
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- *
- * @return args as list.
- */
-/atom/proc/run_armor(damage, tier, flag, mode, attack_type, datum/weapon)
- damage = fetch_armor().resultant_damage(damage, tier, flag)
- return args.Copy()
-
-//? shieldcalls
-
-/**
- * checks for shields
- * not always accurate
- *
- * params are modified and then returned as a list.
- *
- * @params
- * * damage - raw damage
- * * damtype - damage type
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/dcs/signals/atoms/signals_atom_defense.dm]
- * * retval - shieldcall flags passed through components. [code/__DEFINES/dcs/signals/atoms/signals_atom_defense.dm]
- *
- * @return modified args as list
- */
-/atom/proc/atom_shieldcheck(damage, damtype, tier, flag, mode, attack_type, datum/weapon, list/additional = list(), retval = NONE)
- retval |= SHIELDCALL_JUST_CHECKING
- for(var/datum/shieldcall/calling as anything in shieldcalls)
- calling.handle_shieldcall(src, args)
- return args.Copy()
-
-/**
- * runs an attack against shields
- * side effects are allowed
- *
- * params are modified and then returned as a list
- *
- * @params
- * * damage - raw damage
- * * damtype - damage type
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm]
- * * retval - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm]
- *
- * @return modified args as list
- */
-/atom/proc/atom_shieldcall(damage, damtype, tier, flag, mode, attack_type, datum/weapon, list/additional = list(), retval = NONE)
- for(var/datum/shieldcall/calling as anything in shieldcalls)
- calling.handle_shieldcall(src, args)
- return args.Copy()
-
-/atom/proc/register_shieldcall(datum/shieldcall/delegate)
- LAZYINITLIST(shieldcalls)
- BINARY_INSERT(delegate, shieldcalls, /datum/shieldcall, delegate, priority, COMPARE_KEY)
-
-/atom/proc/unregister_shieldcall(datum/shieldcall/delegate)
- LAZYREMOVE(shieldcalls, delegate)
diff --git a/code/game/atoms/defense_old.dm b/code/game/atoms/defense_old.dm
index ab7d141eed0c..d162a947cbfd 100644
--- a/code/game/atoms/defense_old.dm
+++ b/code/game/atoms/defense_old.dm
@@ -12,10 +12,6 @@
// todo: SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)
-/atom/proc/bullet_act(obj/projectile/P, def_zone)
- P.on_hit(src, 0, def_zone)
- . = 0
-
// Called when a blob expands onto the tile the atom occupies.
/atom/proc/blob_act()
return
diff --git a/code/game/atoms/movable/throwing.dm b/code/game/atoms/movable/movable-throw.dm
similarity index 99%
rename from code/game/atoms/movable/throwing.dm
rename to code/game/atoms/movable/movable-throw.dm
index b6b21f4ce20c..f329f1ab5bd2 100644
--- a/code/game/atoms/movable/throwing.dm
+++ b/code/game/atoms/movable/movable-throw.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
+//* Copyright (c) 2024 silicons *//
//! Welcome to unoptimized hell. Enjoy your comsigs.
diff --git a/code/game/atoms/movable/movement.dm b/code/game/atoms/movable/movement.dm
index 64c2e193c2be..a0d0b0c016bf 100644
--- a/code/game/atoms/movable/movement.dm
+++ b/code/game/atoms/movable/movement.dm
@@ -438,13 +438,21 @@
* Make sure you know what you're doing if you override or call this.
*
* This *must* be a pure proc. You cannot act on the atom if you override this! Use Bump() for that.
+ *
+ * * **warning**: `newloc` is a ss13 construct. BYOND-native pixel movement doesn't have that.
+ *
+ * @params
+ * * AM - the thing trying to un-overlap us
+ * * newloc - (optional) where they're going
*/
/atom/movable/Uncross(atom/movable/AM, atom/newloc)
. = TRUE
if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS)
return FALSE
- if(isturf(newloc) && !CheckExit(AM, newloc))
- return FALSE
+ if(isturf(newloc))
+ var/our_opinion = CheckExit(AM, newloc)
+ if(!our_opinion && (AM.generic_canpass || !AM.CanPassThrough(src, newloc, our_opinion)))
+ return FALSE
/**
* Called when something uncrosses us.
@@ -514,7 +522,7 @@
var/list/old_grabbed
if(allow_grabbed)
old_grabbed = list()
- for(var/mob/M in grabbing())
+ for(var/mob/M in get_grabbing())
if(check_grab(M) < allow_grabbed)
continue
old_grabbed += M
@@ -542,7 +550,7 @@
/mob/getLocationTransitForceMoveTargets(atom/destination, recurse_levels = 0, allow_buckled = TRUE, allow_pulled = TRUE, allow_grabbed = GRAB_PASSIVE)
. = ..()
if(allow_grabbed)
- var/list/grabbing = grabbing()
+ var/list/grabbing = get_grabbing()
for(var/mob/M in grabbing)
if(check_grab(M) < allow_grabbed)
continue
diff --git a/code/game/atoms/vv.dm b/code/game/atoms/vv.dm
index 2373c129b6aa..f36655a135e7 100644
--- a/code/game/atoms/vv.dm
+++ b/code/game/atoms/vv.dm
@@ -78,7 +78,7 @@
else if(was_failing && !now_failing)
atom_fix()
-/atom/vv_get_var(var_name)
+/atom/vv_get_var(var_name, resolve)
switch(var_name)
if(NAMEOF(src, base_layer))
if(isnull(base_layer))
diff --git a/code/game/click/context.dm b/code/game/click/context.dm
index 41001e80a434..1a0686db1d69 100644
--- a/code/game/click/context.dm
+++ b/code/game/click/context.dm
@@ -25,10 +25,14 @@
return TRUE
return FALSE
+/**
+ * @params
+ * * e_args - the actor data or a single mob
+ */
/atom/proc/context_menu(datum/event_args/actor/e_args)
set waitfor = FALSE
// admin proccall support
- WRAP_MOB_TO_ACTOR_EVENT_ARGS(e_args)
+ E_ARGS_WRAP_USER_TO_ACTOR(e_args)
// todo: dynamically rebuild menu based on distance?
var/client/receiving = e_args.initiator.client
if(isnull(receiving))
diff --git a/code/game/click/items.dm b/code/game/click/items.dm
index 9514e4b15f66..607c9c2a1745 100644
--- a/code/game/click/items.dm
+++ b/code/game/click/items.dm
@@ -10,6 +10,9 @@
/**
* Called when trying to click something that the user can Reachability() to.
*
+ * todo: this should allow passing in a clickchain datum instead.
+ * todo: lazy_melee_attack() for when you don't want to.
+ *
* @params
* - target - thing hitting
* - user - user using us
@@ -46,6 +49,9 @@
/**
* Called when trying to click something that the user can't Reachability() to.
*
+ * todo: this should allow passing in a clickchain datum instead.
+ * todo: lazy_ranged_attack() for when you don't want to.
+ *
* @params
* - target - thing hitting
* - user - user using us
@@ -313,6 +319,8 @@
* called when we're used to attack a non-mob
* this doesn't actually need to be an obj.
*
+ * todo: purge mult
+ *
* @params
* * target - atom being attacked
* * clickchain - the /datum/event_args/actor/clickchain arguments included
@@ -338,17 +346,21 @@
return CLICKCHAIN_DO_NOT_PROPAGATE
//? legacy: decloak
clickchain.performer.break_cloak()
+ // set mult
+ clickchain.damage_multiplier *= mult
// click cooldown
// todo: clickcd rework
clickchain.performer.setClickCooldown(clickchain.performer.get_attack_speed(src))
// animation
clickchain.performer.animate_swing_at_target(target)
// perform the hit
- . = melee_object_hit(target, clickchain, clickchain_flags, mult)
+ . = melee_object_hit(target, clickchain, clickchain_flags)
/**
* called at base of attack_object after standard melee attack misses
*
+ * todo: purge mult
+ *
* @return clickchain flags to append
*
* @params
@@ -378,7 +390,7 @@
* * clickchain_flags - __DEFINES/procs/clickcode.dm flags
* * mult - damage multiplier
*/
-/obj/item/proc/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags, mult = 1)
+/obj/item/proc/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags)
SHOULD_CALL_PARENT(TRUE)
// harmless, just tap them and leave
@@ -406,7 +418,7 @@
visible = SPAN_DANGER("[target] has been [islist(attack_verb)? pick(attack_verb) : attack_verb] with [src] by [clickchain.performer]!")
)
// damage
- target.melee_act(clickchain.performer, src, mult = mult)
+ target.melee_act(clickchain.performer, src, null, clickchain)
// animate
target.animate_hit_by_weapon(clickchain.performer, src)
diff --git a/code/game/click/mobs.dm b/code/game/click/mobs.dm
index 7ac0ad4c7373..3f0f13fb0bb3 100644
--- a/code/game/click/mobs.dm
+++ b/code/game/click/mobs.dm
@@ -1,6 +1,9 @@
/**
* Called when trying to click on someone we can Reachability() to without an item in hand.
*
+ * todo: this should allow passing in a clickchain datum instead.
+ * todo: lazy_melee_attack() for when you don't want to.
+ *
* @params
* - target - thing we're clicking
* - clickchain_flags - see [code/__DEFINES/procs/clickcode.dm]
@@ -98,7 +101,7 @@
log_attack(key_name(src), ismob(target)? key_name(target) : "[target] ([ref(target)])", "attacked with [style.attack_name] newhp ~[newhp || "unknown"]")
/mob/proc/melee_attack_hit(atom/target, datum/event_args/actor/clickchain/clickchain, datum/unarmed_attack/style, clickchain_flags, target_zone, mult)
- . = target.unarmed_act(src, style, target_zone, mult)
+ . = target.unarmed_act(src, style, target_zone, clickchain)
if(. & CLICKCHAIN_ATTACK_MISSED)
return . | melee_attack_miss(target, clickchain, style, clickchain_flags, target_zone, mult)
// todo: the rest of this proc not qdel-safe
diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm
index 18172ed42cab..c5dfcecbd017 100644
--- a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm
+++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm
@@ -96,7 +96,7 @@
name = "sapper's kit"
/obj/item/storage/box/cargo_null_entry_kit/sapper/legacy_spawn_contents()
- new /obj/item/melee/energy/sword/ionic_rapier(src)
+ new /obj/item/melee/transforming/energy/sword/ionic_rapier(src)
new /obj/item/storage/box/syndie_kit/space(src)
new /obj/item/storage/box/syndie_kit/demolitions(src)
new /obj/item/multitool/ai_detector(src)
diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm
index d3fc78511e77..23757c6fe2cf 100644
--- a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm
+++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm
@@ -32,7 +32,7 @@
name = "Weapons - Experimental weapons crate"
contains = list(
/obj/item/gun/energy/xray = 2,
- /obj/item/shield/energy = 2,
+ /obj/item/shield/transforming/energy = 2,
)
container_type = /obj/structure/closet/crate/secure/corporate/nanotrasen
container_name = "Experimental weapons crate"
diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm
index bb09c938ed6b..fd0841c892d1 100644
--- a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm
+++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm
@@ -466,7 +466,7 @@
/obj/item/clothing/accessory/badge/holo/hos,
/obj/item/clothing/accessory/holster/waist,
/obj/item/melee/telebaton,
- /obj/item/shield/riot/tele,
+ /obj/item/shield/transforming/telescopic,
/obj/item/clothing/head/beret/sec/corporate/hos,
/obj/item/flashlight/maglight,
)
diff --git a/code/game/gamemodes/changeling/powers/armblade.dm b/code/game/gamemodes/changeling/powers/armblade.dm
index db77320eed6c..bc89f0efdeff 100644
--- a/code/game/gamemodes/changeling/powers/armblade.dm
+++ b/code/game/gamemodes/changeling/powers/armblade.dm
@@ -47,6 +47,14 @@
return 1
return 0
+// todo: full rework of all of this; changeling weapons are balanced by a numbskull holy shit fuck bay
+// - block chances are way, way too high
+// - insufficient armor penetration (ironically) for citrp combat balancing directives
+// - need to rethink changeling defensives in general, they shouldn't be reliant on parrying
+
+/datum/parry_frame/passive_block/armblade
+ parry_sfx = 'sound/weapons/slash.ogg'
+
/obj/item/melee/changeling
name = "arm weapon"
desc = "A grotesque weapon made out of bone and flesh that cleaves through people as a hot knife through butter."
@@ -62,8 +70,11 @@
var/weapType = "weapon"
var/weapLocation = "arm"
- defend_chance = 40 // The base chance for the weapon to parry.
- projectile_parry_chance = 15 // The base chance for a projectile to be deflected.
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 40;
+ parry_chance_projectile = 15;
+ parry_frame = /datum/parry_frame/passive_block/armblade;
+ }
/obj/item/melee/changeling/Initialize(mapload)
. = ..()
@@ -106,28 +117,6 @@
host.embedded -= src
qdel(src)
-/obj/item/melee/changeling/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(default_parry_check(user, attacker, damage_source) && prob(defend_chance))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/slash.ogg', 50, 1)
- return 1
- if(unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance))
- user.visible_message("\The [user] deflects [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/slash.ogg', 50, 1)
- return 1
-
- return 0
-
-/obj/item/melee/changeling/unique_parry_check(mob/user, mob/attacker, atom/damage_source)
- if(user.incapacitated() || !istype(damage_source, /obj/projectile))
- return 0
-
- var/bad_arc = global.reverse_dir[user.dir]
- if(!check_shield_arc(user, bad_arc, damage_source, attacker))
- return 0
-
- return 1
-
/obj/item/melee/changeling/arm_blade
name = "arm blade"
desc = "A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter."
@@ -138,15 +127,23 @@
edge = 1
pry = 1
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- defend_chance = 60
- projectile_parry_chance = 25
+
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 60;
+ parry_chance_projectile = 25;
+ parry_frame = /datum/parry_frame/passive_block/armblade;
+ }
/obj/item/melee/changeling/arm_blade/greater
name = "arm greatblade"
desc = "A grotesque blade made out of bone and flesh that cleaves through people and armor as a hot knife through butter."
armor_penetration = 30
- defend_chance = 70
- projectile_parry_chance = 35
+
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 70;
+ parry_chance_projectile = 35;
+ parry_frame = /datum/parry_frame/passive_block/armblade;
+ }
/obj/item/melee/changeling/claw
name = "hand claw"
@@ -156,13 +153,21 @@
sharp = 1
edge = 1
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- defend_chance = 50
- projectile_parry_chance = 15
+
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 50;
+ parry_chance_projectile = 15;
+ parry_frame = /datum/parry_frame/passive_block/armblade;
+ }
/obj/item/melee/changeling/claw/greater
name = "hand greatclaw"
damage_force = 20
armor_penetration = 20
pry = 1
- defend_chance = 60
- projectile_parry_chance = 25
+
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 60;
+ parry_chance_projectile = 25;
+ parry_frame = /datum/parry_frame/passive_block/armblade;
+ }
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index 21fc3fd4d9c5..c9e899bd85c5 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -46,12 +46,13 @@
/obj/structure/cult/pylon/attackby(obj/item/W as obj, mob/user as mob)
attackpylon(user, W.damage_force)
-/obj/structure/cult/pylon/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual)
+/obj/structure/cult/pylon/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon)
pylonhit(damage)
return damage
-/obj/structure/cult/pylon/bullet_act(var/obj/projectile/Proj)
- pylonhit(Proj.get_structure_damage())
+/obj/structure/cult/pylon/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ pylonhit(proj.get_structure_damage())
/obj/structure/cult/pylon/proc/pylonhit(var/damage)
if(!isbroken)
diff --git a/code/game/gamemodes/events/black_hole.dm b/code/game/gamemodes/events/black_hole.dm
index 1bc471d0be17..38e926507bd4 100644
--- a/code/game/gamemodes/events/black_hole.dm
+++ b/code/game/gamemodes/events/black_hole.dm
@@ -11,7 +11,7 @@
/obj/effect/bhole/Initialize(mapload)
. = ..()
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
/obj/effect/bhole/process()
if(!isturf(loc))
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 3e5e2ef17498..7d7e72d4aa4e 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -162,7 +162,7 @@
if(T)
if(istype(T, /turf/simulated/wall))
var/turf/simulated/wall/W = T
- W.inflict_atom_damage(wall_power, flag = ARMOR_BOMB) // Stronger walls can halt asteroids.
+ W.inflict_atom_damage(wall_power, damage_flag = ARMOR_BOMB) // Stronger walls can halt asteroids.
/obj/effect/meteor/proc/get_shield_damage()
return max(((max(hits, 2)) * (heavy + 1) * rand(6, 12)) / hitpwr , 0)
diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm
index 9ac85720d743..9ecdb22d753d 100644
--- a/code/game/gamemodes/sandbox/h_sandbox.dm
+++ b/code/game/gamemodes/sandbox/h_sandbox.dm
@@ -139,7 +139,7 @@ datum/hSB
continue
if(istype(O, /obj/item/dummy))
continue
- if(istype(O, /obj/item/melee/energy/sword))
+ if(istype(O, /obj/item/melee/transforming/energy/sword))
continue
if(istype(O, /obj/structure))
continue
diff --git a/code/game/gamemodes/technomancer/devices/shield_armor.dm b/code/game/gamemodes/technomancer/devices/shield_armor.dm
index 6bee50e73dd6..73892bfbcc7b 100644
--- a/code/game/gamemodes/technomancer/devices/shield_armor.dm
+++ b/code/game/gamemodes/technomancer/devices/shield_armor.dm
@@ -33,7 +33,25 @@
qdel(spark_system)
return ..()
-/obj/item/clothing/suit/armor/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+/obj/item/clothing/suit/armor/shield/equipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/clothing/suit/armor/shield/unequipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/clothing/suit/armor/shield/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack)
+ var/damage = shieldcall_args[SHIELDCALL_ARG_DAMAGE]
+ var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+
//Since this is a pierce of armor that is passive, we do not need to check if the user is incapacitated.
if(!active)
return 0
@@ -66,12 +84,11 @@
P.agony -= agony_blocked
P.damage = P.damage - damage_blocked
- user.visible_message("\The [user]'s [src] absorbs [attack_text]!")
+ user.visible_message("\The [user]'s [src] absorbs the attack!")
to_chat(user, "Your shield has absorbed most of \the [damage_source].")
spark_system.start()
playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return 0 // This shield does not block all damage, so returning 0 is needed to tell the game to apply the new damage.
/obj/item/clothing/suit/armor/shield/attack_self(mob/user)
. = ..()
diff --git a/code/game/gamemodes/technomancer/devices/tesla_armor.dm b/code/game/gamemodes/technomancer/devices/tesla_armor.dm
index d731b5b37af2..c3fe7f29bc91 100644
--- a/code/game/gamemodes/technomancer/devices/tesla_armor.dm
+++ b/code/game/gamemodes/technomancer/devices/tesla_armor.dm
@@ -21,7 +21,27 @@
var/normal_icon_state = "tesla_armor_0"
var/cooldown_to_charge = 15 SECONDS
-/obj/item/clothing/suit/armor/tesla/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+/obj/item/clothing/suit/armor/tesla/equipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/clothing/suit/armor/tesla/unequipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/clothing/suit/armor/tesla/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack)
+ var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+
+ var/datum/event_args/actor/clickchain/clickchain = shieldcall_args[SHIELDCALL_ARG_CLICKCHAIN]
+ var/mob/attacker = clickchain?.performer
+
//First, some retaliation.
if(active)
if(istype(damage_source, /obj/projectile))
@@ -47,7 +67,7 @@
ready = 1
update_icon()
to_chat(user, "\The [src] is ready to protect you once more.")
- visible_message("\The [user]'s [src.name] blocks [attack_text]!")
+ visible_message("\The [user]'s [src.name] blocks the attack!")
update_icon()
return 1
return 0
diff --git a/code/game/gamemodes/technomancer/spells/energy_siphon.dm b/code/game/gamemodes/technomancer/spells/energy_siphon.dm
index cc51d809667d..b05bc7fb2670 100644
--- a/code/game/gamemodes/technomancer/spells/energy_siphon.dm
+++ b/code/game/gamemodes/technomancer/spells/energy_siphon.dm
@@ -172,30 +172,22 @@
icon_state = "lightning"
range = WORLD_ICON_SIZE * 6
power = 5 // This fires really fast, so this may add up if someone keeps standing in the beam.
- penetrating = 5
+ legacy_penetrating = 5
-/obj/projectile/beam/lightning/energy_siphon/Bump(atom/A as mob|obj|turf|area, forced=0)
- if(A == firer) // For this, you CAN shoot yourself.
- on_impact(A)
-
- density = 0
- invisibility = 101
-
- qdel(src)
- return 1
- ..()
-
-/obj/projectile/beam/lightning/energy_siphon/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0)
- if(target_mob == firer) // This shouldn't actually occur due to Bump(), but just in-case.
- return 1
+/obj/projectile/beam/lightning/energy_siphon/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ var/mob/living/target_mob = target
+ if(!isliving(target_mob))
+ return
if(ishuman(target_mob)) // Otherwise someone else stood in the beam and is going to pay for it.
var/mob/living/carbon/human/H = target_mob
var/obj/item/organ/external/affected = H.get_organ(check_zone(BP_TORSO))
H.electrocute_act(power, src, H.get_siemens_coefficient_organ(affected), affected, 0)
else
target_mob.electrocute_act(power, src, 0.75, BP_TORSO)
- return 0 // Since this is a continous beam, it needs to keep flying until it hits the Technomancer.
-
+ return PROJECTILE_IMPACT_PIERCE
#undef SIPHON_CELL_TO_ENERGY
#undef SIPHON_FBP_TO_ENERGY
diff --git a/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm b/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm
index 8d4254e3f33d..75160bb02526 100644
--- a/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm
+++ b/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm
@@ -34,7 +34,15 @@
var/list/hit_mobs = list() //Mobs which were already hit.
var/power = 35 //How hard it will hit for with electrocute_act(), decreases with each bounce.
-/obj/projectile/beam/chain_lightning/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0)
+// todo: rework this shit :/
+
+/obj/projectile/beam/chain_lightning/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & (PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT | PROJECTILE_IMPACT_BLOCKED))
+ return
+ var/mob/living/target_mob = target
+ if(!isliving(target_mob))
+ return
//First we shock the guy we just hit.
if(ishuman(target_mob))
var/mob/living/carbon/human/H = target_mob
@@ -70,11 +78,9 @@
if(new_target)
var/turf/curloc = get_turf(target_mob)
curloc.visible_message("\The [src] bounces to \the [new_target]!")
- redirect(new_target.x, new_target.y, curloc, firer)
+ legacy_redirect(new_target.x, new_target.y, curloc, firer)
bounces--
-
- return 0
- return 1
+ return PROJECTILE_IMPACT_PIERCE
diff --git a/code/game/gamemodes/technomancer/spells/projectile/lightning.dm b/code/game/gamemodes/technomancer/spells/projectile/lightning.dm
index 4ba595480de3..58e3bd19292d 100644
--- a/code/game/gamemodes/technomancer/spells/projectile/lightning.dm
+++ b/code/game/gamemodes/technomancer/spells/projectile/lightning.dm
@@ -32,7 +32,13 @@
var/power = 60 //How hard it will hit for with electrocute_act().
-/obj/projectile/beam/lightning/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0)
+/obj/projectile/beam/lightning/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & (PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT | PROJECTILE_IMPACT_BLOCKED))
+ return
+ var/mob/living/target_mob = target
+ if(!isliving(target_mob))
+ return
if(ishuman(target_mob))
var/mob/living/carbon/human/H = target_mob
var/obj/item/organ/external/affected = H.get_organ(check_zone(BP_TORSO))
diff --git a/code/game/gamemodes/technomancer/spells/reflect.dm b/code/game/gamemodes/technomancer/spells/reflect.dm
index b1bd2526260f..293f5de0827d 100644
--- a/code/game/gamemodes/technomancer/spells/reflect.dm
+++ b/code/game/gamemodes/technomancer/spells/reflect.dm
@@ -30,64 +30,79 @@
spark_system = null
return ..()
-/obj/item/spell/reflect/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+/obj/item/spell/reflect/pickup(mob/user, flags, atom/oldLoc)
+ . = ..()
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/spell/reflect/dropped(mob/user, flags, atom/newLoc)
+ . = ..()
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/spell/reflect/proc/shieldcall(datum/source, list/shieldcall_args, fake_attack)
+ if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE)
+ return
+ var/mob/user = source
if(user.incapacitated())
- return 0
+ return
- var/damage_to_energy_cost = (damage_to_energy_multiplier * damage)
+ var/mob/attacker
+ var/damage_to_energy_cost = (damage_to_energy_multiplier * shieldcall_args[SHIELDCALL_ARG_DAMAGE])
+ var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON]
if(!pay_energy(damage_to_energy_cost))
to_chat(owner, "Your shield fades due to lack of energy!")
qdel(src)
- return 0
-
- //block as long as they are not directly behind us
- var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block
- if(check_shield_arc(user, bad_arc, damage_source, attacker))
+ return
- if(istype(damage_source, /obj/projectile))
- var/obj/projectile/P = damage_source
+ if(istype(damage_source, /obj/projectile))
+ var/obj/projectile/P = damage_source
+ attacker = P.firer
- if(P.starting && !P.reflected)
- visible_message("\The [user]'s [src.name] reflects [attack_text]!")
+ if(P.starting && !P.reflected)
+ visible_message("\The [user]'s [src.name] reflects [P]!")
- var/turf/curloc = get_turf(user)
+ var/turf/curloc = get_turf(user)
- // redirect the projectile
- P.redirect(P.starting.x, P.starting.y, curloc, user)
- P.reflected = 1
- if(check_for_scepter())
- P.damage = P.damage * 1.5
+ // redirect the projectile
+ P.legacy_redirect(P.starting.x, P.starting.y, curloc, user)
+ P.reflected = 1
+ if(check_for_scepter())
+ P.damage = P.damage * 1.5
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- // now send a log so that admins don't think they're shooting themselves on purpose.
- log_and_message_admins("[user] reflected [attacker]'s attack back at them.")
-
- if(!reflecting)
- reflecting = 1
- spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected.
- to_chat(owner, "Your shield fades due being used up!")
- qdel(src)
-
- return PROJECTILE_CONTINUE // complete projectile permutation
-
- else if(istype(damage_source, /obj/item))
- var/obj/item/W = damage_source
+ spark_system.start()
+ playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
+ // now send a log so that admins don't think they're shooting themselves on purpose.
if(attacker)
- W.melee_interaction_chain(attacker)
- to_chat(attacker, "Your [damage_source.name] goes through \the [src] in one location, comes out \
- on the same side, and hits you!")
-
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
-
log_and_message_admins("[user] reflected [attacker]'s attack back at them.")
- if(!reflecting)
- reflecting = 1
- spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected.
- to_chat(owner, "Your shield fades due being used up!")
- qdel(src)
- return 1
- return 0
+ if(!reflecting)
+ reflecting = 1
+ spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected.
+ to_chat(owner, "Your shield fades due being used up!")
+ qdel(src)
+
+ shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_PASSTHROUGH | SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_TERMINATE
+
+ else if(istype(damage_source, /obj/item))
+ var/obj/item/W = damage_source
+ var/datum/event_args/actor/clickchain/clickchain = shieldcall_args[SHIELDCALL_ARG_CLICKCHAIN]
+ attacker = clickchain.performer
+ if(attacker)
+ W.melee_interaction_chain(attacker, attacker)
+ to_chat(attacker, "Your [damage_source] goes through \the [src] in one location, comes out \
+ on the same side, and hits you!")
+
+ spark_system.start()
+ playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
+
+ log_and_message_admins("[user] reflected [attacker]'s attack back at them.")
+
+ if(!reflecting)
+ reflecting = 1
+ spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected.
+ to_chat(owner, "Your shield fades due being used up!")
+ qdel(src)
+ shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_TERMINATE
diff --git a/code/game/gamemodes/technomancer/spells/shield.dm b/code/game/gamemodes/technomancer/spells/shield.dm
index ad005ef6572b..cd487b6917e3 100644
--- a/code/game/gamemodes/technomancer/spells/shield.dm
+++ b/code/game/gamemodes/technomancer/spells/shield.dm
@@ -28,7 +28,24 @@
spark_system = null
return ..()
-/obj/item/spell/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+/obj/item/spell/shield/equipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/spell/shield/unequipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/spell/shield/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack)
+ var/damage = shieldcall_args[SHIELDCALL_ARG_DAMAGE]
+
if(user.incapacitated())
return 0
@@ -50,12 +67,8 @@
qdel(src)
return 0
- //block as long as they are not directly behind us
- var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block
- if(check_shield_arc(user, bad_arc, damage_source, attacker))
- user.visible_message("\The [user]'s [src] blocks [attack_text]!")
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- adjust_instability(2)
- return 1
- return 0
+
+ user.visible_message("\The [user]'s [src] blocks the attack!")
+ spark_system.start()
+ playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
+ adjust_instability(2)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index dfb3d955096c..3c8879162bf9 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -156,7 +156,7 @@
///Volume of interface sounds.
var/clickvol = 40
var/obj/item/circuitboard/circuit = null
- ///If false, SSmachines. If true, SSfastprocess.
+ ///If false, SSmachines. If true, SSprocess_5fps.
var/speed_process = FALSE
var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE
@@ -176,7 +176,7 @@
if(!speed_process)
START_MACHINE_PROCESSING(src)
else
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
if(!mapload) // area handles this
power_change()
@@ -186,7 +186,7 @@
if(!speed_process)
STOP_MACHINE_PROCESSING(src)
else
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
if(component_parts)
for(var/atom/A in component_parts)
if(A.loc == src) // If the components are inside the machine, delete them.
@@ -236,22 +236,6 @@
panel_open = panel_opened
update_appearance()
-/obj/machinery/legacy_ex_act(severity)
- switch(severity)
- if(1.0)
- qdel(src)
- return
- if(2.0)
- if(prob(50))
- qdel(src)
- return
- if(3.0)
- if(prob(25))
- qdel(src)
- return
- else
- return
-
/obj/machinery/vv_edit_var(var_name, new_value)
if(var_name == NAMEOF(src, use_power))
update_use_power(new_value)
diff --git a/code/game/machinery/doors/airlock/airlock.dm b/code/game/machinery/doors/airlock/airlock.dm
index 819a31bca723..541fb37f2597 100644
--- a/code/game/machinery/doors/airlock/airlock.dm
+++ b/code/game/machinery/doors/airlock/airlock.dm
@@ -909,7 +909,7 @@ About the new airlock wires panel:
for(var/turf/turf in locs)
for(var/atom/movable/AM in turf)
if(AM.airlock_crush(DOOR_CRUSH_DAMAGE))
- inflict_atom_damage(DOOR_CRUSH_DAMAGE, flag = ARMOR_MELEE)
+ inflict_atom_damage(DOOR_CRUSH_DAMAGE, damage_flag = ARMOR_MELEE)
use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people
has_beeped = 0
diff --git a/code/game/machinery/doors/defense.dm b/code/game/machinery/doors/defense.dm
index eb2c3b434913..850c13bc2a3f 100644
--- a/code/game/machinery/doors/defense.dm
+++ b/code/game/machinery/doors/defense.dm
@@ -3,5 +3,5 @@
if(exposed_temperature > maxtemperature)
var/burndamage = log(RAND_F(0.9, 1.1) * (exposed_temperature - maxtemperature))
- inflict_atom_damage(burndamage, flag = ARMOR_FIRE, gradual = TRUE)
+ inflict_atom_damage(burndamage, damage_flag = ARMOR_FIRE, damage_mode = DAMAGE_MODE_GRADUAL)
return ..()
diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm
index 6236374a1055..1b6b5fb30232 100644
--- a/code/game/machinery/doors/unpowered.dm
+++ b/code/game/machinery/doors/unpowered.dm
@@ -9,7 +9,7 @@
return
/obj/machinery/door/unpowered/attackby(obj/item/I as obj, mob/user as mob)
- if(istype(I, /obj/item/melee/energy/blade)) return
+ if(istype(I, /obj/item/melee/ninja_energy_blade)) return
if(src.locked) return
..()
return
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 9bdb1357afc6..95921c715c2f 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -194,7 +194,7 @@
return
//Emags and ninja swords? You may pass.
- if (istype(I, /obj/item/melee/energy/blade))
+ if (istype(I, /obj/item/melee/ninja_energy_blade))
if(emag_act(10, user))
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src.loc)
diff --git a/code/game/machinery/fire_alarm.dm b/code/game/machinery/fire_alarm.dm
index 1ee973b36652..40757b9a3aae 100644
--- a/code/game/machinery/fire_alarm.dm
+++ b/code/game/machinery/fire_alarm.dm
@@ -121,8 +121,9 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21)
/obj/machinery/fire_alarm/attack_ai(mob/user)
return attack_hand(user)
-/obj/machinery/fire_alarm/bullet_act()
- return alarm()
+/obj/machinery/fire_alarm/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ alarm()
/obj/machinery/fire_alarm/emp_act(severity)
if(prob(50 / severity))
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index 77f4f9f052ba..c2d4b3020add 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -240,7 +240,7 @@
if(!speed_process)
START_MACHINE_PROCESSING(src)
else
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
update_appearance()
//! Plumbing Signal
diff --git a/code/game/machinery/turnstile.dm b/code/game/machinery/turnstile.dm
index 36e45c60134c..e694c18177b7 100644
--- a/code/game/machinery/turnstile.dm
+++ b/code/game/machinery/turnstile.dm
@@ -18,11 +18,6 @@
/obj/machinery/turnstile/CanAtmosPass(turf/T)
return TRUE
-/*
-/obj/machinery/turnstile/bullet_act(obj/item/projectile/P, def_zone)
- return -1 //Pass through!
-*/
-
/obj/machinery/turnstile/proc/allowed_access(var/mob/B)
if(B.pulledby && ismob(B.pulledby))
return allowed(B.pulledby) | allowed(B)
diff --git a/code/game/machinery/turrets/subtypes/misc.dm b/code/game/machinery/turrets/subtypes/misc.dm
index 83e75bc795d2..e7ed365d0ea0 100644
--- a/code/game/machinery/turrets/subtypes/misc.dm
+++ b/code/game/machinery/turrets/subtypes/misc.dm
@@ -75,7 +75,7 @@
integrity_max = 200
turret_type = "industrial"
-/obj/machinery/porta_turret/industrial/bullet_act(obj/projectile/Proj)
+/obj/machinery/porta_turret/industrial/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
if(enabled)
if(!attacked && !emagged)
diff --git a/code/game/machinery/turrets/turret-ai_holder.dm b/code/game/machinery/turrets/turret-ai_holder.dm
index 6c2a3d1a6669..01590029a9e9 100644
--- a/code/game/machinery/turrets/turret-ai_holder.dm
+++ b/code/game/machinery/turrets/turret-ai_holder.dm
@@ -161,7 +161,8 @@
*/
/datum/ai_holder/turret/proc/trace_trajectory(atom/target, angle)
var/obj/projectile/trace/trace = new(agent.loc)
- trace.prepare_trace(target, null, TRUE)
+ trace.only_opacity = TRUE
+ trace.prepare_trace(target)
trace.fire(angle)
return trace.could_hit_target
diff --git a/code/game/machinery/turrets/turret.dm b/code/game/machinery/turrets/turret.dm
index 265f3c8afd84..02a1860c9e10 100644
--- a/code/game/machinery/turrets/turret.dm
+++ b/code/game/machinery/turrets/turret.dm
@@ -463,16 +463,18 @@
if(!gradual && prob(45) && amount > 5)
spark_system.start()
-/obj/machinery/porta_turret/bullet_act(obj/projectile/P, def_zone)
+/obj/machinery/porta_turret/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
aggro_for(6 SECONDS)
- if(P.firer)
+ if(proj.firer)
// todo: proper AI provoke API.
var/datum/ai_holder/turret/snowflake_ai_holder = ai_holder
- snowflake_ai_holder.retaliate(P.firer)
- return ..()
+ snowflake_ai_holder.retaliate(proj.firer)
-/obj/machinery/porta_turret/melee_act(mob/user, obj/item/weapon, target_zone, mult)
+/obj/machinery/porta_turret/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain)
. = ..()
+ if(. & CLICKCHAIN_FLAGS_ATTACK_ABORT)
+ return
if(. > 0)
aggro_for(6 SECONDS, user)
// todo: proper AI provoke API.
diff --git a/code/game/objects/defense.dm b/code/game/objects/defense.dm
deleted file mode 100644
index 29786cc77d47..000000000000
--- a/code/game/objects/defense.dm
+++ /dev/null
@@ -1,82 +0,0 @@
-//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
-
-/obj/ex_act(power, dir, datum/automata/wave/explosion/E)
- . = ..()
- // todo: wave explosions
- if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
- return
- inflict_atom_damage(power * (1 / 2.5), flag = ARMOR_BOMB)
-
-/obj/legacy_ex_act(severity, target)
- . = ..()
- if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
- return
- inflict_atom_damage(global._legacy_ex_atom_damage[severity], flag = ARMOR_BOMB)
-
-/obj/melee_act(mob/user, obj/item/weapon, target_zone, mult)
- if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
- return
- inflict_atom_damage(weapon.damage_force, weapon.damage_tier, weapon.damage_flag, weapon.damage_mode, ATTACK_TYPE_MELEE, weapon)
- return NONE
-
-/obj/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult)
- // todo: this should just be style.attack(attacker, src)
- if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
- return
- inflict_atom_damage(style.get_unarmed_damage(attacker, src), style.damage_tier, style.damage_flag, style.damage_mode, ATTACK_TYPE_UNARMED, attacker)
- return NONE
-
-/obj/bullet_act(obj/projectile/P, def_zone)
- . = ..()
- // todo: should this really be here?
- if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
- return
- inflict_atom_damage(P.get_structure_damage(), P.damage_tier, P.damage_flag, P.damage_mode, ATTACK_TYPE_PROJECTILE, P)
-
-/obj/throw_impacted(atom/movable/AM, datum/thrownthing/TT)
- . = ..()
- // todo: /atom/movable/proc/throw_impact_attack(atom/target)
- if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
- return
- if(isitem(AM))
- var/obj/item/I = AM
- inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM)
- else
- inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM)
- // if we got destroyed
- if(QDELETED(src) && (obj_flags & OBJ_ALLOW_THROW_THROUGH))
- . |= COMPONENT_THROW_HIT_PIERCE
-
-/obj/blob_act(obj/structure/blob/blob)
- . = ..()
- inflict_atom_damage(100, flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE)
-
-/obj/drop_products(method, atom/where)
- . = ..()
- if(obj_storage?.drop_on_deconstruction_methods & method)
- obj_storage.drop_everything_at(where)
-
-/obj/hitsound_melee(obj/item/I)
- if(!isnull(material_primary))
- var/datum/material/primary = get_primary_material()
- . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute
- if(!isnull(.))
- return
- return ..()
-
-/obj/hitsound_throwhit(obj/item/I)
- if(!isnull(material_primary))
- var/datum/material/primary = get_primary_material()
- . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute
- if(!isnull(.))
- return
- return ..()
-
-/obj/hitsound_unarmed(mob/M, datum/unarmed_attack/style)
- if(!isnull(material_primary))
- var/datum/material/primary = get_primary_material()
- . = style.damage_type == BURN? primary.sound_melee_burn : primary.sound_melee_brute
- if(!isnull(.))
- return
- return ..()
diff --git a/code/game/objects/effects/_effect.dm b/code/game/objects/effects/_effect.dm
index f2f3ff7e6f1b..14ae0396a50e 100644
--- a/code/game/objects/effects/_effect.dm
+++ b/code/game/objects/effects/_effect.dm
@@ -10,6 +10,7 @@
* however, at a certain point, do consider using /structure or /machinery instead.
*/
/obj/effect
+ anchored = TRUE
integrity_enabled = FALSE
density = FALSE
opacity = FALSE
diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm
index bc62c5021dc1..5e949319b074 100644
--- a/code/game/objects/effects/effect_system.dm
+++ b/code/game/objects/effects/effect_system.dm
@@ -406,7 +406,7 @@ steam.start() -- spawns the effect
return
if(!ismovable(holder))
return
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
on = TRUE
/datum/effect_system/ion_trail_follow/process(wait)
@@ -425,7 +425,7 @@ steam.start() -- spawns the effect
return
oldposition = null
on = FALSE
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
/////////////////////////////////////////////
//////// Attach a steam trail to an object (eg. a reacting beaker) that will follow it
diff --git a/code/game/objects/effects/item_pickup_ghost.dm b/code/game/objects/effects/item_pickup_ghost.dm
index 0944e2753eea..58675c8ff70a 100644
--- a/code/game/objects/effects/item_pickup_ghost.dm
+++ b/code/game/objects/effects/item_pickup_ghost.dm
@@ -1,11 +1,17 @@
/obj/effect/temporary_effect/item_pickup_ghost
- anchored = 1
plane = MOB_PLANE
layer = ABOVE_MOB_LAYER
- mouse_opacity = 0//just in case something dumb happens
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
time_to_die = 0.5 SECONDS
var/lifetime = 0.25 SECONDS //so it doesn't die before animation ends
+/obj/effect/temporary_effect/item_pickup_ghost/Initialize(mapload, obj/item/from_item, atom/towards_target)
+ . = ..()
+ if(from_item)
+ assumeform(from_item)
+ if(towards_target)
+ animate_towards(towards_target)
+
/obj/effect/temporary_effect/item_pickup_ghost/proc/assumeform(var/obj/item/picked_up)
icon = picked_up.icon
icon_state = picked_up.icon_state
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index 88e0f9904874..8338325c57e6 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -18,6 +18,8 @@
wires = new(src)
/obj/effect/mine/proc/explode(var/mob/living/M)
+ if(QDELETED(src))
+ return
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread()
triggered = 1
s.set_up(3, 1, src)
@@ -27,8 +29,9 @@
qdel(s)
qdel(src)
-/obj/effect/mine/bullet_act()
- if(prob(50))
+/obj/effect/mine/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ spawn(0)
explode()
/obj/effect/mine/legacy_ex_act(severity)
@@ -167,7 +170,7 @@
var/turf/O = get_turf(src)
if(!O)
return
- src.fragmentate(O, 20, 7, list(/obj/projectile/bullet/pellet/fragment)) //only 20 weak fragments because you're stepping directly on it
+ shrapnel_explosion(20, 7, /obj/projectile/bullet/pellet/fragment)
visible_message("\The [src.name] detonates!")
spawn(0)
qdel(s)
diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm
index 1ce7e665ac46..d03e8a0d3c43 100644
--- a/code/game/objects/effects/misc.dm
+++ b/code/game/objects/effects/misc.dm
@@ -116,25 +116,5 @@
icon_state = "begin"
anchored = TRUE
-/obj/effect/list_container
- name = "list container"
-
-/obj/effect/list_container/mobl
- name = "mobl"
- var/master = null
-
- var/list/container = list( )
-
-/obj/effect/stop
- icon_state = "empty"
- name = "Geas"
- desc = "You can't resist."
- var/atom/movable/victim
-
-/obj/effect/stop/Uncross(atom/movable/AM)
- . = ..()
- if(AM == victim)
- return FALSE
-
/obj/effect/spawner
name = "object spawner"
diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm
index 557cd497a544..a0b957a07837 100644
--- a/code/game/objects/effects/spiders.dm
+++ b/code/game/objects/effects/spiders.dm
@@ -15,10 +15,10 @@
if(exposed_temperature > 300 + T0C)
damage_integrity(5)
-/obj/effect/spider/melee_act(mob/user, obj/item/weapon, target_zone, mult)
- if(weapon.damtype == BURN)
- mult *= 2
- return ..()
+/obj/effect/spider/process_damage_instance(list/shieldcall_args, filter_zone)
+ . = ..()
+ if(shieldcall_args[SHIELDCALL_ARG_DAMAGE_TYPE])
+ shieldcall_args[SHIELDCALL_ARG_DAMAGE] *= 2
/obj/effect/spider/stickyweb
icon_state = "stickyweb1"
diff --git a/code/game/objects/effects/traps.dm b/code/game/objects/effects/traps.dm
index 01a3f46511bd..42855a8026c5 100644
--- a/code/game/objects/effects/traps.dm
+++ b/code/game/objects/effects/traps.dm
@@ -554,7 +554,7 @@ Add those other swinging traps you mentioned above!
/* This is all per-tick processing stuff. It isn't working the way I want, so I'm reverting it.
if (istype(AM, /mob/living))
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
var/mob/living/M = AM
M.visible_message("[M] is slashed by the spinning blades!", \
"You are slashed by the spinning blades!")
@@ -565,7 +565,7 @@ if (istype(AM, /mob/living))
M.apply_damage(damage, BRUTE)
/obj/effect/trap/pop_up/pillar/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
return ..()
*/
diff --git a/code/game/objects/items-carry_weight.dm b/code/game/objects/items-carry_weight.dm
new file mode 100644
index 000000000000..f26b7114f7f3
--- /dev/null
+++ b/code/game/objects/items-carry_weight.dm
@@ -0,0 +1,74 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Carry Weight *//
+//* The carry weight system is a modular system used to discourage *//
+//* carrying too much, through both weight (recursive weight) and *//
+//* encumbrance (only on the stuff worn, and potentially also in hand) *//
+
+/obj/item/proc/get_weight()
+ return weight + obj_storage?.get_containing_weight()
+
+/obj/item/proc/get_encumbrance()
+ return encumbrance
+
+/obj/item/proc/get_flat_encumbrance()
+ return flat_encumbrance
+
+/obj/item/proc/update_weight()
+ if(isnull(weight_registered))
+ return null
+ . = get_weight()
+ if(. == weight_registered)
+ return 0
+ . -= weight_registered
+ weight_registered += .
+ var/mob/living/wearer = worn_mob()
+ if(istype(wearer))
+ wearer.adjust_current_carry_weight(.)
+
+/obj/item/proc/update_encumbrance()
+ if(isnull(encumbrance_registered))
+ return null
+ . = get_encumbrance()
+ if(. == encumbrance_registered)
+ return 0
+ . -= encumbrance_registered
+ encumbrance_registered += .
+ var/mob/living/wearer = worn_mob()
+ if(istype(wearer))
+ wearer.adjust_current_carry_encumbrance(.)
+
+/obj/item/proc/update_flat_encumbrance()
+ var/mob/living/wearer = worn_mob()
+ if(istype(wearer))
+ wearer.recalculate_carry()
+
+/obj/item/proc/set_weight(amount)
+ if(amount == weight)
+ return
+ var/old = weight
+ weight = amount
+ update_weight()
+ propagate_weight(old, weight)
+
+/obj/item/proc/set_encumbrance(amount)
+ if(amount == encumbrance)
+ return
+ encumbrance = amount
+ update_encumbrance()
+
+/obj/item/proc/set_flat_encumbrance(amount)
+ if(amount == flat_encumbrance)
+ return
+ flat_encumbrance = amount
+ update_flat_encumbrance()
+
+/obj/item/proc/set_slowdown(amount)
+ if(amount == slowdown)
+ return
+ slowdown = amount
+ 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-defense.dm b/code/game/objects/items-defense.dm
new file mode 100644
index 000000000000..4ad604fcbcd6
--- /dev/null
+++ b/code/game/objects/items-defense.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Armor *//
+
+/**
+ * Called during a mob armor call cycle
+ */
+/obj/item/proc/mob_armorcall(mob/defending, list/shieldcall_args, fake_attack)
+ // use our own armor
+ var/datum/armor/our_armor = fetch_armor()
+ our_armor.handle_shieldcall(shieldcall_args, fake_attack)
diff --git a/code/game/objects/items-interaction.dm b/code/game/objects/items-interaction.dm
index 8e557d574c81..a1b37b7c17c2 100644
--- a/code/game/objects/items-interaction.dm
+++ b/code/game/objects/items-interaction.dm
@@ -1,6 +1,165 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 silicons *//
+//* Attack Hand *//
+
+/obj/item/on_attack_hand(datum/event_args/actor/clickchain/e_args)
+ . = ..()
+ if(.)
+ return
+
+ if(e_args.performer.is_in_inventory(src))
+ if(e_args.performer.is_holding(src))
+ if(obj_storage?.allow_open_via_offhand_click && obj_storage.auto_handle_interacted_open(e_args))
+ return TRUE
+ else
+ if(obj_storage?.allow_open_via_equipped_click && obj_storage.auto_handle_interacted_open(e_args))
+ return TRUE
+ if(!e_args.performer.is_holding(src))
+ if(should_attempt_pickup(e_args) && attempt_pickup(e_args.performer))
+ return TRUE
+
+/obj/item/proc/should_attempt_pickup(datum/event_args/actor/actor)
+ return TRUE
+
+/**
+ * @params
+ * * actor - (optional) person doing it
+ * * silent - suppress feedback
+ */
+/obj/item/proc/should_allow_pickup(datum/event_args/actor/actor, silent)
+ if(anchored)
+ if(!silent)
+ actor?.chat_feedback(
+ SPAN_NOTICE("\The [src] won't budge, you can't pick it up!"),
+ target = src,
+ )
+ return FALSE
+ return TRUE
+
+/obj/item/proc/attempt_pickup(mob/user)
+ . = TRUE
+ if (!user)
+ return
+
+ if(!should_allow_pickup(new /datum/event_args/actor(user)))
+ return FALSE
+
+ if(!CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP))
+ user.action_feedback(SPAN_WARNING("You can't do that right now."), src)
+ return
+
+ // todo: rewrite this part iin hand rewrite
+ if (hasorgans(user))
+ var/mob/living/carbon/human/H = user
+ var/obj/item/organ/external/temp = H.organs_by_name[H.hand? "l_hand" : "r_hand"]
+ if(!temp)
+ to_chat(user, "You try to use your hand, but realize it is no longer attached!")
+ return
+ if(!temp.is_usable())
+ to_chat(user, "You try to move your [temp.name], but cannot!")
+ return
+
+ var/old_loc = src.loc
+ var/obj/item/actually_picked_up = src
+ var/has_to_drop_to_ground_on_fail = FALSE
+
+ if(isturf(old_loc))
+ // if picking up from floor
+ throwing?.terminate()
+ else if(item_flags & ITEM_IN_STORAGE)
+ // trying to take out of backpack
+ var/datum/object_system/storage/resolved
+ if(istype(loc, /atom/movable/storage_indirection))
+ var/atom/movable/storage_indirection/holder = loc
+ resolved = holder.parent
+ else if(isobj(loc))
+ var/obj/obj_loc = loc
+ resolved = obj_loc.obj_storage
+ if(isnull(resolved))
+ item_flags &= ~ITEM_IN_STORAGE
+ CRASH("in storage at [loc] ([REF(loc)]) ([loc?.type || "NULL"]) but cannot resolve storage system")
+ actually_picked_up = resolved.try_remove(src, user, new /datum/event_args/actor(user))
+ // they're in user, but not equipped now. this is so it doesn't touch the ground first.
+ has_to_drop_to_ground_on_fail = TRUE
+
+ if(isnull(actually_picked_up))
+ to_chat(user, SPAN_WARNING("[src] somehow slips through your grasp. What just happened?"))
+ return
+ if(!user.put_in_hands(actually_picked_up))
+ if(has_to_drop_to_ground_on_fail)
+ actually_picked_up.forceMove(user.drop_location())
+ return
+ // success
+ if(isturf(old_loc))
+ new /obj/effect/temporary_effect/item_pickup_ghost(old_loc, actually_picked_up, user)
+
+//* Drag / Drop *//
+
+/obj/item/OnMouseDrop(atom/over, mob/user, proximity, params)
+ . = ..()
+ if(. & CLICKCHAIN_DO_NOT_PROPAGATE)
+ return
+ if(anchored) // Don't.
+ return
+ if(user.restrained())
+ return // don't.
+ // todo: restraint levels, e.g. handcuffs vs straightjacket
+ if(!user.is_in_inventory(src))
+ // not being held
+ if(!isturf(loc)) // yea nah
+ return ..()
+ if(user.Adjacent(src))
+ // check for equip
+ if(istype(over, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over
+ user.put_in_hand(src, H.index)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ else if(istype(over, /atom/movable/screen/inventory/slot))
+ var/atom/movable/screen/inventory/slot/S = over
+ user.equip_to_slot_if_possible(src, S.slot_id)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ // check for slide
+ if(Adjacent(over) && user.CanSlideItem(src, over) && (istype(over, /obj/structure/table/rack) || istype(over, /obj/structure/table) || istype(over, /turf)))
+ var/turf/old = get_turf(src)
+ if(over == old) // same tile don't bother
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(!Move(get_turf(over)))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ //! todo: i want to strangle the mofo who did planes instead of invisibility, which makes it computationally infeasible to check ghost invisibility in get hearers in view
+ //! :) FUCK YOU.
+ //! this if check is all for you. FUCK YOU.
+ if(!isobserver(user))
+ user.visible_message(SPAN_NOTICE("[user] slides [src] over."), SPAN_NOTICE("You slide [src] over."), range = MESSAGE_RANGE_COMBAT_SUBTLE)
+ log_inventory("[user] slid [src] from [COORD(old)] to [COORD(over)]")
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ else
+ // being held, check for attempt unequip
+ if(istype(over, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over
+ user.put_in_hand(src, H.index)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ else if(istype(over, /atom/movable/screen/inventory/slot))
+ var/atom/movable/screen/inventory/slot/S = over
+ user.equip_to_slot_if_possible(src, S.slot_id)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ else if(istype(over, /turf))
+ user.drop_item_to_ground(src)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+
+// funny!
+// todo: move to mob files
+/mob/proc/CanSlideItem(obj/item/I, turf/over)
+ return FALSE
+
+// todo: move to mob files
+/mob/living/CanSlideItem(obj/item/I, turf/over)
+ return Adjacent(I) && !incapacitated() && !stat && !restrained()
+
+// todo: move to mob files
+/mob/observer/dead/CanSlideItem(obj/item/I, turf/over)
+ return is_spooky
+
//* Inhand Triggers *//
/**
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index bd331dd7d3a3..e61d13a64890 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -27,7 +27,18 @@
/// the action will check for
var/item_action_mobility_flags = MOBILITY_CAN_HOLD | MOBILITY_CAN_USE
- //? Flags
+ //* Combat *//
+ /// passive parry data / frame
+ ///
+ /// * anonymous typepath is allowed
+ /// * typepath is allowed
+ /// * instance is allowed
+ ///
+ /// note that the component will not be modified while held;
+ /// if this is changed, the component needs to be remade.
+ var/passive_parry
+
+ //* Flags *//
/// Item flags.
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
var/item_flags = ITEM_ENCUMBERS_WHILE_HELD
@@ -423,14 +434,6 @@
/obj/item/ui_action_click(datum/action/action, datum/event_args/actor/actor)
attack_self(usr)
-//RETURN VALUES
-//handle_shield should return a positive value to indicate that the attack is blocked and should be prevented.
-//If a negative value is returned, it should be treated as a special return value for bullet_act() and handled appropriately.
-//For non-projectile attacks this usually means the attack is blocked.
-//Otherwise should return 0 to indicate that the attack is not affected in any way.
-/obj/item/proc/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- return 0
-
/obj/item/proc/get_loc_turf()
var/atom/L = loc
while(L && !istype(L, /turf/))
@@ -526,7 +529,7 @@
if (!..())
return 0
- if(istype(src, /obj/item/melee/energy))
+ if(istype(src, /obj/item/melee/transforming/energy))
return
//if we haven't made our blood_overlay already
@@ -759,27 +762,6 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
var/datum/action/action = item_actions
action.revoke(user.inventory.actions)
-//* Armor *//
-
-/**
- * called to be checked for mob armor
- *
- * @returns copy of args with modified values
- */
-/obj/item/proc/checking_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- damage = fetch_armor().resultant_damage(damage, tier, flag)
- return args.Copy()
-
-/**
- * called to be used as mob armor
- * side effects are allowed
- *
- * @returns copy of args with modified values
- */
-/obj/item/proc/running_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- damage = fetch_armor().resultant_damage(damage, tier, flag)
- return args.Copy()
-
//* Attack *//
/**
@@ -830,7 +812,20 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
/obj/item/proc/is_shred(strict)
return (damage_mode & DAMAGE_MODE_SHRED)
-//* Interaction *//
+//* Combat *//
+
+/obj/item/proc/load_passive_parry()
+ if(!passive_parry)
+ return
+ passive_parry = resolve_passive_parry_data(passive_parry)
+ var/datum/component/passive_parry/loaded = GetComponent(/datum/component/passive_parry)
+ if(loaded)
+ loaded.parry_data = passive_parry
+
+/obj/item/proc/reload_passive_parry()
+ load_passive_parry()
+
+//* Interactions *//
/obj/item/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier)
if(isturf(loc) && I.obj_storage?.allow_mass_gather && I.obj_storage.allow_mass_gather_via_click)
@@ -859,237 +854,8 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
/obj/item/proc/attacksound_override(atom/target, attack_type)
return
-//* Carry Weight *//
-
-/obj/item/proc/get_weight()
- return weight + obj_storage?.get_containing_weight()
-
-/obj/item/proc/get_encumbrance()
- return encumbrance
-
-/obj/item/proc/get_flat_encumbrance()
- return flat_encumbrance
-
-/obj/item/proc/update_weight()
- if(isnull(weight_registered))
- return null
- . = get_weight()
- if(. == weight_registered)
- return 0
- . -= weight_registered
- weight_registered += .
- var/mob/living/wearer = worn_mob()
- if(istype(wearer))
- wearer.adjust_current_carry_weight(.)
-
-/obj/item/proc/update_encumbrance()
- if(isnull(encumbrance_registered))
- return null
- . = get_encumbrance()
- if(. == encumbrance_registered)
- return 0
- . -= encumbrance_registered
- encumbrance_registered += .
- var/mob/living/wearer = worn_mob()
- if(istype(wearer))
- wearer.adjust_current_carry_encumbrance(.)
-
-/obj/item/proc/update_flat_encumbrance()
- var/mob/living/wearer = worn_mob()
- if(istype(wearer))
- wearer.recalculate_carry()
-
-/obj/item/proc/set_weight(amount)
- if(amount == weight)
- return
- var/old = weight
- weight = amount
- update_weight()
- propagate_weight(old, weight)
-
-/obj/item/proc/set_encumbrance(amount)
- if(amount == encumbrance)
- return
- encumbrance = amount
- update_encumbrance()
-
-/obj/item/proc/set_flat_encumbrance(amount)
- if(amount == flat_encumbrance)
- return
- flat_encumbrance = amount
- update_flat_encumbrance()
-
-/obj/item/proc/set_slowdown(amount)
- if(amount == slowdown)
- return
- slowdown = amount
- worn_mob()?.update_item_slowdown()
-
-/obj/item/proc/propagate_weight(old_weight, new_weight)
- loc?.on_contents_weight_change(src, old_weight, new_weight)
-
-//* Interactions *//
-
-/obj/item/on_attack_hand(datum/event_args/actor/clickchain/e_args)
- . = ..()
- if(.)
- return
-
- if(e_args.performer.is_in_inventory(src))
- if(e_args.performer.is_holding(src))
- if(obj_storage?.allow_open_via_offhand_click && obj_storage.auto_handle_interacted_open(e_args))
- return TRUE
- else
- if(obj_storage?.allow_open_via_equipped_click && obj_storage.auto_handle_interacted_open(e_args))
- return TRUE
- if(!e_args.performer.is_holding(src))
- if(should_attempt_pickup(e_args) && attempt_pickup(e_args.performer))
- return TRUE
-
-/obj/item/OnMouseDrop(atom/over, mob/user, proximity, params)
- . = ..()
- if(. & CLICKCHAIN_DO_NOT_PROPAGATE)
- return
- if(anchored) // Don't.
- return
- if(user.restrained())
- return // don't.
- // todo: restraint levels, e.g. handcuffs vs straightjacket
- if(!user.is_in_inventory(src))
- // not being held
- if(!isturf(loc)) // yea nah
- return ..()
- if(user.Adjacent(src))
- // check for equip
- if(istype(over, /atom/movable/screen/inventory/hand))
- var/atom/movable/screen/inventory/hand/H = over
- user.put_in_hand(src, H.index)
- return CLICKCHAIN_DO_NOT_PROPAGATE
- else if(istype(over, /atom/movable/screen/inventory/slot))
- var/atom/movable/screen/inventory/slot/S = over
- user.equip_to_slot_if_possible(src, S.slot_id)
- return CLICKCHAIN_DO_NOT_PROPAGATE
- // check for slide
- if(Adjacent(over) && user.CanSlideItem(src, over) && (istype(over, /obj/structure/table/rack) || istype(over, /obj/structure/table) || istype(over, /turf)))
- var/turf/old = get_turf(src)
- if(over == old) // same tile don't bother
- return CLICKCHAIN_DO_NOT_PROPAGATE
- if(!Move(get_turf(over)))
- return CLICKCHAIN_DO_NOT_PROPAGATE
- //! todo: i want to strangle the mofo who did planes instead of invisibility, which makes it computationally infeasible to check ghost invisibility in get hearers in view
- //! :) FUCK YOU.
- //! this if check is all for you. FUCK YOU.
- if(!isobserver(user))
- user.visible_message(SPAN_NOTICE("[user] slides [src] over."), SPAN_NOTICE("You slide [src] over."), range = MESSAGE_RANGE_COMBAT_SUBTLE)
- log_inventory("[user] slid [src] from [COORD(old)] to [COORD(over)]")
- return CLICKCHAIN_DO_NOT_PROPAGATE
- else
- // being held, check for attempt unequip
- if(istype(over, /atom/movable/screen/inventory/hand))
- var/atom/movable/screen/inventory/hand/H = over
- user.put_in_hand(src, H.index)
- return CLICKCHAIN_DO_NOT_PROPAGATE
- else if(istype(over, /atom/movable/screen/inventory/slot))
- var/atom/movable/screen/inventory/slot/S = over
- user.equip_to_slot_if_possible(src, S.slot_id)
- return CLICKCHAIN_DO_NOT_PROPAGATE
- else if(istype(over, /turf))
- user.drop_item_to_ground(src)
- return CLICKCHAIN_DO_NOT_PROPAGATE
-
-// funny!
-// todo: move to mob files
-/mob/proc/CanSlideItem(obj/item/I, turf/over)
- return FALSE
-
-// todo: move to mob files
-/mob/living/CanSlideItem(obj/item/I, turf/over)
- return Adjacent(I) && !incapacitated() && !stat && !restrained()
-
-// todo: move to mob files
-/mob/observer/dead/CanSlideItem(obj/item/I, turf/over)
- return is_spooky
-
//* Inventory *//
-/obj/item/proc/should_attempt_pickup(datum/event_args/actor/actor)
- return TRUE
-
-/**
- * @params
- * * actor - (optional) person doing it
- * * silent - suppress feedback
- */
-/obj/item/proc/should_allow_pickup(datum/event_args/actor/actor, silent)
- if(anchored)
- if(!silent)
- actor?.chat_feedback(
- SPAN_NOTICE("\The [src] won't budge, you can't pick it up!"),
- target = src,
- )
- return FALSE
- return TRUE
-
-/obj/item/proc/attempt_pickup(mob/user)
- . = TRUE
- if (!user)
- return
-
- if(!should_allow_pickup(new /datum/event_args/actor(user)))
- return FALSE
-
- if(!CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP))
- user.action_feedback(SPAN_WARNING("You can't do that right now."), src)
- return
-
- if (hasorgans(user))
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user, "You try to move your [temp.name], but cannot!")
- return
- if(!temp)
- to_chat(user, "You try to use your hand, but realize it is no longer attached!")
- return
-
- var/old_loc = src.loc
- var/obj/item/actually_picked_up = src
- var/has_to_drop_to_ground_on_fail = FALSE
-
- if(isturf(old_loc))
- // if picking up from floor
- throwing?.terminate()
- else if(item_flags & ITEM_IN_STORAGE)
- // trying to take out of backpack
- var/datum/object_system/storage/resolved
- if(istype(loc, /atom/movable/storage_indirection))
- var/atom/movable/storage_indirection/holder = loc
- resolved = holder.parent
- else if(isobj(loc))
- var/obj/obj_loc = loc
- resolved = obj_loc.obj_storage
- if(isnull(resolved))
- item_flags &= ~ITEM_IN_STORAGE
- CRASH("in storage at [loc] ([REF(loc)]) ([loc?.type || "NULL"]) but cannot resolve storage system")
- actually_picked_up = resolved.try_remove(src, user, new /datum/event_args/actor(user))
- // they're in user, but not equipped now. this is so it doesn't touch the ground first.
- has_to_drop_to_ground_on_fail = TRUE
-
- if(isnull(actually_picked_up))
- to_chat(user, SPAN_WARNING("[src] somehow slips through your grasp. What just happened?"))
- return
- if(!user.put_in_hands(actually_picked_up))
- if(has_to_drop_to_ground_on_fail)
- actually_picked_up.forceMove(user.drop_location())
- return
- // success
- if(isturf(old_loc))
- var/obj/effect/temporary_effect/item_pickup_ghost/ghost = new(old_loc)
- ghost.assumeform(actually_picked_up)
- ghost.animate_towards(user)
-
/**
* Called when someone clisk us on a storage, before the storage handler's
* 'put item in' runs. Return FALSE to deny.
@@ -1172,8 +938,18 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
//* VV *//
+/obj/item/vv_get_var(var_name, resolve)
+ switch(var_name)
+ if(NAMEOF(src, passive_parry))
+ if(resolve)
+ load_passive_parry()
+ return ..()
+
/obj/item/vv_edit_var(var_name, var_value, mass_edit, raw_edit)
switch(var_name)
+ if(NAMEOF(src, passive_parry))
+ . = ..()
+ reload_passive_parry()
if(NAMEOF(src, item_flags))
var/requires_update = (item_flags & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD)) != (var_value & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD))
. = ..()
@@ -1193,7 +969,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
L.update_carry_slowdown()
if(NAMEOF(src, slowdown))
. = ..()
- if(. )
+ if(.)
var/mob/living/L = worn_mob()
// check, as worn_mob() returns /mob, not /living
if(istype(L))
diff --git a/code/game/objects/items/contraband.dm b/code/game/objects/items/contraband.dm
index a54ee11a41e7..e8e86f7fc15e 100644
--- a/code/game/objects/items/contraband.dm
+++ b/code/game/objects/items/contraband.dm
@@ -109,12 +109,12 @@
/obj/item/grenade/flashbang/clusterbang,
/obj/item/grenade/flashbang/clusterbang,
/obj/item/grenade/spawnergrenade/spesscarp,
- /obj/item/melee/energy/sword/ionic_rapier,
+ /obj/item/melee/transforming/energy/sword/ionic_rapier,
/obj/item/clothing/shoes/syndigaloshes,
/obj/item/storage/backpack/dufflebag/syndie,
/obj/item/binoculars,
/obj/item/storage/firstaid/combat,
- /obj/item/melee/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
/obj/item/melee/telebaton,
/obj/item/pen/reagent/paralysis,
/obj/item/pickaxe/diamonddrill,
@@ -124,7 +124,7 @@
/obj/item/reagent_containers/food/snacks/xenomeat,
/obj/item/reagent_containers/glass/beaker/neurotoxin,
/obj/item/hardsuit/combat,
- /obj/item/shield/energy,
+ /obj/item/shield/transforming/energy,
/obj/item/stamp/centcom,
/obj/item/stamp/oricon,
/obj/item/storage/fancy/cigar/havana,
@@ -217,13 +217,13 @@
/obj/item/grenade/flashbang/clusterbang,
/obj/item/grenade/flashbang/clusterbang,
/obj/item/grenade/spawnergrenade/spesscarp,
- /obj/item/melee/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
/obj/item/melee/telebaton,
/obj/item/pen/reagent/paralysis,
/obj/item/pickaxe/diamonddrill,
/obj/item/reagent_containers/glass/beaker/neurotoxin,
/obj/item/hardsuit/combat,
- /obj/item/shield/energy,
+ /obj/item/shield/transforming/energy,
/obj/item/stamp/centcom,
/obj/item/stamp/oricon,
/obj/item/storage/fancy/cigar/havana,
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index 40a96b4cd4f0..366b6f29a8ae 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -120,10 +120,10 @@
to_chat(M, "Your chameleon-projector deactivates.")
master.disrupt()
-/obj/effect/dummy/chameleon/bullet_act()
+/obj/effect/dummy/chameleon/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
for(var/mob/M in src)
to_chat(M, "Your chameleon-projector deactivates.")
- ..()
master.disrupt()
/obj/effect/dummy/chameleon/relaymove(var/mob/user, direction)
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index a2712ccf6d4f..009374244b8b 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -212,7 +212,7 @@
hud_bound?.add_screen(hud_arrow)
hud_arrow.set_disabled(FALSE)
update_tracking()
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
return TRUE
/**
@@ -225,7 +225,7 @@
tracking = null
// just kick it out
hud_arrow?.set_disabled(TRUE)
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
return TRUE
/obj/item/gps/process(delta_time)
diff --git a/code/game/objects/items/devices/spy_bug.dm b/code/game/objects/items/devices/spy_bug.dm
index 8165600c4cb9..fd10b1265039 100644
--- a/code/game/objects/items/devices/spy_bug.dm
+++ b/code/game/objects/items/devices/spy_bug.dm
@@ -109,14 +109,15 @@
qdel(src)
..()
-/obj/item/camerabug/bullet_act()
+/obj/item/camerabug/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
visible_message("The [src] lens shatters!")
new brokentype(get_turf(src))
if(linkedmonitor)
linkedmonitor.unpair(src)
linkedmonitor = null
- spawn(0)
- qdel(src)
/obj/item/camerabug/Destroy()
if(linkedmonitor)
diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm
index 71c444de8c53..07aa8e7721a6 100644
--- a/code/game/objects/items/devices/text_to_speech.dm
+++ b/code/game/objects/items/devices/text_to_speech.dm
@@ -51,8 +51,20 @@
if(activated)
var/message = message_args["message"]
var/whispering = message_args["whispering"]
+ var/message_mode = message_args["message_mode"]
+ var/speech_verb = whispering ? "quietly states" : "states"
message_args["cancelled"] = TRUE
- audible_message("[icon2html(thing = src, target = world)] \The [name] [whispering ? "quietly states" : "states"], \"[message]\"", null, whispering ? 2 : world.view)
+ audible_message("[icon2html(thing = src, target = world)] \The [name] [speech_verb], \"[message]\"", null, whispering ? 2 : world.view)
if(!whispering)
linked_user.say_overhead(message, FALSE, MESSAGE_RANGE_COMBAT_LOUD)
+
+ if(ishuman(source) && message_mode != null)
+ var/mob/living/carbon/human/H = source
+ var/obj/item/radio/headset/left_radio = H.l_ear
+ var/obj/item/radio/headset/right_radio = H.r_ear
+ if(istype(left_radio))
+ left_radio.talk_into(source, message, message_mode, speech_verb, null)
+ if(istype(right_radio))
+ right_radio.talk_into(source, message, message_mode, speech_verb, null)
+
playsound(src, 'sound/items/tts/stopped_type.ogg', 25, TRUE)
diff --git a/code/game/objects/items/id_cards/station_ids.dm b/code/game/objects/items/id_cards/station_ids.dm
index f14e2d1b912f..ccbca7e089f1 100644
--- a/code/game/objects/items/id_cards/station_ids.dm
+++ b/code/game/objects/items/id_cards/station_ids.dm
@@ -71,7 +71,6 @@
. += ""
if(Adjacent(user))
. += SPAN_NOTICE("Alt-click to [extra_info_visible ? "close" : "open"] the confidential information flap.")
- return .
/obj/item/card/id/get_description_info()
. = ..()
diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm
index 8c5ecb1731b0..5b469f669e58 100644
--- a/code/game/objects/items/latexballoon.dm
+++ b/code/game/objects/items/latexballoon.dm
@@ -30,16 +30,8 @@
item_state = "lgloves"
loc.assume_air(air_contents)
-/obj/item/latexballon/legacy_ex_act(severity)
- burst()
- switch(severity)
- if (1)
- qdel(src)
- if (2)
- if (prob(50))
- qdel(src)
-
-/obj/item/latexballon/bullet_act()
+/obj/item/latexballon/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon)
+ . = ..()
burst()
/obj/item/latexballon/fire_act(datum/gas_mixture/air, temperature, volume)
diff --git a/code/game/objects/items/melee/melee.dm b/code/game/objects/items/melee/melee.dm
new file mode 100644
index 000000000000..ec0b78656760
--- /dev/null
+++ b/code/game/objects/items/melee/melee.dm
@@ -0,0 +1,22 @@
+/datum/passive_parry/melee
+ parry_arc = 155
+ parry_arc_round_down = TRUE
+
+/**
+ * this is like /device and /weapon but a little less dumb
+ *
+ * this has some simple wrappers for default defense stuff, so 'common'ly melee weapons
+ * like knives, armblades, etc, can easily use them.
+ *
+ * * Certain things may or may not 'graduate' to /obj/item level later.
+ */
+/obj/item/melee
+ icon = 'icons/obj/weapons.dmi'
+ attack_sound = "swing_hit"
+ item_icons = list(
+ SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
+ SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
+ )
+ passive_parry = /datum/passive_parry{
+ parry_chance_melee = 5;
+ }
diff --git a/code/game/objects/items/weapons/melee/misc.dm b/code/game/objects/items/melee/types/misc.dm
similarity index 94%
rename from code/game/objects/items/weapons/melee/misc.dm
rename to code/game/objects/items/melee/types/misc.dm
index 41774bbc0db3..ec2cf5250f4c 100644
--- a/code/game/objects/items/weapons/melee/misc.dm
+++ b/code/game/objects/items/melee/types/misc.dm
@@ -68,13 +68,10 @@
can_speak = 1
var/list/voice_mobs = list() //The curse of the sword is that it has someone trapped inside.
-
-/obj/item/melee/cursedblade/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(default_parry_check(user, attacker, damage_source) && prob(50))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_projectile = 15;
+ parry_chance_default = 50;
+ }
/obj/item/melee/cursedblade/proc/ghost_inhabit(var/mob/candidate)
if(!isobserver(candidate))
@@ -329,19 +326,14 @@
var/wieldsound = null
var/unwieldsound = null
-//Allow a small chance of parrying melee attacks when wielded - maybe generalize this to other weapons someday
-/obj/item/melee/twohanded/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(wielded && default_parry_check(user, attacker, damage_source) && prob(15))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_melee = 15;
+ }
/obj/item/melee/twohanded/attack_self(mob/user)
. = ..()
if(.)
return
- . = ..()
if(!wielded)
wielded = 1
else if(wielded)
diff --git a/code/game/objects/items/melee/types/ninja_energy_blade.dm b/code/game/objects/items/melee/types/ninja_energy_blade.dm
new file mode 100644
index 000000000000..c7d110bc483e
--- /dev/null
+++ b/code/game/objects/items/melee/types/ninja_energy_blade.dm
@@ -0,0 +1,66 @@
+/obj/item/melee/ninja_energy_blade
+ name = "energy blade"
+ desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal."
+ icon_state = "ninja_energy_blade"
+ item_state = "ninja_energy_blade"
+ damage_force = 40 //Normal attacks deal very high damage - about the same as wielded fire axe
+ armor_penetration = 100
+ sharp = 1
+ edge = 1
+ anchored = 1 // Never spawned outside of inventory, should be fine.
+ throw_force = 1 //Throwing or dropping the item deletes it.
+ throw_speed = 1
+ throw_range = 1
+ w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such.
+ atom_flags = NOBLOODY
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+
+ var/mob/living/creator
+ var/datum/effect_system/spark_spread/spark_system
+
+ var/lrange = 2
+ var/lpower = 2
+ var/lcolor = "#00FF00"
+
+ passive_parry = /datum/passive_parry{
+ parry_chance_default = 60;
+ parry_chance_projectile = 60;
+ }
+
+/obj/item/melee/ninja_energy_blade/Initialize(mapload)
+ . = ..()
+ spark_system = new /datum/effect_system/spark_spread()
+ spark_system.set_up(5, 0, src)
+ spark_system.attach(src)
+
+ START_PROCESSING(SSobj, src)
+ set_light(lrange, lpower, lcolor)
+
+/obj/item/melee/ninja_energy_blade/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/melee/ninja_energy_blade/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return
+ qdel(src)
+
+/obj/item/melee/ninja_energy_blade/dropped(mob/user, atom_flags, atom/newLoc)
+ . = ..()
+ qdel(src)
+
+/obj/item/melee/ninja_energy_blade/process(delta_time)
+ if(!creator || loc != creator || !creator.is_holding(src))
+ // Tidy up a bit.
+ if(istype(loc,/mob/living))
+ var/mob/living/carbon/human/host = loc
+ if(istype(host))
+ for(var/obj/item/organ/external/organ in host.organs)
+ for(var/obj/item/O in organ.implants)
+ if(O == src)
+ organ.implants -= src
+ host.pinned -= src
+ host.embedded -= src
+ host._handle_inventory_hud_remove(src)
+ qdel(src)
diff --git a/code/game/objects/items/melee/types/transforming.dm b/code/game/objects/items/melee/types/transforming.dm
new file mode 100644
index 000000000000..fe7f4160f939
--- /dev/null
+++ b/code/game/objects/items/melee/types/transforming.dm
@@ -0,0 +1,177 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/**
+ * single-toggle weapons
+ */
+/obj/item/melee/transforming
+ icon = 'icons/items/melee/transforming.dmi'
+ damage_mode = NONE
+ item_icons = null
+
+ /// are we active?
+ var/active = FALSE
+ /// when active, do we use an overlay instead of an icon state?
+ var/active_via_overlay = FALSE
+
+ /// activation sound; also deactivation if it's not specified
+ var/activation_sound = 'sound/weapons/empty.ogg'
+ var/deactivation_sound
+ /// sound volume
+ var/toggle_sound_volume = 50
+
+ /// do not allow passive parry while off
+ var/no_block_while_off = TRUE
+
+ //* active / inactive damage *//
+
+ var/active_damage_force
+ var/inactive_damage_force
+
+ var/active_damage_mode
+ var/inactive_damage_mode
+
+ var/active_damage_tier
+ var/inactive_damage_tier
+
+ var/active_damage_type
+ var/inactive_damage_type
+
+ //* active / inactive effects *//
+
+ var/list/active_attack_verb
+ var/list/inactive_attack_verb
+
+ //* active / inactive inventory costs *//
+
+ var/active_weight_class
+ var/inactive_weight_class
+
+ var/active_weight_volume
+ var/inactive_weight_volume
+
+ //* active / inactive throwing *//
+
+ var/active_throw_force
+ var/inactive_throw_force
+
+ var/active_throw_resist
+ var/inactive_throw_resist
+
+ var/active_throw_range
+ var/inactive_throw_range
+
+ var/active_throw_speed
+ var/inactive_throw_speed
+
+/obj/item/melee/transforming/Initialize(mapload)
+ . = ..()
+ if(islist(active_attack_verb))
+ active_attack_verb = typelist(NAMEOF(src, active_attack_verb), active_attack_verb)
+ if(islist(inactive_attack_verb))
+ inactive_attack_verb = typelist(NAMEOF(src, inactive_attack_verb), inactive_attack_verb)
+
+/obj/item/melee/transforming/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ if(!active && no_block_while_off)
+ return // cancel
+ return ..()
+
+/obj/item/melee/transforming/update_icon_state()
+ icon_state = "[base_icon_state || initial(icon_state)][active && !active_via_overlay ? "-active" : ""]"
+ return ..()
+
+/obj/item/melee/transforming/update_overlays()
+ . = ..()
+ if(!active || !active_via_overlay)
+ return
+ . += build_active_overlay()
+
+/obj/item/melee/transforming/proc/build_active_overlay()
+ RETURN_TYPE(/image)
+ var/image/creating = image(icon, "[base_icon_state || icon_state]-active")
+ return creating
+
+/obj/item/melee/transforming/proc/build_active_worn_overlay(worn_state)
+ RETURN_TYPE(/image)
+ var/image/creating = image(icon, "[worn_state]-active")
+ return creating
+
+/obj/item/melee/transforming/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
+ . = ..()
+ if(active_via_overlay && active)
+ MA.overlays += build_active_worn_overlay(MA.icon_state)
+
+/obj/item/melee/transforming/on_attack_self(datum/event_args/actor/e_args)
+ . = ..()
+ if(.)
+ return
+ add_fingerprint(e_args.performer)
+ toggle(e_args)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+
+/**
+ * actor can be /datum/event_args/actor or a single mob.
+ */
+/obj/item/melee/transforming/proc/toggle(datum/event_args/actor/actor, silent)
+ set_activation(!active, actor, silent)
+
+/obj/item/melee/transforming/proc/set_activation(state, datum/event_args/actor/actor, silent)
+ active = state
+ if(active)
+ on_activate(actor, silent)
+ else
+ on_deactivate(actor, silent)
+ update_icon()
+ update_worn_icon()
+
+/**
+ * actor can be /datum/event_args/actor or a single mob.
+ */
+/obj/item/melee/transforming/proc/on_activate(datum/event_args/actor/actor, silent)
+ E_ARGS_WRAP_USER_TO_ACTOR(actor)
+
+ damage_force = VALUE_OR_DEFAULT(active_damage_force, initial(damage_force))
+ damage_tier = VALUE_OR_DEFAULT(active_damage_tier, initial(damage_tier))
+ damage_mode = VALUE_OR_DEFAULT(active_damage_mode, initial(damage_mode))
+ damtype = VALUE_OR_DEFAULT(active_damage_type, initial(damtype))
+
+ throw_force = VALUE_OR_DEFAULT(active_throw_force, initial(throw_force))
+ throw_resist = VALUE_OR_DEFAULT(active_throw_resist, initial(throw_resist))
+ throw_range = VALUE_OR_DEFAULT(active_throw_range, initial(throw_range))
+ throw_speed = VALUE_OR_DEFAULT(active_throw_speed, initial(throw_speed))
+
+ set_weight_class(VALUE_OR_DEFAULT(active_weight_class, initial(w_class)))
+ set_weight_volume(VALUE_OR_DEFAULT(active_weight_volume, initial(weight_volume)))
+
+ attack_verb = active_attack_verb
+
+ if(!silent && activation_sound)
+ playsound(src, activation_sound, toggle_sound_volume, TRUE)
+
+ // todo: logging
+
+/**
+ * actor can be /datum/event_args/actor or a single mob.
+ */
+/obj/item/melee/transforming/proc/on_deactivate(datum/event_args/actor/actor, silent)
+ E_ARGS_WRAP_USER_TO_ACTOR(actor)
+
+ damage_force = VALUE_OR_DEFAULT(inactive_damage_force, initial(damage_force))
+ damage_tier = VALUE_OR_DEFAULT(inactive_damage_tier, initial(damage_tier))
+ damage_mode = VALUE_OR_DEFAULT(inactive_damage_mode, initial(damage_mode))
+ damtype = VALUE_OR_DEFAULT(inactive_damage_type, initial(damtype))
+
+ throw_force = VALUE_OR_DEFAULT(inactive_throw_force, initial(throw_force))
+ throw_resist = VALUE_OR_DEFAULT(inactive_throw_resist, initial(throw_resist))
+ throw_range = VALUE_OR_DEFAULT(inactive_throw_range, initial(throw_range))
+ throw_speed = VALUE_OR_DEFAULT(inactive_throw_speed, initial(throw_speed))
+
+ set_weight_class(VALUE_OR_DEFAULT(inactive_weight_class, initial(w_class)))
+ set_weight_volume(VALUE_OR_DEFAULT(inactive_weight_volume, initial(weight_volume)))
+
+ attack_verb = inactive_attack_verb
+
+ if(!silent && (activation_sound || deactivation_sound))
+ playsound(src, deactivation_sound || activation_sound, toggle_sound_volume, TRUE)
+
+ // todo: logging
diff --git a/code/game/objects/items/melee/types/transforming/energy.dm b/code/game/objects/items/melee/types/transforming/energy.dm
new file mode 100644
index 000000000000..f011dec3091b
--- /dev/null
+++ b/code/game/objects/items/melee/types/transforming/energy.dm
@@ -0,0 +1,176 @@
+/datum/passive_parry/melee/energy
+ parry_frame = /datum/parry_frame/passive_block/energy
+
+/datum/parry_frame/passive_block/energy
+ parry_sfx = 'sound/weapons/blade1.ogg'
+
+/obj/item/melee/transforming/energy
+ armor_penetration = 50
+ atom_flags = NOCONDUCT | NOBLOODY
+ active_via_overlay = TRUE
+
+ var/lrange = 2
+ var/lpower = 2
+ var/lcolor = "#0099FF"
+ var/colorable = FALSE
+ var/rainbow = FALSE
+ // If it uses energy.
+ var/use_cell = FALSE
+ var/hitcost = 120
+ var/obj/item/cell/bcell = null
+ var/cell_type = /obj/item/cell/device
+
+ passive_parry = /datum/passive_parry/melee/energy
+
+ activation_sound = 'sound/weapons/saberon.ogg'
+ deactivation_sound = 'sound/weapons/saberoff.ogg'
+
+/obj/item/melee/transforming/energy/examine(mob/user, dist)
+ . = ..()
+ if(colorable)
+ . += SPAN_NOTICE("Alt-click to recolor it.")
+
+/obj/item/melee/transforming/energy/on_activate(datum/event_args/actor/actor, silent)
+ . = ..()
+ set_light(lrange, lpower, lcolor)
+
+/obj/item/melee/transforming/energy/on_deactivate(datum/event_args/actor/actor, silent)
+ . = ..()
+ set_light(0)
+
+/obj/item/melee/transforming/energy/proc/use_charge(var/cost)
+ if(active)
+ if(bcell)
+ if(bcell.checked_use(cost))
+ return 1
+ else
+ return 0
+ return null
+
+/obj/item/melee/transforming/energy/examine(mob/user, dist)
+ . = ..()
+ if(use_cell)
+ if(bcell)
+ . += "The blade is [round(bcell.percent())]% charged."
+ if(!bcell)
+ . += "The blade does not have a power source installed."
+
+/obj/item/melee/transforming/energy/toggle(datum/event_args/actor/actor, silent)
+ if(use_cell)
+ if((!bcell || bcell.charge < hitcost) && !active)
+ if(!silent)
+ actor.chat_feedback(
+ "\The [src] does not seem to have power.",
+ target = src,
+ )
+ return FALSE
+ return ..()
+
+/obj/item/melee/transforming/energy/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
+ . = ..()
+ if(active && use_cell)
+ if(!use_charge(hitcost))
+ set_activation(FALSE)
+ visible_message("\The [src]'s blade flickers, before deactivating.")
+
+/obj/item/melee/transforming/energy/attackby(obj/item/W, mob/user)
+ if(istype(W, /obj/item/multitool) && colorable && !active)
+ if(!rainbow)
+ rainbow = TRUE
+ else
+ rainbow = FALSE
+ to_chat(user, "You manipulate the color controller in [src].")
+ update_icon()
+ if(use_cell)
+ if(istype(W, cell_type))
+ if(!bcell)
+ if(!user.attempt_insert_item_for_installation(W, src))
+ return
+ bcell = W
+ to_chat(user, "You install a cell in [src].")
+ update_icon()
+ else
+ to_chat(user, "[src] already has a cell.")
+ else if(W.is_screwdriver() && bcell)
+ bcell.update_icon()
+ bcell.forceMove(get_turf(loc))
+ bcell = null
+ to_chat(user, "You remove the cell from \the [src].")
+ set_activation(FALSE)
+ update_icon()
+ return
+ return ..()
+
+/obj/item/melee/transforming/energy/get_cell(inducer)
+ return bcell
+
+/obj/item/melee/transforming/energy/build_active_overlay()
+ var/image/creating = ..()
+ if(rainbow)
+ creating.icon_state += "-rainbow"
+ else
+ creating.color = lcolor
+ return creating
+
+/obj/item/melee/transforming/energy/build_active_worn_overlay()
+ var/image/creating = ..()
+ if(rainbow)
+ creating.icon_state += "-rainbow"
+ else
+ creating.color = lcolor
+ return creating
+
+/obj/item/melee/transforming/energy/AltClick(mob/living/user)
+ if(!colorable) //checks if is not colorable
+ return
+ if(!in_range(src, user)) //Basic checks to prevent abuse
+ return
+ if(user.incapacitated() || !istype(user))
+ to_chat(user, "You can't do that right now!")
+ return
+
+ if(alert("Are you sure you want to recolor your blade?", "Confirm Recolor", "Yes", "No") == "Yes")
+ var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null
+ if(energy_color_input)
+ lcolor = "#[sanitize_hexcolor(energy_color_input)]"
+ color = lcolor
+ update_icon()
+ return ..()
+
+// todo: no inhand!
+// /obj/item/melee/transforming/energy/spear
+// name = "energy spear"
+// desc = "Concentrated energy forming a sharp tip at the end of a long rod."
+// icon_state = "espear"
+// armor_penetration = 75
+// sharp = 1
+// edge = 1
+// damage_force = 5
+// throw_force = 10
+// throw_speed = 7
+// throw_range = 11
+// reach = 2
+// w_class = WEIGHT_CLASS_BULKY
+// active_damage_force = 25
+// active_throw_force = 30
+// active_weight_class = WEIGHT_CLASS_HUGE
+// colorable = TRUE
+// lcolor = "#800080"
+
+// passive_parry = /datum/passive_parry/melee/energy{
+// parry_chance_default = 50
+// }
+
+// /obj/item/melee/transforming/energy/spear/activate(mob/living/user)
+// if(!active)
+// to_chat(user, "\The [src] is now energised.")
+// ..()
+// attack_verb = list("jabbed", "stabbed", "impaled")
+// AddComponent(/datum/component/jousting)
+
+// /obj/item/melee/transforming/energy/spear/deactivate(mob/living/user)
+// if(active)
+// to_chat(user, "\The [src] deactivates!")
+// ..()
+// attack_verb = list("whacked", "beat", "slapped", "thonked")
+// DelComponent(/datum/component/jousting)
diff --git a/code/game/objects/items/melee/types/transforming/energy/axe.dm b/code/game/objects/items/melee/types/transforming/energy/axe.dm
new file mode 100644
index 000000000000..42d6e7be1c9a
--- /dev/null
+++ b/code/game/objects/items/melee/types/transforming/energy/axe.dm
@@ -0,0 +1,49 @@
+
+/obj/item/melee/transforming/energy/axe
+ name = "energy axe"
+ desc = "An energised battle axe."
+ icon_state = "energy_axe"
+ base_icon_state = "energy_axe"
+ damage_force = 20
+ throw_force = 10
+ throw_speed = 1
+ throw_range = 5
+ w_class = WEIGHT_CLASS_NORMAL
+ origin_tech = list(TECH_MAGNET = 3, TECH_COMBAT = 4)
+ attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut")
+ sharp = 1
+ edge = 1
+ can_cleave = TRUE
+
+ active_damage_force = 60
+ active_throw_force = 35
+ active_weight_class = WEIGHT_CLASS_HUGE
+ active_damage_type = SEARING
+
+/obj/item/melee/transforming/energy/axe/on_activate(datum/event_args/actor/actor, silent)
+ . = ..()
+ actor.chat_feedback(
+ SPAN_WARNING("You energize \the [src]."),
+ target = src,
+ )
+
+/obj/item/melee/transforming/energy/axe/on_deactivate(datum/event_args/actor/actor, silent)
+ . = ..()
+ actor.chat_feedback(
+ SPAN_WARNING("You de-energize \the [src]."),
+ target = src,
+ )
+
+/obj/item/melee/transforming/energy/axe/charge
+ name = "charge axe"
+ desc = "An energised axe."
+ active_damage_force = 30
+ active_throw_force = 20
+ armor_penetration = 25
+ damage_force = 15
+ use_cell = TRUE
+ hitcost = 120
+
+/obj/item/melee/transforming/energy/axe/charge/loaded/Initialize(mapload)
+ . = ..()
+ bcell = new/obj/item/cell/device/weapon(src)
diff --git a/code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm b/code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm
new file mode 100644
index 000000000000..16c15a514bbe
--- /dev/null
+++ b/code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm
@@ -0,0 +1,56 @@
+/obj/item/melee/transforming/energy/sword/ionic_rapier
+ name = "ionic rapier"
+ desc = "Designed specifically for disrupting electronics at close range, it is extremely deadly against synthetics, but almost harmless to pure organic targets."
+ description_info = "This is a dangerous melee weapon that will deliver a moderately powerful electromagnetic pulse to whatever it strikes. \
+ Striking a lesser robotic entity will compel it to attack you, as well. It also does extra burn damage to robotic entities, but it does \
+ very little damage to purely organic targets."
+ icon_state = "ionrapier"
+ item_state = "ionrapier"
+ active_damage_force = 10
+ active_throw_force = 3
+ sharp = 1
+ edge = 1
+ armor_penetration = 0
+ atom_flags = NOBLOODY
+ lrange = 2
+ lpower = 2
+ lcolor = "#0000FF"
+
+ passive_parry = /datum/passive_parry{
+ parry_chance_default = 60;
+ parry_chance_projectile = 30;
+ }
+
+/obj/item/melee/transforming/energy/sword/ionic_rapier/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(istype(target, /obj) && (clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) && active)
+ // EMP stuff.
+ var/obj/O = target
+ O.emp_act(3) // A weaker severity is used because this has infinite uses.
+ playsound(get_turf(O), 'sound/effects/EMPulse.ogg', 100, 1)
+ user.setClickCooldown(user.get_attack_speed(src)) // A lot of objects don't set click delay.
+ return ..()
+
+/obj/item/melee/transforming/energy/sword/ionic_rapier/melee_mob_hit(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
+ . = ..()
+ var/mob/living/L = target
+ if(!istype(L))
+ return
+ if(L.isSynthetic() && active)
+ // Do some extra damage. Not a whole lot more since emp_act() is pretty nasty on FBPs already.
+ L.emp_act(3) // A weaker severity is used because this has infinite uses.
+ playsound(get_turf(L), 'sound/effects/EMPulse.ogg', 100, 1)
+ L.adjustFireLoss(damage_force * 3) // 15 Burn, for 20 total.
+ playsound(get_turf(L), 'sound/weapons/blade1.ogg', 100, 1)
+
+ // Make lesser robots really mad at us.
+ if(L.mob_class & MOB_CLASS_SYNTHETIC)
+ if(L.has_polaris_AI())
+ L.taunt(user)
+ L.adjustFireLoss(damage_force * 6) // 30 Burn, for 50 total.
+
+/obj/item/melee/transforming/energy/sword/ionic_rapier/lance
+ name = "zero-point lance"
+ desc = "Designed specifically for disrupting electronics at relatively close range, however it is still capable of dealing some damage to living beings."
+ active_damage_force = 20
+ armor_penetration = 15
+ reach = 2
diff --git a/code/game/objects/items/melee/types/transforming/energy/saber.dm b/code/game/objects/items/melee/types/transforming/energy/saber.dm
new file mode 100644
index 000000000000..48e1eb491400
--- /dev/null
+++ b/code/game/objects/items/melee/types/transforming/energy/saber.dm
@@ -0,0 +1,141 @@
+/obj/item/melee/transforming/energy/sword
+ name = "energy saber"
+ desc = "May the fourth be within you."
+ icon_state = "saber"
+ base_icon_state = "saber"
+ damage_force = 3
+ throw_force = 5
+ throw_speed = 1
+ throw_range = 5
+ w_class = WEIGHT_CLASS_SMALL
+ atom_flags = NOBLOODY
+ origin_tech = list(TECH_MAGNET = 3, TECH_ILLEGAL = 4)
+ colorable = TRUE
+ drop_sound = 'sound/items/drop/sword.ogg'
+ pickup_sound = 'sound/items/pickup/sword.ogg'
+
+ active_damage_force = 30
+ active_throw_force = 20
+ active_weight_class = WEIGHT_CLASS_BULKY
+ active_damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
+ active_attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+
+ passive_parry = /datum/passive_parry{
+ parry_chance_default = 60;
+ parry_chance_projectile = 65;
+ }
+
+/obj/item/melee/transforming/energy/sword/on_activate(datum/event_args/actor/actor, silent)
+ . = ..()
+ actor.chat_feedback(
+ SPAN_WARNING("You energize \the [src]."),
+ target = src,
+ )
+
+/obj/item/melee/transforming/energy/sword/on_deactivate(datum/event_args/actor/actor, silent)
+ . = ..()
+ actor.chat_feedback(
+ SPAN_WARNING("You de-energize \the [src]."),
+ target = src,
+ )
+
+/obj/item/melee/transforming/energy/sword/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ . = ..()
+ if(!.)
+ return
+
+ var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
+ spark_system.set_up(5, 0, defending.loc)
+ spark_system.start()
+
+/obj/item/melee/transforming/energy/sword/attackby(obj/item/W, mob/living/user, params)
+ if(istype(W, /obj/item/melee/transforming/energy/sword))
+ if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP))
+ to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!")
+ return
+ if(istype(W, /obj/item/melee/transforming/energy/sword/charge))
+ to_chat(user,"These blades are incompatible, you can't attach them to each other!")
+ return
+ else
+ to_chat(user, "You combine the two energy swords, making a single supermassive blade! You're cool.")
+ new /obj/item/melee/transforming/energy/sword/dual(user.drop_location())
+ qdel(W)
+ qdel(src)
+ else
+ return ..()
+
+/obj/item/melee/transforming/energy/sword/cutlass
+ name = "energy cutlass"
+ desc = "Arrrr matey."
+ icon_state = "cutlass"
+ base_icon_state = "cutlass"
+ colorable = TRUE
+
+/obj/item/melee/transforming/energy/sword/dual
+ name = "double-bladed energy sword"
+ desc = "Handle with care."
+ icon_state = "saber-dual"
+ base_icon_state = "saber-dual"
+ damage_force = 3
+ active_damage_force = 60
+ throw_force = 5
+ throw_speed = 3
+ armor_penetration = 35
+ colorable = TRUE
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+
+ passive_parry = /datum/passive_parry{
+ parry_chance_default = 60;
+ parry_chance_projectile = 85;
+ }
+
+/obj/item/melee/transforming/energy/sword/charge
+ name = "charge sword"
+ desc = "A small, handheld device which emits a high-energy 'blade'."
+ origin_tech = list(TECH_COMBAT = 5, TECH_MAGNET = 3, TECH_ILLEGAL = 4)
+ active_damage_force = 25
+ armor_penetration = 25
+ colorable = TRUE
+ use_cell = TRUE
+ hitcost = 75
+
+/obj/item/melee/transforming/energy/sword/charge/loaded/Initialize(mapload)
+ . = ..()
+ bcell = new/obj/item/cell/device/weapon(src)
+
+/obj/item/melee/transforming/energy/sword/charge/attackby(obj/item/W, mob/living/user, params)
+ if(istype(W, /obj/item/melee/transforming/energy/sword/charge))
+ if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP))
+ to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!")
+ return
+ else
+ to_chat(user, "You combine the two charge swords, making a single supermassive blade! You're cool.")
+ new /obj/item/melee/transforming/energy/sword/charge/dualsaber(user.drop_location())
+ qdel(W)
+ qdel(src)
+ else
+ return ..()
+
+/obj/item/melee/transforming/energy/sword/charge/dualsaber
+ name = "double-bladed charge sword"
+ desc = "Make sure you bought batteries."
+ icon_state = "saber-dual"
+ base_icon_state = "saber-dual"
+ damage_force = 3
+ active_damage_force = 50
+ throw_force = 5
+ throw_speed = 3
+ armor_penetration = 30
+ colorable = TRUE
+ hitcost = 150
+
+ passive_parry = /datum/passive_parry{
+ parry_chance_default = 60;
+ parry_chance_projectile = 65;
+ }
+
+/obj/item/melee/transforming/energy/sword/imperial
+ name = "imperial sword"
+ desc = "What the hell is this?"
+ icon_state = "imperial_sword"
+ base_icon_state = "imperial_sword"
diff --git a/code/game/objects/items/melee/types/transforming/hfmachete.dm b/code/game/objects/items/melee/types/transforming/hfmachete.dm
new file mode 100644
index 000000000000..5b5653a84f74
--- /dev/null
+++ b/code/game/objects/items/melee/types/transforming/hfmachete.dm
@@ -0,0 +1,50 @@
+/obj/item/melee/transforming/hfmachete
+ name = "high-frequency machete"
+ desc = "A high-frequency broad blade used either as an implement or in combat like a short sword."
+ icon_state = "hfmachete"
+ base_icon_state = "hfmachete"
+ damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
+ damage_force = 20 // You can be crueler than that, Jack.
+ throw_force = 40
+ throw_speed = 8
+ throw_range = 8
+ w_class = WEIGHT_CLASS_NORMAL
+ siemens_coefficient = 1
+ origin_tech = list(TECH_COMBAT = 3, TECH_ILLEGAL = 3)
+ attack_verb = list("attacked", "diced", "cleaved", "torn", "cut", "slashed")
+ armor_penetration = 50
+ var/base_state = "hfmachete"
+ attack_sound = "machete_hit_sound" // dont mind the meaty hit sounds if you hit something that isnt meaty
+ can_cleave = TRUE
+ embed_chance = 0 // let's not
+
+ active_damage_force = 40
+
+ activation_sound = 'sound/weapons/hf_machete/hfmachete1.ogg'
+ deactivation_sound = 'sound/weapons/hf_machete/hfmachete0.ogg'
+
+ active_weight_class = WEIGHT_CLASS_BULKY
+ inactive_weight_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/melee/transforming/hfmachete/on_activate(datum/event_args/actor/actor, silent)
+ . = ..()
+ actor.chat_feedback(
+ SPAN_WARNING("You energize \the [src]."),
+ target = src,
+ )
+
+/obj/item/melee/transforming/hfmachete/on_deactivate(datum/event_args/actor/actor, silent)
+ . = ..()
+ actor.chat_feedback(
+ SPAN_WARNING("You de-energize \the [src]."),
+ target = src,
+ )
+
+/obj/item/melee/transforming/hfmachete/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
+ return
+ ..()
+ if(target)
+ if(istype(target,/obj/effect/plant))
+ var/obj/effect/plant/P = target
+ P.die_off()
diff --git a/code/game/objects/items/shield/shield.dm b/code/game/objects/items/shield/shield.dm
new file mode 100644
index 000000000000..c55b07df8598
--- /dev/null
+++ b/code/game/objects/items/shield/shield.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/passive_parry/shield
+ parry_arc = 155
+ parry_arc_round_down = TRUE
+ parry_frame = /datum/parry_frame/passive_block/shield
+
+/datum/parry_frame/passive_block/shield
+ parry_sfx = /datum/soundbyte/grouped/block_metal_with_metal
+ block_verb = "blocks" // full block for now
+
+/obj/item/shield
+ name = "shield"
+ passive_parry = /datum/passive_parry/shield{
+ parry_chance_default = 50;
+ }
diff --git a/code/game/objects/items/shield/types/shields_legacy.dm b/code/game/objects/items/shield/types/shields_legacy.dm
new file mode 100644
index 000000000000..1b73e20897f7
--- /dev/null
+++ b/code/game/objects/items/shield/types/shields_legacy.dm
@@ -0,0 +1,204 @@
+/obj/item/shield/riot
+ name = "riot shield"
+ desc = "A shield adept for close quarters engagement. It's also capable of protecting from less powerful projectiles."
+ icon = 'icons/items/shields/basic.dmi'
+ icon_state = "riot"
+ slot_flags = SLOT_BACK
+ damage_force = 5
+ throw_force = 5
+ throw_speed = 1
+ throw_range = 4
+ w_class = WEIGHT_CLASS_BULKY
+ origin_tech = list(TECH_MATERIAL = 2)
+ materials_base = list(MAT_GLASS = 7500, MAT_STEEL = 1000)
+ attack_verb = list("shoved", "bashed")
+ worth_intrinsic = 300
+ var/cooldown = 0 //shield bash cooldown. based on world.time
+
+/obj/item/shield/riot/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ if(istype(weapon, /obj/projectile))
+ var/obj/projectile/proj = weapon
+ if(((is_sharp(proj) && proj.armor_penetration >= 10) || proj.damage_tier >= ARMOR_TIER_HIGH || istype(proj, /obj/projectile/beam)) && prob(50))
+ //If we're at this point, the bullet/beam is going to go through the shield, however it will hit for less damage.
+ //Bullets get slowed down, while beams are diffused as they hit the shield, so these shields are not /completely/
+ //useless. Extremely penetrating projectiles will go through the shield without less damage.
+ defending.visible_message("\The [defending]'s [src.name] is pierced by [proj]!")
+ proj.dampen_on_pierce_experimental(src, 20, ARMOR_TIER_HIGH)
+ playsound(src, /datum/soundbyte/grouped/block_metal_with_metal, 65, TRUE)
+ return null
+ return ..()
+
+/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob)
+ if(istype(W, /obj/item/melee/baton))
+ if(cooldown < world.time - 25)
+ user.visible_message("[user] bashes [src] with [W]!")
+ playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1)
+ cooldown = world.time
+ else
+ ..()
+
+/obj/item/shield/riot/flash
+ name = "strobe shield"
+ desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs."
+ icon_state = "riot-flash"
+ item_state = "riot-flash"
+ var/obj/item/flash/embedded_flash
+ var/flashfail = 0
+
+/obj/item/shield/riot/flash/Initialize(mapload)
+ . = ..()
+ embedded_flash = new(src)
+
+/obj/item/shield/riot/flash/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
+ if(user.a_intent == INTENT_HARM)
+ return ..()
+ return embedded_flash.attack_mob(arglist(args))
+
+/obj/item/shield/riot/flash/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return
+ . = embedded_flash.attack_self(user)
+ update_icon()
+
+/obj/item/shield/riot/flash/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ . = ..()
+ if(!.)
+ return
+ if(embedded_flash.broken)
+ return
+ if(!(attack_type & (ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED)))
+ return
+ // var/datum/event_args/actor/clickchain/clickchain = shieldcall_args[SHIELDCALL_ARG_CLICKCHAIN]
+ // var/mob/attacker = clickchain?.performer
+ // if(attacker)
+ // log_attack(key_name(attacker), key_name(defending), "flash shield auto-invoke")
+ // embedded_flash.melee_interaction_chain(attacker, defending)
+ // else
+ log_attack(key_name(defending), "none (AoE)", "flash shield auto-invoke")
+ embedded_flash.attack_self(defending)
+ update_icon()
+
+/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user)
+ if(istype(W, /obj/item/flash))
+ var/obj/item/flash/flash = W
+ if(flashfail)
+ to_chat(user, "No sense replacing it with a broken bulb!")
+ return
+ else
+ to_chat(user, "You begin to replace the bulb...")
+ if(do_after(user, 20, target = user))
+ if(flashfail || !flash || QDELETED(flash))
+ return
+ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
+ qdel(embedded_flash)
+ embedded_flash = flash
+ flash.forceMove(src)
+ update_icon()
+ return
+ ..()
+
+/obj/item/shield/riot/flash/emp_act(severity)
+ . = ..()
+ embedded_flash.emp_act(severity)
+ update_icon()
+
+/obj/item/shield/riot/flash/update_icon_state()
+ . = ..()
+ if(!embedded_flash || embedded_flash.broken)
+ icon_state = "riot"
+ item_state = "riot"
+ else
+ icon_state = "flashshield"
+ item_state = "flashshield"
+
+/obj/item/shield/riot/flash/examine(mob/user, dist)
+ . = ..()
+ if (embedded_flash?.broken)
+ . += "The mounted bulb has burnt out. You can try replacing it with a new one."
+
+/obj/item/shield/makeshift
+ name = "metal shield"
+ desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather, making it unwieldy."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "makeshift"
+ inhand_state = "metal"
+ slot_flags = null
+ damage_force = 10
+ throw_force = 7
+
+/obj/item/shield/riot/tower
+ name = "tower shield"
+ desc = "An immense tower shield. Designed to ensure maximum protection to the user, at the expense of mobility."
+ item_state = "metal"
+ damage_force = 16
+ encumbrance = ITEM_ENCUMBRANCE_SHIELD_TOWER
+ throw_force = 15 //Massive piece of metal
+ w_class = WEIGHT_CLASS_HUGE
+
+/obj/item/shield/riot/tower/swat
+ name = "swat shield"
+
+/obj/item/shield/riot/energy_proof
+ name = "energy resistant shield"
+ desc = "An ablative shield designed to absorb and disperse energy attacks. This comes at significant cost to its ability to withstand ballistics and kinetics, breaking apart easily."
+ icon_state = "riot-laser"
+
+/obj/item/shield/riot/kinetic_proof
+ name = "kinetic resistant shield"
+ desc = "A polymer and ceramic shield designed to absorb ballistic projectiles and kinetic force. It doesn't do very well into energy attacks, especially from weapons that inflict burns."
+ icon_state = "riot-bullet"
+
+//Exotics/Costume Shields
+/obj/item/shield/riot/roman
+ name = "scutum"
+ desc = "A replica shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "roman"
+ slot_flags = SLOT_BACK
+ materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000)
+
+/obj/item/shield/fluff/roman
+ name = "replica scutum"
+ desc = "A replica shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "roman"
+ slot_flags = SLOT_BACK
+ damage_force = 5.0
+ throw_force = 5.0
+ throw_speed = 2
+ throw_range = 6
+
+/obj/item/shield/riot/buckler
+ name = "buckler"
+ desc = "A wrist mounted round shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "buckler"
+ slot_flags = SLOT_BACK | SLOT_BELT
+ materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000)
+
+/obj/item/shield/riot/foam
+ name = "foam riot shield"
+ desc = "A shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "foam"
+ slot_flags = SLOT_BACK
+ damage_force = 0
+ throw_force = 0
+ throw_speed = 2
+ throw_range = 6
+ materials_base = list(MAT_PLASTIC = 7500, "foam" = 1000)
+
+/obj/item/shield/riot/foam/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ var/allowed = FALSE
+ if(isobj(weapon))
+ var/obj/casted_object = weapon
+ if(istype(casted_object, /obj/projectile))
+ var/obj/projectile/casted_projectile = casted_object
+ if(istype(casted_projectile, /obj/projectile/bullet/reusable/foam))
+ allowed = TRUE
+ else if(casted_object.get_primary_material_id() == /datum/material/toy_foam::id)
+ allowed = TRUE
+ if(!allowed)
+ return
+ return ..()
diff --git a/code/game/objects/items/shield/types/shields_legacy_vr.dm b/code/game/objects/items/shield/types/shields_legacy_vr.dm
new file mode 100644
index 000000000000..d0d00bba9d9a
--- /dev/null
+++ b/code/game/objects/items/shield/types/shields_legacy_vr.dm
@@ -0,0 +1,14 @@
+
+/obj/item/shield/fluff/wolfgirlshield
+ name = "Autumn Shield"
+ desc = "A shiny silvery shield with a large red leaf symbol in the center."
+ icon = 'icons/obj/weapons_vr.dmi'
+ icon_state = "wolfgirlshield"
+ slot_flags = SLOT_BACK | SLOT_OCLOTHING
+ damage_force = 5.0
+ throw_force = 5.0
+ throw_speed = 2
+ throw_range = 6
+ item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', SLOT_ID_BACK = 'icons/vore/custom_items_vr.dmi', SLOT_ID_SUIT = 'icons/vore/custom_items_vr.dmi')
+ attack_verb = list("shoved", "bashed")
+ allowed = list(/obj/item/melee/fluffstuff/wolfgirlsword)
diff --git a/code/game/objects/items/shield/types/transforming.dm b/code/game/objects/items/shield/types/transforming.dm
new file mode 100644
index 000000000000..d60a18805403
--- /dev/null
+++ b/code/game/objects/items/shield/types/transforming.dm
@@ -0,0 +1,110 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/**
+ * toggleable shields, like energy combat shields and telescoping shields
+ */
+/obj/item/shield/transforming
+ /// are we active?
+ var/active = FALSE
+ /// when active, do we use an overlay instead of an icon state?
+ ///
+ /// * applies to regular overlay
+ /// * applies to worn overlay as well
+ var/active_via_overlay = FALSE
+
+ var/active_weight_class = WEIGHT_CLASS_BULKY
+ var/inactive_weight_class
+ var/active_weight_volume
+ var/inactive_weight_volume
+
+ var/active_damage_force
+ var/inactive_damage_force
+
+ /// activation sound; also deactivation if it's not specified
+ var/activation_sound = 'sound/weapons/empty.ogg'
+ var/deactivation_sound
+ /// sound volume
+ var/toggle_sound_volume = 50
+
+/obj/item/shield/transforming/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ if(!active)
+ return // cancel
+ return ..()
+
+/obj/item/shield/transforming/update_icon_state()
+ icon_state = "[initial(icon_state)][active && !active_via_overlay ? "-active" : ""]"
+ return ..()
+
+/obj/item/shield/transforming/update_overlays()
+ . = ..()
+ if(!active || !active_via_overlay)
+ return
+ . += build_active_overlay()
+
+/obj/item/shield/transforming/proc/build_active_overlay()
+ RETURN_TYPE(/image)
+ var/image/creating = image(icon, "[base_icon_state || icon_state]-active")
+ return creating
+
+/obj/item/shield/transforming/proc/build_active_worn_overlay(worn_state)
+ RETURN_TYPE(/image)
+ var/image/creating = image(icon, "[worn_state]-active")
+ return creating
+
+/obj/item/shield/transforming/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
+ . = ..()
+ if(active_via_overlay && active)
+ MA.overlays += build_active_worn_overlay(MA.icon_state)
+
+/obj/item/shield/transforming/on_attack_self(datum/event_args/actor/e_args)
+ . = ..()
+ if(.)
+ return
+ add_fingerprint(e_args.performer)
+ toggle(e_args)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+
+/**
+ * actor can be /datum/event_args/actor or a single mob.
+ */
+/obj/item/shield/transforming/proc/toggle(datum/event_args/actor/actor, silent)
+ active = !active
+ if(active)
+ on_activate(actor, silent)
+ else
+ on_deactivate(actor, silent)
+ update_icon()
+ update_worn_icon()
+
+/**
+ * actor can be /datum/event_args/actor or a single mob.
+ */
+/obj/item/shield/transforming/proc/on_activate(datum/event_args/actor/actor, silent)
+ E_ARGS_WRAP_USER_TO_ACTOR(actor)
+
+ damage_force = VALUE_OR_DEFAULT(active_damage_force, initial(damage_force))
+
+ set_weight_class(VALUE_OR_DEFAULT(active_weight_class, initial(w_class)))
+ set_weight_volume(VALUE_OR_DEFAULT(active_weight_volume, initial(weight_volume)))
+
+ if(!silent && activation_sound)
+ playsound(src, activation_sound, toggle_sound_volume, TRUE)
+
+ // todo: logging
+
+/**
+ * actor can be /datum/event_args/actor or a single mob.
+ */
+/obj/item/shield/transforming/proc/on_deactivate(datum/event_args/actor/actor, silent)
+ E_ARGS_WRAP_USER_TO_ACTOR(actor)
+
+ damage_force = VALUE_OR_DEFAULT(inactive_damage_force, initial(damage_force))
+
+ set_weight_class(VALUE_OR_DEFAULT(inactive_weight_class, initial(w_class)))
+ set_weight_volume(VALUE_OR_DEFAULT(inactive_weight_volume, initial(weight_volume)))
+
+ if(!silent && (activation_sound || deactivation_sound))
+ playsound(src, deactivation_sound || activation_sound, toggle_sound_volume, TRUE)
+
+ // todo: logging
diff --git a/code/game/objects/items/shield/types/transforming/energy.dm b/code/game/objects/items/shield/types/transforming/energy.dm
new file mode 100644
index 000000000000..143665070c65
--- /dev/null
+++ b/code/game/objects/items/shield/types/transforming/energy.dm
@@ -0,0 +1,111 @@
+/obj/item/shield/transforming/energy
+ name = "energy combat shield"
+ desc = "A shield capable of stopping most projectile and melee attacks. It can be retracted, expanded, and stored anywhere."
+ icon = 'icons/items/shields/transforming.dmi'
+ icon_state = "energy"
+ base_icon_state = "energy"
+ slot_flags = SLOT_EARS
+ atom_flags = NOCONDUCT
+
+ damage_force = 3
+ active_damage_force = 10
+
+ w_class = WEIGHT_CLASS_TINY
+ active_weight_class = WEIGHT_CLASS_BULKY
+
+ throw_force = 5.0
+ throw_speed = 1
+ throw_range = 4
+
+ passive_parry = /datum/passive_parry/shield{
+ parry_chance_default = 50;
+ parry_frame = /datum/parry_frame/passive_block/energy;
+ }
+
+ active_via_overlay = TRUE
+
+ var/lrange = 1.5
+ var/lpower = 1.5
+ var/lcolor = "#006AFF"
+
+ origin_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_ILLEGAL = 4)
+ attack_verb = list("shoved", "bashed")
+
+ /// drop projectiles sometimes?
+ var/legacy_projectile_damage_drop = TRUE
+ /// divisor to projectile damage before we drop the hit
+ var/legacy_projectile_damage_drop_divisor = 1.6
+
+ activation_sound = 'sound/weapons/saberon.ogg'
+ deactivation_sound = 'sound/weapons/saberoff.ogg'
+
+/obj/item/shield/transforming/energy/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data)
+ if(istype(weapon, /obj/projectile))
+ var/obj/projectile/casted_projectile = weapon
+ if(legacy_projectile_damage_drop)
+ if((is_sharp(casted_projectile) && casted_projectile.damage > 10) || (casted_projectile.projectile_type & PROJECTILE_TYPE_BEAM))
+ if(prob(casted_projectile.damage / legacy_projectile_damage_drop_divisor))
+ return // drop the shield
+ . = ..()
+ if(!.)
+ return
+ var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
+ spark_system.set_up(3, 0, defending.loc)
+ spark_system.start()
+
+/obj/item/shield/transforming/energy/on_activate(datum/event_args/actor/actor, silent)
+ . = ..()
+ slot_flags = NONE
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("You activate \the [src]."),
+ target = src,
+ )
+ set_light(lrange, lpower, lcolor)
+
+/obj/item/shield/transforming/energy/on_deactivate(datum/event_args/actor/actor, silent)
+ . = ..()
+ slot_flags = SLOT_EARS
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("You collapse \the [src]."),
+ target = src,
+ )
+ set_light(0)
+
+/obj/item/shield/transforming/energy/build_active_overlay()
+ var/image/built = ..()
+ if(lcolor)
+ built.color = lcolor
+ return built
+
+/obj/item/shield/transforming/energy/build_active_worn_overlay()
+ var/image/built = ..()
+ if(lcolor)
+ built.color = lcolor
+ return built
+
+// todo: legacy below
+
+/obj/item/shield/transforming/energy/AltClick(mob/living/user)
+ if(!in_range(src, user)) //Basic checks to prevent abuse
+ return
+ if(user.incapacitated() || !istype(user))
+ to_chat(user, "You can't do that right now!")
+ return
+ if(alert("Are you sure you want to recolor your shield?", "Confirm Recolor", "Yes", "No") == "Yes")
+ var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null
+ if(energy_color_input)
+ lcolor = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1)
+ update_icon()
+
+/obj/item/shield/transforming/energy/examine(mob/user, dist)
+ . = ..()
+ . += "Alt-click to recolor it."
+
+/obj/item/shield/transforming/energy/imperial
+ name = "imperial shield"
+ desc = "What the hell is this?"
+ desc = "It's really easy to mispronounce the name of this shield if you've only read it in books."
+ icon_state = "imperial_shield"
+ base_icon_state = "imperial_shield"
diff --git a/code/game/objects/items/shield/types/transforming/telescopic.dm b/code/game/objects/items/shield/types/transforming/telescopic.dm
new file mode 100644
index 000000000000..76e96d790f6f
--- /dev/null
+++ b/code/game/objects/items/shield/types/transforming/telescopic.dm
@@ -0,0 +1,37 @@
+/obj/item/shield/transforming/telescopic
+ name = "telescopic shield"
+ desc = "An advanced riot shield made of lightweight materials that collapses for easy storage."
+ icon = 'icons/items/shields/transforming.dmi'
+ icon_state = "telescopic"
+ base_icon_state = "telescopic"
+ slot_flags = NONE
+
+ damage_force = 5
+ active_damage_force = 10
+ inactive_damage_force = 5
+
+ w_class = WEIGHT_CLASS_NORMAL
+ active_weight_class = WEIGHT_CLASS_BULKY
+ inactive_weight_class = WEIGHT_CLASS_NORMAL
+
+ activation_sound = 'sound/weapons/empty.ogg'
+
+ throw_force = 5
+ throw_speed = 3
+ throw_range = 5
+
+/obj/item/shield/transforming/telescopic/on_activate(datum/event_args/actor/actor, silent)
+ . = ..()
+ slot_flags = SLOT_BACK
+ actor.chat_feedback(
+ SPAN_WARNING("You extend \the [src]."),
+ target = src,
+ )
+
+/obj/item/shield/transforming/telescopic/on_deactivate(datum/event_args/actor/actor, silent)
+ . = ..()
+ slot_flags = NONE
+ actor.chat_feedback(
+ SPAN_WARNING("You collapse \the [src]."),
+ target = src,
+ )
diff --git a/code/game/objects/items/shield_projector/shield_matrix.dm b/code/game/objects/items/shield_projector/shield_matrix.dm
new file mode 100644
index 000000000000..eee6c5f280c2
--- /dev/null
+++ b/code/game/objects/items/shield_projector/shield_matrix.dm
@@ -0,0 +1,79 @@
+// todo: /obj/effect/shield_matrix
+// This is the actual shield. The projector is a different item.
+/obj/effect/directional_shield
+ name = "directional combat shield"
+ desc = "A wide shield, which has the property to block incoming projectiles but allow outgoing projectiles to pass it. \
+ Slower moving objects are not blocked, so people can walk in and out of the barrier, and things can be thrown into and out \
+ of it."
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "directional_shield"
+ density = FALSE // People can move pass these shields.
+ opacity = FALSE
+ anchored = TRUE
+ integrity_flags = INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF
+ layer = MOB_LAYER + 0.1
+ mouse_opacity = FALSE
+ var/obj/item/shield_projector/projector = null // The thing creating the shield.
+ var/x_offset = 0 // Offset from the 'center' of where the projector is, so that if it moves, the shield can recalc its position.
+ var/y_offset = 0 // Ditto.
+
+/obj/effect/directional_shield/New(var/newloc, var/new_projector)
+ if(new_projector)
+ projector = new_projector
+ var/turf/us = get_turf(src)
+ var/turf/them = get_turf(projector)
+ if(them)
+ x_offset = us.x - them.x
+ y_offset = us.y - them.y
+ else
+ update_color()
+ ..(newloc)
+
+/obj/effect/directional_shield/proc/relocate()
+ if(!projector)
+ return // Nothing to follow.
+ var/turf/T = get_turf(projector)
+ if(!T)
+ return
+ var/turf/new_pos = locate(T.x + x_offset, T.y + y_offset, T.z)
+ if(new_pos)
+ forceMove(new_pos)
+ else
+ qdel(src)
+
+/obj/effect/directional_shield/proc/update_color(var/new_color)
+ if(!projector)
+ color = "#0099FF"
+ else
+ animate(src, color = new_color, 5)
+// color = new_color
+
+/obj/effect/directional_shield/Destroy()
+ if(projector)
+ projector.active_shields -= src
+ projector = null
+ return ..()
+
+/obj/effect/directional_shield/CanPass(atom/movable/mover, turf/target)
+ . = ..()
+ if(istype(mover, /obj/projectile))
+ var/obj/projectile/P = mover
+ if(P.projectile_type & PROJECTILE_TYPE_TRACE)
+ return TRUE
+ if(check_defensive_arc_tile(src, P, 90, null, dir))
+ return FALSE
+ return TRUE
+
+/obj/effect/directional_shield/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ impact_flags &= ~PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ adjust_health(-proj.get_structure_damage())
+ playsound(src, 'sound/effects/EMPulse.ogg', 75, 1)
+
+// All the shields tied to their projector are one 'unit', and don't have individualized health values like most other shields.
+/obj/effect/directional_shield/proc/adjust_health(amount)
+ if(projector)
+ projector.adjust_health(amount) // Projector will kill the shield if needed.
+ // If the shield lacks a projector, then it was probably spawned in by an admin for bus, so it's indestructable.
diff --git a/code/modules/shieldgen/directional_shield.dm b/code/game/objects/items/shield_projector/shield_projector.dm
similarity index 81%
rename from code/modules/shieldgen/directional_shield.dm
rename to code/game/objects/items/shield_projector/shield_projector.dm
index fd1bc9df0f6d..e9b3547cf28e 100644
--- a/code/modules/shieldgen/directional_shield.dm
+++ b/code/game/objects/items/shield_projector/shield_projector.dm
@@ -1,81 +1,3 @@
-// This is the actual shield. The projector is a different item.
-/obj/effect/directional_shield
- name = "directional combat shield"
- desc = "A wide shield, which has the property to block incoming projectiles but allow outgoing projectiles to pass it. \
- Slower moving objects are not blocked, so people can walk in and out of the barrier, and things can be thrown into and out \
- of it."
- icon = 'icons/effects/effects.dmi'
- icon_state = "directional_shield"
- density = FALSE // People can move pass these shields.
- opacity = FALSE
- anchored = TRUE
- integrity_flags = INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF
- layer = MOB_LAYER + 0.1
- mouse_opacity = FALSE
- var/obj/item/shield_projector/projector = null // The thing creating the shield.
- var/x_offset = 0 // Offset from the 'center' of where the projector is, so that if it moves, the shield can recalc its position.
- var/y_offset = 0 // Ditto.
-
-/obj/effect/directional_shield/New(var/newloc, var/new_projector)
- if(new_projector)
- projector = new_projector
- var/turf/us = get_turf(src)
- var/turf/them = get_turf(projector)
- if(them)
- x_offset = us.x - them.x
- y_offset = us.y - them.y
- else
- update_color()
- ..(newloc)
-
-/obj/effect/directional_shield/proc/relocate()
- if(!projector)
- return // Nothing to follow.
- var/turf/T = get_turf(projector)
- if(!T)
- return
- var/turf/new_pos = locate(T.x + x_offset, T.y + y_offset, T.z)
- if(new_pos)
- forceMove(new_pos)
- else
- qdel(src)
-
-/obj/effect/directional_shield/proc/update_color(var/new_color)
- if(!projector)
- color = "#0099FF"
- else
- animate(src, color = new_color, 5)
-// color = new_color
-
-/obj/effect/directional_shield/Destroy()
- if(projector)
- projector.active_shields -= src
- projector = null
- return ..()
-
-/obj/effect/directional_shield/CanPass(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover, /obj/projectile))
- var/obj/projectile/P = mover
- if(istype(P, /obj/projectile/test)) // Turrets need to try to kill the shield and so their test bullet needs to penetrate.
- return TRUE
-
- var/bad_arc = global.reverse_dir[dir] // Arc of directions from which we cannot block.
- if(check_shield_arc(src, bad_arc, P)) // This is actually for mobs but it will work for our purposes as well.
- return FALSE
- return TRUE
-
-/obj/effect/directional_shield/bullet_act(var/obj/projectile/P)
- adjust_health(-P.get_structure_damage())
- P.on_hit()
- playsound(src, 'sound/effects/EMPulse.ogg', 75, 1)
-
-// All the shields tied to their projector are one 'unit', and don't have individualized health values like most other shields.
-/obj/effect/directional_shield/proc/adjust_health(amount)
- if(projector)
- projector.adjust_health(amount) // Projector will kill the shield if needed.
- // If the shield lacks a projector, then it was probably spawned in by an admin for bus, so it's indestructable.
-
// This actually creates the shields. It's an item so that it can be carried, but it could also be placed inside a stationary object if desired.
// It should work inside the contents of any mob.
diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm
index 0d19a6ecb53b..3f2ad6ff22e5 100644
--- a/code/game/objects/items/shooting_range.dm
+++ b/code/game/objects/items/shooting_range.dm
@@ -75,12 +75,14 @@
desc = "A shooting target that looks vaguely human shaped but not enough to cause controversy."
hp = 1800 // alium onest too kinda
-/obj/item/target/bullet_act(var/obj/projectile/Proj)
- var/p_x = Proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset Proj.p_x!"
- var/p_y = Proj.p_y + pick(0,0,0,0,0,-1,1)
+/obj/item/target/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+
+ var/p_x = proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset proj.p_x!"
+ var/p_y = proj.p_y + pick(0,0,0,0,0,-1,1)
var/decaltype = 1 // 1 - scorch, 2 - bullet
- if(istype(/obj/projectile/bullet, Proj))
+ if(istype(/obj/projectile/bullet, proj))
decaltype = 2
@@ -88,7 +90,7 @@
if( virtualIcon.GetPixel(p_x, p_y) ) // if the located pixel isn't blank (null)
- hp -= Proj.damage
+ hp -= proj.damage
if(hp <= 0)
for(var/mob/O in oviewers())
if ((O.client && !( O.has_status_effect(/datum/status_effect/sight/blindness) )))
@@ -109,7 +111,7 @@
bmark.pixel_x--
bmark.pixel_y--
- if(Proj.damage >= 20 || istype(Proj, /obj/projectile/beam/practice))
+ if(proj.damage >= 20 || istype(proj, /obj/projectile/beam/practice))
bmark.icon_state = "scorch"
bmark.setDir(pick(NORTH,SOUTH,EAST,WEST)) // random scorch design
@@ -121,12 +123,12 @@
// Bullets are hard. They make dents!
bmark.icon_state = "dent"
- if(Proj.damage >= 10 && bulletholes.len <= 35) // maximum of 35 bullet holes
+ if(proj.damage >= 10 && bulletholes.len <= 35) // maximum of 35 bullet holes
if(decaltype == 2) // bullet
- if(prob(Proj.damage+30)) // bullets make holes more commonly!
+ if(prob(proj.damage+30)) // bullets make holes more commonly!
new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole
else // Lasers!
- if(prob(Proj.damage-10)) // lasers make holes less commonly
+ if(prob(proj.damage-10)) // lasers make holes less commonly
new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole
// draw bullet holes
@@ -141,8 +143,7 @@
return
- return PROJECTILE_CONTINUE // the bullet/projectile goes through the target!
-
+ return . | PROJECTILE_IMPACT_PASSTHROUGH
// Small memory holder entity for transparent bullet holes
/datum/bullethole
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index aecfe62bf7f6..2e9fea684955 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -262,8 +262,8 @@
/obj/item/cell/device/weapon,
/obj/item/material/butterfly,
/obj/item/material/knife,
- /obj/item/melee/energy/sword,
- /obj/item/shield/energy,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/shield/transforming/energy,
/obj/item/ammo_casing/,
/obj/item/ammo_magazine/,
/obj/item/storage/box/beanbags,
diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm
index 558830cbd480..98673ed4e5c1 100644
--- a/code/game/objects/items/storage/lockbox.dm
+++ b/code/game/objects/items/storage/lockbox.dm
@@ -36,7 +36,7 @@
return
else
to_chat(user, "Access Denied")
- else if(istype(W, /obj/item/melee/energy/blade))
+ else if(istype(W, /obj/item/melee/ninja_energy_blade))
if(emag_act(INFINITY, user, W, "The locker has been sliced open by [user] with an energy blade!", "You hear metal being sliced and sparks flying."))
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src.loc)
diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm
index ee732cef7faf..1c4f05bca68a 100644
--- a/code/game/objects/items/storage/secure.dm
+++ b/code/game/objects/items/storage/secure.dm
@@ -33,7 +33,7 @@
/obj/item/storage/secure/attackby(obj/item/W as obj, mob/user as mob)
if(locked)
- if (istype(W, /obj/item/melee/energy/blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]"))
+ if (istype(W, /obj/item/melee/ninja_energy_blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]"))
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src.loc)
spark_system.start()
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 710acf38d926..03498f3fb1e0 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -28,7 +28,7 @@
new /obj/item/plastique(src)
if("murder")
- new /obj/item/melee/energy/sword(src)
+ new /obj/item/melee/transforming/energy/sword(src)
new /obj/item/clothing/glasses/thermal/syndi(src)
new /obj/item/card/emag(src)
new /obj/item/clothing/shoes/syndigaloshes(src)
diff --git a/code/game/objects/items/tools/switchtool.dm b/code/game/objects/items/tools/switchtool.dm
index c6d9b2bd7545..ae87558fd9e3 100644
--- a/code/game/objects/items/tools/switchtool.dm
+++ b/code/game/objects/items/tools/switchtool.dm
@@ -169,11 +169,6 @@
/obj/item/switchtool/proc/get_switchtool_enum(obj/item/I)
return tools[I]
-/obj/item/switchtool/handle_shield(mob/user)
- if(get_switchtool_enum(deployed) == SWITCHTOOL_SHIELD)
- return TRUE
- return FALSE
-
/obj/item/switchtool/update_overlays()
. = ..()
if(!deployed)
@@ -344,8 +339,8 @@
/obj/item/multitool/holoswitch = SWITCHTOOL_MULTITOOL,
/obj/item/flashlight/holoswitch = SWITCHTOOL_LIGHT,
/obj/item/soap/holoswitch = SWITCHTOOL_SOAP,
- /obj/item/melee/energy/sword/holoswitch = SWITCHTOOL_SWORD,
- /obj/item/shield/holoswitch = SWITCHTOOL_SHIELD
+ /obj/item/melee/transforming/energy/sword/holoswitch = SWITCHTOOL_SWORD,
+ // /obj/item/shield/holoswitch = SWITCHTOOL_SHIELD
)
tool_functions = list(
TOOL_SCALPEL,
@@ -484,7 +479,7 @@
desc = "This should not exist."
tool_speed = 0.9
-/obj/item/melee/energy/sword/holoswitch
+/obj/item/melee/transforming/energy/sword/holoswitch
name = "hardlight blade"
desc = "This should not exist."
diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm
index 630b93d5e7c4..58db3effd425 100644
--- a/code/game/objects/items/weapons/cigs_lighters.dm
+++ b/code/game/objects/items/weapons/cigs_lighters.dm
@@ -289,8 +289,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM
/obj/item/clothing/mask/smokable/cigarette/attackby(obj/item/W as obj, mob/user as mob)
..()
- if(istype(W, /obj/item/melee/energy/sword))
- var/obj/item/melee/energy/sword/S = W
+ if(istype(W, /obj/item/melee/transforming/energy/sword))
+ var/obj/item/melee/transforming/energy/sword/S = W
if(S.active)
light("[user] swings their [W], barely missing their nose. They light their [name] in the process.")
@@ -464,7 +464,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
quench()
/obj/item/clothing/mask/smokable/pipe/attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/melee/energy/sword))
+ if(istype(W, /obj/item/melee/transforming/energy/sword))
return
..()
diff --git a/code/game/objects/items/weapons/grenades/concussion.dm b/code/game/objects/items/weapons/grenades/concussion.dm
index b8d5da08343b..c3ea4db24d4a 100644
--- a/code/game/objects/items/weapons/grenades/concussion.dm
+++ b/code/game/objects/items/weapons/grenades/concussion.dm
@@ -84,5 +84,5 @@
var/turf/O = get_turf(src)
if(!O)
return
- src.fragmentate(O, num_fragments, spread_range, fragment_types)
+ shrapnel_explosion(num_fragments, spread_range, fragment_types)
..()
diff --git a/code/game/objects/items/weapons/grenades/explosive.dm b/code/game/objects/items/weapons/grenades/explosive.dm
index 0eae29d84cca..c750cac61625 100644
--- a/code/game/objects/items/weapons/grenades/explosive.dm
+++ b/code/game/objects/items/weapons/grenades/explosive.dm
@@ -21,7 +21,7 @@
if(explosion_size)
on_explosion(O)
- src.fragmentate(O, num_fragments, spread_range, fragment_types)
+ shrapnel_explosion(num_fragments, spread_range, fragment_types)
qdel(src)
/obj/item/grenade/explosive/proc/on_explosion(var/turf/O)
@@ -38,34 +38,6 @@
fragment_types = list(/obj/projectile/bullet/pellet/fragment)
num_fragments = 200 //total number of fragments produced by the grenade
-
-
-/obj/proc/fragmentate(var/turf/T=get_turf(src), var/fragment_number = 30, var/spreading_range = 5, var/list/fragtypes=list(/obj/projectile/bullet/pellet/fragment/))
- set waitfor = 0
- var/list/target_turfs = getcircle(T, spreading_range)
- var/fragments_per_projectile = round(fragment_number/target_turfs.len)
-
- for(var/turf/O in target_turfs)
- sleep(0)
- var/fragment_type = pickweight(fragtypes)
- var/obj/projectile/bullet/pellet/fragment/P = new fragment_type(T)
- P.pellets = fragments_per_projectile
- P.shot_from = name
-
- P.old_style_target(O)
- P.fire()
-
- //Make sure to hit any mobs in the source turf
- for(var/mob/living/M in T)
- //lying on a frag grenade while the grenade is on the ground causes you to absorb most of the shrapnel.
- //you will most likely be dead, but others nearby will be spared the fragments that hit you instead.
- if(M.lying && isturf(src.loc))
- P.projectile_attack_mob(M, 0, 5)
- else if(!M.lying && src.loc != get_turf(src)) //if it's not on the turf, it must be in the mob!
- P.projectile_attack_mob(M, 0, 25) //you're holding a grenade, dude!
- else
- P.projectile_attack_mob(M, 0, 100) //otherwise, allow a decent amount of fragments to pass
-
/obj/item/grenade/explosive/mini
name = "mini fragmentation grenade"
desc = "A miniaturized fragmentation grenade, this one poses relatively little threat on its own."
diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm
index 7018d5f8d96f..1a447da35f2b 100644
--- a/code/game/objects/items/weapons/grenades/flashbang.dm
+++ b/code/game/objects/items/weapons/grenades/flashbang.dm
@@ -110,7 +110,7 @@
var/turf/O = get_turf(src)
if(!O)
return
- src.fragmentate(O, num_fragments, spread_range, fragment_types)
+ shrapnel_explosion(num_fragments, spread_range, fragment_types)
..()
/obj/item/grenade/flashbang/stingbang/shredbang
diff --git a/code/game/objects/items/weapons/grenades/projectile.dm b/code/game/objects/items/weapons/grenades/projectile.dm
index 3b781ef46118..2621d12150bf 100644
--- a/code/game/objects/items/weapons/grenades/projectile.dm
+++ b/code/game/objects/items/weapons/grenades/projectile.dm
@@ -7,6 +7,7 @@
//The radius of the circle used to launch projectiles. Lower values mean less projectiles are used but if set too low gaps may appear in the spread pattern
var/spread_range = 7
+ var/total_pellets = 1 // default value of 1 just forces one per turf as we round up
loadable = FALSE
@@ -17,12 +18,9 @@
if(!O)
return
- src.launch_many_projectiles(O, spread_range, projectile_types)
-
+ shrapnel_explosion(total_pellets, spread_range, projectile_types)
qdel(src)
-
-
/obj/item/grenade/shooter/rubber
name = "rubber pellet grenade"
desc = "An anti-riot grenade that fires a cloud of rubber projectiles upon detonation."
@@ -47,30 +45,3 @@
/obj/item/grenade/shooter/energy/tesla
name = "tesla grenade"
projectile_types = list(/obj/projectile/beam/chain_lightning/lesser)
-
-
-// This is just fragmentate, but less specific. Don't know how to make either of them less awful, at the moment
-/obj/proc/launch_many_projectiles(var/turf/T=get_turf(src), var/spreading_range = 5, var/list/projectiletypes=list(/obj/projectile/bullet/pistol/rubber))
- set waitfor = 0
- var/list/target_turfs = getcircle(T, spreading_range)
-
- for(var/turf/O in target_turfs)
- sleep(0)
- var/shot_type = pick(projectiletypes)
-
- var/obj/projectile/P = new shot_type(T)
- P.shot_from = src.name
-
- P.old_style_target(O)
- P.fire()
-
- //Make sure to hit any mobs in the source turf
- for(var/mob/living/M in T)
- //lying on a frag grenade while the grenade is on the ground causes you to absorb most of the shrapnel.
- //you will most likely be dead, but others nearby will be spared the fragments that hit you instead.
- if(M.lying && isturf(src.loc))
- P.projectile_attack_mob(M, 0, 5)
- else if(!M.lying && src.loc != get_turf(src)) //if it's not on the turf, it must be in the mob!
- P.projectile_attack_mob(M, 0, 25) //you're holding a grenade, dude!
- else
- P.projectile_attack_mob(M, 0, 100) //otherwise, allow a decent amount of fragments to pass
diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm
index 0bfe624f50f9..92769892f314 100644
--- a/code/game/objects/items/weapons/implants/implant.dm
+++ b/code/game/objects/items/weapons/implants/implant.dm
@@ -92,6 +92,10 @@
else
..()
+/obj/item/implant/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from)
+ . = ..()
+ imp_in = null
+ implanted = 0
//////////////////////////////
// Tracking Implant
//////////////////////////////
diff --git a/code/game/objects/items/weapons/material/knives.dm b/code/game/objects/items/weapons/material/knives.dm
index 2c7c409f1a73..a7fa5162c084 100644
--- a/code/game/objects/items/weapons/material/knives.dm
+++ b/code/game/objects/items/weapons/material/knives.dm
@@ -171,12 +171,10 @@
item_state = "armblade"
slot_flags = NONE
-/obj/item/material/knife/machete/armblade/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(default_parry_check(user, attacker, damage_source) && prob(33))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return TRUE
- return FALSE
+ passive_parry = /datum/passive_parry{
+ parry_chance_projectile = 0;
+ parry_chance_default = 33;
+ }
/obj/item/material/knife/machete/armblade/hardsuit
var/obj/item/hardsuit_module/armblade/storing_module
diff --git a/code/game/objects/items/weapons/material/material.dm b/code/game/objects/items/weapons/material/material.dm
index f1ebf5f3b956..c3394a87aea4 100644
--- a/code/game/objects/items/weapons/material/material.dm
+++ b/code/game/objects/items/weapons/material/material.dm
@@ -36,6 +36,7 @@
// var/dulled_divisor = 0.1 //Just drops the damage to a tenth
// var/drops_debris = 1
+
/obj/item/material/Initialize(mapload, material)
if(!isnull(material))
material_parts = material
diff --git a/code/game/objects/items/weapons/material/swords.dm b/code/game/objects/items/weapons/material/swords.dm
index 0b4cdc519d42..b29f86dd53b5 100644
--- a/code/game/objects/items/weapons/material/swords.dm
+++ b/code/game/objects/items/weapons/material/swords.dm
@@ -11,19 +11,17 @@
pickup_sound = 'sound/items/pickup/sword.ogg'
force_multiplier = 1.5
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 50;
+ parry_chance_projectile = 10;
+ }
+
/obj/item/material/sword/plasteel
material_parts = /datum/material/plasteel
/obj/item/material/sword/durasteel
material_parts = /datum/material/durasteel
-/obj/item/material/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(unique_parry_check(user, attacker, damage_source) && prob(50))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
-
/obj/item/material/sword/suicide_act(mob/user)
var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
viewers(user) << "[user] is falling on the [src.name]! It looks like [TU.he] [TU.is] trying to commit suicide."
@@ -63,31 +61,13 @@
SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
)
-/obj/item/material/sword/sabre/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(default_parry_check(user, attacker, damage_source) && prob(60))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
-
- playsound(user.loc, 'sound/items/drop/knife.ogg', 50, 1)
- return 1
- if(unique_parry_check(user, attacker, damage_source) && prob(50))
- user.visible_message("\The [user] deflects [attack_text] with \the [src]!")
-
- playsound(user.loc, 'sound/weapons/plasma_cutter.ogg', 50, 1)
- return 1
-
- return 0
-
-/obj/item/material/sword/sabre/unique_parry_check(mob/user, mob/attacker, atom/damage_source)
- if(user.incapacitated() || !istype(damage_source, /obj/projectile/))
- return 0
-
- var/bad_arc = global.reverse_dir[user.dir]
- if(!check_shield_arc(user, bad_arc, damage_source, attacker))
- return 0
-
- return 1
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_default = 50;
+ }
-//meant to play when unsheathing the blade from the sabre sheath.
+// meant to play when unsheathing the blade from the sabre sheath.
+// todo: -_- this should be on the sheath
+// todo: we need a better way to do unsheath sounds for weapons and storage...
/obj/item/material/sword/sabre/on_enter_storage(datum/object_system/storage/storage)
. = ..()
playsound(loc, 'sound/effects/holster/sheathin.ogg', 50, 1)
diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm
index 4e615154fed7..16f3fb71cd2b 100644
--- a/code/game/objects/items/weapons/material/twohanded.dm
+++ b/code/game/objects/items/weapons/material/twohanded.dm
@@ -28,6 +28,10 @@
drop_sound = 'sound/items/drop/sword.ogg'
pickup_sound = 'sound/items/pickup/sword.ogg'
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_melee = 15;
+ }
+
/obj/item/material/twohanded/update_held_icon()
var/mob/living/M = loc
if(istype(M) && M.can_wield_item(src) && is_held_twohanded(M))
@@ -50,14 +54,6 @@
. = ..()
update_icon()
-//Allow a small chance of parrying melee attacks when wielded - maybe generalize this to other weapons someday
-/obj/item/material/twohanded/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(wielded && default_parry_check(user, attacker, damage_source) && prob(15))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
-
/obj/item/material/twohanded/update_icon()
icon_state = "[base_icon][wielded]"
item_state = icon_state
diff --git a/code/game/objects/items/weapons/melee/deflect.dm b/code/game/objects/items/weapons/melee/deflect.dm
deleted file mode 100644
index e951f4d5c5a1..000000000000
--- a/code/game/objects/items/weapons/melee/deflect.dm
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * The home of basic deflect / defense code.
- */
-
-/obj/item/melee
- var/defend_chance = 5 // The base chance for the weapon to parry.
- var/projectile_parry_chance = 0 // The base chance for a projectile to be deflected.
-
-/obj/item/melee/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(.)
- return .
- if(default_parry_check(user, attacker, damage_source) && prob(defend_chance))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- return 1
- if(unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance))
- user.visible_message("\The [user] deflects [attack_text] with \the [src]!")
- return 1
-
- return 0
-
-/obj/item/melee/unique_parry_check(mob/user, mob/attacker, atom/damage_source)
- if(.)
- return .
- if(user.incapacitated() || !istype(damage_source, /obj/projectile))
- return 0
-
- var/bad_arc = global.reverse_dir[user.dir]
- if(!check_shield_arc(user, bad_arc, damage_source, attacker))
- return 0
-
- return 1
diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm
deleted file mode 100644
index 467de03f3614..000000000000
--- a/code/game/objects/items/weapons/melee/energy.dm
+++ /dev/null
@@ -1,729 +0,0 @@
-/obj/item/melee/energy
- var/active = 0
- var/active_force
- var/active_throwforce
- var/active_w_class
- var/active_embed_chance = 0 //In the off chance one of these is supposed to embed, you can just tweak this var
- icon = 'icons/obj/weapons.dmi'
- sharp = 0
- edge = 0
- armor_penetration = 50
- atom_flags = NOCONDUCT | NOBLOODY
- var/lrange = 2
- var/lpower = 2
- var/lcolor = "#0099FF"
- var/colorable = FALSE
- var/rainbow = FALSE
- // If it uses energy.
- var/use_cell = FALSE
- var/hitcost = 120
- var/obj/item/cell/bcell = null
- var/cell_type = /obj/item/cell/device
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
-
-/obj/item/melee/energy/proc/activate(mob/living/user)
- if(active)
- return
- active = 1
- if(rainbow)
- item_state = "[icon_state]_blade_rainbow"
- else
- item_state = "[icon_state]_blade"
- embed_chance = active_embed_chance
- damage_force = active_force
- throw_force = active_throwforce
- sharp = 1
- edge = 1
- set_weight_class(active_w_class)
- playsound(user, 'sound/weapons/saberon.ogg', 50, 1)
- update_icon()
- set_light(lrange, lpower, lcolor)
- to_chat(user, "Alt-click to recolor it.")
-
-/obj/item/melee/energy/proc/deactivate(mob/living/user)
- if(!active)
- return
- playsound(user, 'sound/weapons/saberoff.ogg', 50, 1)
- item_state = "[icon_state]"
- active = 0
- embed_chance = initial(embed_chance)
- damage_force = initial(damage_force)
- throw_force = initial(throw_force)
- sharp = initial(sharp)
- edge = initial(edge)
- set_weight_class(initial(w_class))
- update_icon()
- set_light(0,0)
-
-/obj/item/melee/energy/proc/use_charge(var/cost)
- if(active)
- if(bcell)
- if(bcell.checked_use(cost))
- return 1
- else
- return 0
- return null
-
-/obj/item/melee/energy/examine(mob/user, dist)
- . = ..()
- if(use_cell)
- if(bcell)
- . += "The blade is [round(bcell.percent())]% charged."
- if(!bcell)
- . += "The blade does not have a power source installed."
-
-/obj/item/melee/energy/attack_self(mob/user)
- . = ..()
- if(.)
- return
- if(use_cell)
- if((!bcell || bcell.charge < hitcost) && !active)
- to_chat(user, "\The [src] does not seem to have power.")
- return
-
- var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
- if (active)
- if ((MUTATION_CLUMSY in user.mutations) && prob(50))
- user.visible_message("\The [user] accidentally cuts [TU.himself] with \the [src].",\
- "You accidentally cut yourself with \the [src].")
- var/mob/living/carbon/human/H = ishuman(user)? user : null
- H.take_random_targeted_damage(brute = 5, burn = 5)
- deactivate(user)
- else
- activate(user)
-
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
- add_fingerprint(user)
- return
-
-/obj/item/melee/energy/suicide_act(mob/user)
- var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
- if(active)
- user.visible_message(pick("\The [user] is slitting [TU.his] stomach open with \the [src]! It looks like [TU.he] [TU.is] trying to commit seppuku.",\
- "\The [user] is falling on \the [src]! It looks like [TU.he] [TU.is] trying to commit suicide."))
- return (BRUTELOSS|FIRELOSS)
-
-/obj/item/melee/energy/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
- . = ..()
- if(active && use_cell)
- if(!use_charge(hitcost))
- deactivate(user)
- visible_message("\The [src]'s blade flickers, before deactivating.")
-
-/obj/item/melee/energy/attackby(obj/item/W, mob/user)
- if(istype(W, /obj/item/multitool) && colorable && !active)
- if(!rainbow)
- rainbow = TRUE
- else
- rainbow = FALSE
- to_chat(user, "You manipulate the color controller in [src].")
- update_icon()
- if(use_cell)
- if(istype(W, cell_type))
- if(!bcell)
- if(!user.attempt_insert_item_for_installation(W, src))
- return
- bcell = W
- to_chat(user, "You install a cell in [src].")
- update_icon()
- else
- to_chat(user, "[src] already has a cell.")
- else if(W.is_screwdriver() && bcell)
- bcell.update_icon()
- bcell.forceMove(get_turf(loc))
- bcell = null
- to_chat(user, "You remove the cell from \the [src].")
- deactivate()
- update_icon()
- return
- return ..()
-
-/obj/item/melee/energy/get_cell(inducer)
- return bcell
-
-/obj/item/melee/energy/update_icon()
- . = ..()
- var/mutable_appearance/blade_overlay = mutable_appearance(icon, "[icon_state]_blade")
- blade_overlay.color = lcolor
- color = lcolor
- if(rainbow)
- blade_overlay = mutable_appearance(icon, "[icon_state]_blade_rainbow")
- blade_overlay.color = "FFFFFF"
- color = "FFFFFF"
- cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other
- if(active)
- add_overlay(blade_overlay)
- if(istype(usr,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = usr
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
-/obj/item/melee/energy/AltClick(mob/living/user)
- if(!colorable) //checks if is not colorable
- return
- if(!in_range(src, user)) //Basic checks to prevent abuse
- return
- if(user.incapacitated() || !istype(user))
- to_chat(user, "You can't do that right now!")
- return
-
- if(alert("Are you sure you want to recolor your blade?", "Confirm Recolor", "Yes", "No") == "Yes")
- var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null
- if(energy_color_input)
- lcolor = "#[sanitize_hexcolor(energy_color_input)]"
- color = lcolor
- deactivate()
- update_icon()
- . = ..()
-
-
-/*
- * Energy Axe
- */
-/obj/item/melee/energy/axe
- name = "energy axe"
- desc = "An energised battle axe."
- icon_state = "eaxe"
- item_state = "eaxe"
- //active_force = 150 //holy...
- active_force = 60
- active_throwforce = 35
- active_w_class = WEIGHT_CLASS_HUGE
- //damage_force = 40
- //throw_force = 25
- damage_force = 20
- throw_force = 10
- throw_speed = 1
- throw_range = 5
- w_class = WEIGHT_CLASS_NORMAL
- origin_tech = list(TECH_MAGNET = 3, TECH_COMBAT = 4)
- attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut")
- sharp = 1
- edge = 1
- can_cleave = TRUE
-
-/obj/item/melee/energy/axe/activate(mob/living/user)
- ..()
- damtype = SEARING
- to_chat(user, "\The [src] is now energised.")
-
-/obj/item/melee/energy/axe/deactivate(mob/living/user)
- ..()
- damtype = BRUTE
- to_chat(user, "\The [src] is de-energised. It's just a regular axe now.")
-
-/obj/item/melee/energy/axe/suicide_act(mob/user)
- var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
- visible_message("\The [user] swings \the [src] towards [TU.his] head! It looks like [TU.he] [TU.is] trying to commit suicide.")
- return (BRUTELOSS|FIRELOSS)
-
-/obj/item/melee/energy/axe/charge
- name = "charge axe"
- desc = "An energised axe."
- active_force = 35
- active_throwforce = 20
- damage_force = 15
- use_cell = TRUE
- hitcost = 120
-
-/obj/item/melee/energy/axe/charge/loaded/Initialize(mapload)
- . = ..()
- bcell = new/obj/item/cell/device/weapon(src)
-
-/*
- * Energy Sword
- */
-/obj/item/melee/energy/sword
- color
- name = "energy sword"
- desc = "May the damage_force be within you."
- icon_state = "esword"
- item_state = "esword"
- active_force = 30
- active_throwforce = 20
- active_w_class = WEIGHT_CLASS_BULKY
- damage_force = 3
- throw_force = 5
- throw_speed = 1
- throw_range = 5
- w_class = WEIGHT_CLASS_SMALL
- atom_flags = NOBLOODY
- origin_tech = list(TECH_MAGNET = 3, TECH_ILLEGAL = 4)
- sharp = 1
- edge = 1
- colorable = TRUE
- drop_sound = 'sound/items/drop/sword.ogg'
- pickup_sound = 'sound/items/pickup/sword.ogg'
- projectile_parry_chance = 65
-
-/obj/item/melee/energy/sword/dropped(mob/user, atom_flags, atom/newLoc)
- . = ..()
- if(!istype(loc,/mob))
- deactivate(user)
-
-/obj/item/melee/energy/sword/activate(mob/living/user)
- if(!active)
- to_chat(user, "\The [src] is now energised.")
-
- ..()
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
-
-/obj/item/melee/energy/sword/deactivate(mob/living/user)
- if(active)
- to_chat(user, "\The [src] deactivates!")
- ..()
- attack_verb = list()
-
-/obj/item/melee/energy/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(active && default_parry_check(user, attacker, damage_source) && prob(60))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
-
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return 1
- if(active && unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance))
- user.visible_message("\The [user] deflects [attack_text] with \the [src]!")
-
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return 1
-
- return 0
-
-/obj/item/melee/energy/sword/unique_parry_check(mob/user, mob/attacker, atom/damage_source)
- if(user.incapacitated() || !istype(damage_source, /obj/projectile/))
- return 0
-
- var/bad_arc = global.reverse_dir[user.dir]
- if(!check_shield_arc(user, bad_arc, damage_source, attacker))
- return 0
-
- return 1
-
-/obj/item/melee/energy/sword/attackby(obj/item/W, mob/living/user, params)
- if(istype(W, /obj/item/melee/energy/sword))
- if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP))
- to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!")
- return
- if(istype(W, /obj/item/melee/energy/sword/charge))
- to_chat(user,"These blades are incompatible, you can't attach them to each other!")
- return
- else
- to_chat(user, "You combine the two energy swords, making a single supermassive blade! You're cool.")
- new /obj/item/melee/energy/sword/dualsaber(user.drop_location())
- qdel(W)
- qdel(src)
- else
- return ..()
-
-/obj/item/melee/energy/sword/pirate
- name = "energy cutlass"
- desc = "Arrrr matey."
- icon_state = "cutlass"
- item_state = "cutlass"
- colorable = TRUE
-
-//Return of the King
-/obj/item/melee/energy/sword/dualsaber
- name = "double-bladed energy sword"
- desc = "Handle with care."
- icon_state = "dualsaber"
- item_state = "dualsaber"
- damage_force = 3
- active_force = 60
- throw_force = 5
- throw_speed = 3
- armor_penetration = 35
- colorable = TRUE
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- projectile_parry_chance = 85
-
-/*
- *Ionic Rapier
- */
-
-/obj/item/melee/energy/sword/ionic_rapier
- name = "ionic rapier"
- desc = "Designed specifically for disrupting electronics at close range, it is extremely deadly against synthetics, but almost harmless to pure organic targets."
- description_info = "This is a dangerous melee weapon that will deliver a moderately powerful electromagnetic pulse to whatever it strikes. \
- Striking a lesser robotic entity will compel it to attack you, as well. It also does extra burn damage to robotic entities, but it does \
- very little damage to purely organic targets."
- icon_state = "ionrapier"
- item_state = "ionrapier"
- active_force = 5
- active_throwforce = 3
- active_embed_chance = 0
- sharp = 1
- edge = 1
- armor_penetration = 0
- atom_flags = NOBLOODY
- lrange = 2
- lpower = 2
- lcolor = "#0000FF"
- projectile_parry_chance = 30 // It's not specifically designed for cutting and slashing, but it can still, maybe, save your life.
-
-/obj/item/melee/energy/sword/ionic_rapier/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(istype(target, /obj) && (clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) && active)
- // EMP stuff.
- var/obj/O = target
- O.emp_act(3) // A weaker severity is used because this has infinite uses.
- playsound(get_turf(O), 'sound/effects/EMPulse.ogg', 100, 1)
- user.setClickCooldown(user.get_attack_speed(src)) // A lot of objects don't set click delay.
- return ..()
-
-/obj/item/melee/energy/sword/ionic_rapier/melee_mob_hit(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
- . = ..()
- var/mob/living/L = target
- if(!istype(L))
- return
- if(L.isSynthetic() && active)
- // Do some extra damage. Not a whole lot more since emp_act() is pretty nasty on FBPs already.
- L.emp_act(3) // A weaker severity is used because this has infinite uses.
- playsound(get_turf(L), 'sound/effects/EMPulse.ogg', 100, 1)
- L.adjustFireLoss(damage_force * 3) // 15 Burn, for 20 total.
- playsound(get_turf(L), 'sound/weapons/blade1.ogg', 100, 1)
-
- // Make lesser robots really mad at us.
- if(L.mob_class & MOB_CLASS_SYNTHETIC)
- if(L.has_polaris_AI())
- L.taunt(user)
- L.adjustFireLoss(damage_force * 6) // 30 Burn, for 50 total.
-
-/obj/item/melee/energy/sword/ionic_rapier/lance
- name = "zero-point lance"
- desc = "Designed specifically for disrupting electronics at relatively close range, however it is still capable of dealing some damage to living beings."
- active_force = 20
- armor_penetration = 15
- reach = 2
-
-/*
- * Charge blade. Uses a cell, and costs energy per strike.
- */
-
-/obj/item/melee/energy/sword/charge
- name = "charge sword"
- desc = "A small, handheld device which emits a high-energy 'blade'."
- origin_tech = list(TECH_COMBAT = 5, TECH_MAGNET = 3, TECH_ILLEGAL = 4)
- active_force = 25
- armor_penetration = 25
- projectile_parry_chance = 40
- colorable = TRUE
- use_cell = TRUE
- hitcost = 75
-
-/obj/item/melee/energy/sword/charge/loaded/Initialize(mapload)
- . = ..()
- bcell = new/obj/item/cell/device/weapon(src)
-
-/obj/item/melee/energy/sword/charge/attackby(obj/item/W, mob/living/user, params)
- if(istype(W, /obj/item/melee/energy/sword/charge))
- if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP))
- to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!")
- return
- else
- to_chat(user, "You combine the two charge swords, making a single supermassive blade! You're cool.")
- new /obj/item/melee/energy/sword/charge/dualsaber(user.drop_location())
- qdel(W)
- qdel(src)
- else
- return ..()
-
-//Charge Type Double Esword
-/obj/item/melee/energy/sword/charge/dualsaber
- name = "double-bladed charge sword"
- desc = "Make sure you bought batteries."
- icon_state = "dualsaber"
- item_state = "dualsaber"
- damage_force = 3
- active_force = 50
- throw_force = 5
- throw_speed = 3
- armor_penetration = 30
- colorable = TRUE
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- projectile_parry_chance = 65
- hitcost = 150
-
-//Energy Blade (ninja uses this)
-
-//Can't be activated or deactivated, so no reason to be a subtype of energy
-/obj/item/melee/energy/blade
- name = "energy blade"
- desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal."
- icon_state = "blade"
- item_state = "blade"
- damage_force = 40 //Normal attacks deal very high damage - about the same as wielded fire axe
- armor_penetration = 100
- sharp = 1
- edge = 1
- anchored = 1 // Never spawned outside of inventory, should be fine.
- throw_force = 1 //Throwing or dropping the item deletes it.
- throw_speed = 1
- throw_range = 1
- w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such.
- atom_flags = NOBLOODY
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- var/mob/living/creator
- var/datum/effect_system/spark_spread/spark_system
- projectile_parry_chance = 60
- lcolor = "#00FF00"
-
-/obj/item/melee/energy/blade/Initialize(mapload)
- . = ..()
- spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, src)
- spark_system.attach(src)
-
- START_PROCESSING(SSobj, src)
- set_light(lrange, lpower, lcolor)
-
-/obj/item/melee/energy/blade/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/item/melee/energy/blade/attack_self(mob/user)
- . = ..()
- if(.)
- return
- qdel(src)
-
-/obj/item/melee/energy/blade/dropped(mob/user, atom_flags, atom/newLoc)
- . = ..()
- qdel(src)
-
-/obj/item/melee/energy/blade/process(delta_time)
- if(!creator || loc != creator || !creator.is_holding(src))
- // Tidy up a bit.
- if(istype(loc,/mob/living))
- var/mob/living/carbon/human/host = loc
- if(istype(host))
- for(var/obj/item/organ/external/organ in host.organs)
- for(var/obj/item/O in organ.implants)
- if(O == src)
- organ.implants -= src
- host.pinned -= src
- host.embedded -= src
- host._handle_inventory_hud_remove(src)
- qdel(src)
-
-/obj/item/melee/energy/blade/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(default_parry_check(user, attacker, damage_source) && prob(60))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
-
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return 1
- if(unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance))
- user.visible_message("\The [user] deflects [attack_text] with \the [src]!")
-
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return 1
-
- return 0
-
-/obj/item/melee/energy/blade/unique_parry_check(mob/user, mob/attacker, atom/damage_source)
-
- if(user.incapacitated() || !istype(damage_source, /obj/projectile/))
- return 0
-
- var/bad_arc = global.reverse_dir[user.dir]
- if(!check_shield_arc(user, bad_arc, damage_source, attacker))
- return 0
-
- return 1
-
-//Energy Spear
-
-/obj/item/melee/energy/spear
- name = "energy spear"
- desc = "Concentrated energy forming a sharp tip at the end of a long rod."
- icon_state = "espear"
- armor_penetration = 75
- sharp = 1
- edge = 1
- damage_force = 5
- throw_force = 10
- throw_speed = 7
- throw_range = 11
- reach = 2
- w_class = WEIGHT_CLASS_BULKY
- active_force = 25
- active_throwforce = 30
- active_w_class = WEIGHT_CLASS_HUGE
- colorable = TRUE
- lcolor = "#800080"
-
-/obj/item/melee/energy/spear/activate(mob/living/user)
- if(!active)
- to_chat(user, "\The [src] is now energised.")
- ..()
- attack_verb = list("jabbed", "stabbed", "impaled")
- AddComponent(/datum/component/jousting)
-
-
-/obj/item/melee/energy/spear/deactivate(mob/living/user)
- if(active)
- to_chat(user, "\The [src] deactivates!")
- ..()
- attack_verb = list("whacked", "beat", "slapped", "thonked")
- DelComponent(/datum/component/jousting)
-
-/obj/item/melee/energy/spear/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(active && default_parry_check(user, attacker, damage_source) && prob(50))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return 1
- return 0
-
-/obj/item/melee/energy/hfmachete // ported from /vg/station - vgstation-coders/vgstation13#13913, fucked up by hatterhat
- name = "high-frequency machete"
- desc = "A high-frequency broad blade used either as an implement or in combat like a short sword."
- icon_state = "hfmachete0"
- sharp = TRUE
- edge = TRUE
- damage_force = 20 // You can be crueler than that, Jack.
- throw_force = 40
- throw_speed = 8
- throw_range = 8
- w_class = WEIGHT_CLASS_NORMAL
- siemens_coefficient = 1
- origin_tech = list(TECH_COMBAT = 3, TECH_ILLEGAL = 3)
- attack_verb = list("attacked", "diced", "cleaved", "torn", "cut", "slashed")
- armor_penetration = 50
- var/base_state = "hfmachete"
- attack_sound = "machete_hit_sound" // dont mind the meaty hit sounds if you hit something that isnt meaty
- can_cleave = TRUE
- embed_chance = 0 // let's not
-
-/obj/item/melee/energy/hfmachete/update_icon()
- icon_state = "[base_state][active]"
-
-/obj/item/melee/energy/hfmachete/attack_self(mob/user)
- toggleActive(user)
- add_fingerprint(user)
-
-/obj/item/melee/energy/hfmachete/proc/toggleActive(mob/user, var/togglestate = "")
- switch(togglestate)
- if("on")
- active = 1
- if("off")
- active = 0
- else
- active = !active
- if(active)
- damage_force = 40
- throw_force = 20
- throw_speed = 3
- // sharpness = 1.7
- // sharpness_flags += HOT_EDGE | CUT_WALL | CUT_AIRLOCK - if only there a good sharpness system
- armor_penetration = 100
- to_chat(user, " [src] starts vibrating.")
- playsound(user, 'sound/weapons/hf_machete/hfmachete1.ogg', 40, 0)
- set_weight_class(WEIGHT_CLASS_BULKY)
- // user.lazy_register_event(/lazy_event/on_moved, src, PROC_REF(mob_moved))
- else
- damage_force = initial(damage_force)
- throw_force = initial(throw_force)
- throw_speed = initial(throw_speed)
- // sharpness = initial(sharpness)
- // sharpness_flags = initial(sharpness_flags) - if only there was a good sharpness system
- armor_penetration = initial(armor_penetration)
- to_chat(user, " [src] stops vibrating.")
- playsound(user, 'sound/weapons/hf_machete/hfmachete0.ogg', 40, 0)
- set_weight_class(WEIGHT_CLASS_NORMAL)
- // user.lazy_unregister_event(/lazy_event/on_moved, src, PROC_REF(mob_moved))
- update_icon()
-
-/obj/item/melee/energy/hfmachete/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
- return
- ..()
- if(target)
- if(istype(target,/obj/effect/plant))
- var/obj/effect/plant/P = target
- P.die_off()
-
-/*
-/obj/item/melee/energy/hfmachete/dropped(mob/user, atom_flags, atom/newLoc)
- user.lazy_unregister_event(/lazy_event/on_moved, src, PROC_REF(mob_moved))
-
-/obj/item/melee/energy/hfmachete/throw_at_old(atom/target, range, speed, thrower) // todo: get silicons to interpret this because >sleeps
- if(!usr)
- return ..()
- spawn()
- playsound(src, get_sfx("machete_throw"),30, 0)
- animate(src, transform = turn(matrix(), -30), time = 1, loop = -1)
- animate(transform = turn(matrix(), -60), time = 1)
- animate(transform = turn(matrix(), -90), time = 1)
- animate(transform = turn(matrix(), -120), time = 1)
- animate(transform = turn(matrix(), -150), time = 1)
- animate(transform = null, time = 1)
- while(throwing)
- sleep(5)
- animate(src)
- ..(target, range, speed = 3, thrower)
-*/
-
-// none of these are working properly in testing which is something you absolutely hate to see
-/*
-/obj/item/melee/energy/hfmachete/throw_at_old(atom/target, range, speed, thrower)
- playsound(src, get_sfx("machete_throw"), 30, 0)
- . = ..()
-
-/obj/item/melee/energy/hfmachete/throw_impact(atom/hit_atom, speed)
- if(isturf(hit_atom))
- for(var/mob/M in hit_atom)
- playsound(M, get_sfx("machete_throw_hit"), 60, 0)
- ..()
-
-/obj/item/melee/energy/hfmachete/attack(mob/M, mob/living/user)
- playsound(M, get_sfx("machete_hit"), 50, 0)
- ..()
-*/
-/*
-/obj/item/melee/energy/hfmachete/proc/mob_moved(atom/movable/mover)
- if(iscarbon(mover) && active)
- for(var/obj/effect/plantsegment/P in range(mover,0))
- qdel(P)
-
-/obj/item/melee/energy/hfmachete/attackby(obj/item/W, mob/living/user)
- ..()
- if(istype(W, /obj/item/melee/energy/hfmachete))
- to_chat(user, "You combine the two [W] together, making a single scissor-bladed weapon! You feel fucking invincible!")
- qdel(W)
- W = null
- qdel(src)
- var/B = new /obj/item/bloodlust(user.loc)
- user.put_in_hands(B)
- // blust one day lads.
-*/
-
-/obj/item/melee/energy/sword/imperial
- name = "energy gladius"
- desc = "A broad, short energy blade. You'll be glad to have this in a fight."
- icon_state = "sword0"
- icon = 'icons/obj/weapons_vr.dmi'
- item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi')
-
-/obj/item/melee/energy/sword/imperial/activate(mob/living/user)
- ..()
- icon_state = "sword1"
diff --git a/code/game/objects/items/weapons/melee/melee.dm b/code/game/objects/items/weapons/melee/melee.dm
deleted file mode 100644
index 0856d665d166..000000000000
--- a/code/game/objects/items/weapons/melee/melee.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-/obj/item/melee
- icon = 'icons/obj/weapons.dmi'
- attack_sound = "swing_hit"
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm
deleted file mode 100644
index b036965c9baf..000000000000
--- a/code/game/objects/items/weapons/shields.dm
+++ /dev/null
@@ -1,494 +0,0 @@
-//** Shield Helpers
-//These are shared by various items that have shield-like behaviour
-
-//bad_arc is the ABSOLUTE arc of directions from which we cannot block. If you want to fix it to e.g. the user's facing you will need to rotate the dirs yourself.
-/proc/check_shield_arc(mob/user, var/bad_arc, atom/damage_source = null, mob/attacker = null)
- //check attack direction
- var/attack_dir = 0 //direction from the user to the source of the attack
- if(istype(damage_source, /obj/projectile))
- var/obj/projectile/P = damage_source
- attack_dir = get_dir(get_turf(user), P.starting)
- else if(attacker)
- attack_dir = get_dir(get_turf(user), get_turf(attacker))
- else if(damage_source)
- attack_dir = get_dir(get_turf(user), get_turf(damage_source))
-
- if(!(attack_dir && (attack_dir & bad_arc)))
- return 1
- return 0
-
-/proc/default_parry_check(mob/user, mob/attacker, atom/damage_source)
- //parry only melee attacks
- if(istype(damage_source, /obj/projectile) || (attacker && get_dist(user, attacker) > 1) || user.incapacitated())
- return 0
-
- //block as long as they are not directly behind us
- var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block
- if(!check_shield_arc(user, bad_arc, damage_source, attacker))
- return 0
-
- return 1
-
-/obj/item/proc/unique_parry_check(mob/user, mob/attacker, atom/damage_source) // An overrideable version of the above proc.
- return default_parry_check(user, attacker, damage_source)
-
-/obj/item/shield
- name = "shield"
- var/base_block_chance = 50
- preserve_item = 1
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
-
-/obj/item/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(user.incapacitated())
- return 0
-
- //block as long as they are not directly behind us
- var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block
- if(check_shield_arc(user, bad_arc, damage_source, attacker))
- if(prob(get_block_chance(user, damage, damage_source, attacker)))
- user.visible_message("\The [user] blocks [attack_text] with \the [src]!")
- return 1
- return 0
-
-/obj/item/shield/proc/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null)
- return base_block_chance
-
-/obj/item/shield/riot
- name = "riot shield"
- desc = "A shield adept for close quarters engagement. It's also capable of protecting from less powerful projectiles."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "riot"
- slot_flags = SLOT_BACK
- damage_force = 5.0
- throw_force = 5.0
- throw_speed = 1
- throw_range = 4
- w_class = WEIGHT_CLASS_BULKY
- origin_tech = list(TECH_MATERIAL = 2)
- materials_base = list(MAT_GLASS = 7500, MAT_STEEL = 1000)
- attack_verb = list("shoved", "bashed")
- worth_intrinsic = 300
- var/cooldown = 0 //shield bash cooldown. based on world.time
-
-/obj/item/shield/riot/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(user.incapacitated())
- return 0
-
- //block as long as they are not directly behind us
- var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block
- if(check_shield_arc(user, bad_arc, damage_source, attacker))
- if(prob(get_block_chance(user, damage, damage_source, attacker)))
- //At this point, we succeeded in our roll for a block attempt, however these kinds of shields struggle to stand up
- //to strong bullets and lasers. They still do fine to pistol rounds of all kinds, however.
- if(istype(damage_source, /obj/projectile))
- var/obj/projectile/P = damage_source
- if((is_sharp(P) && P.armor_penetration >= 10) || istype(P, /obj/projectile/beam))
- //If we're at this point, the bullet/beam is going to go through the shield, however it will hit for less damage.
- //Bullets get slowed down, while beams are diffused as they hit the shield, so these shields are not /completely/
- //useless. Extremely penetrating projectiles will go through the shield without less damage.
- user.visible_message("\The [user]'s [src.name] is pierced by [attack_text]!")
- if(P.armor_penetration < 30) //PTR bullets and x-rays will bypass this entirely.
- P.damage = P.damage / 2
- return 0
- //Otherwise, if we're here, we're gonna stop the attack entirely.
- user.visible_message("\The [user] blocks [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1)
- return 1
- return 0
-
-/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/melee/baton))
- if(cooldown < world.time - 25)
- user.visible_message("[user] bashes [src] with [W]!")
- playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1)
- cooldown = world.time
- else
- ..()
-
-/obj/item/shield/riot/flash
- name = "strobe shield"
- desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs."
- icon_state = "flashshield"
- item_state = "flashshield"
- var/obj/item/flash/embedded_flash
- var/flashfail = 0
-
-/obj/item/shield/riot/flash/Initialize(mapload)
- . = ..()
- embedded_flash = new(src)
-
-/obj/item/shield/riot/flash/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
- if(user.a_intent == INTENT_HARM)
- return ..()
- return embedded_flash.attack_mob(arglist(args))
-
-/obj/item/shield/riot/flash/attack_self(mob/user)
- . = ..()
- if(.)
- return
- . = embedded_flash.attack_self(user)
- update_icon()
-
-/obj/item/shield/riot/flash/handle_shield(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
- . = ..()
- if (. && damage && !embedded_flash.broken)
- embedded_flash.melee_interaction_chain()
- update_icon()
-
-/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user)
- if(istype(W, /obj/item/flash))
- var/obj/item/flash/flash = W
- if(flashfail)
- to_chat(user, "No sense replacing it with a broken bulb!")
- return
- else
- to_chat(user, "You begin to replace the bulb...")
- if(do_after(user, 20, target = user))
- if(flashfail || !flash || QDELETED(flash))
- return
- playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
- qdel(embedded_flash)
- embedded_flash = flash
- flash.forceMove(src)
- update_icon()
- return
- ..()
-
-/obj/item/shield/riot/flash/emp_act(severity)
- . = ..()
- embedded_flash.emp_act(severity)
- update_icon()
-
-/obj/item/shield/riot/flash/update_icon_state()
- . = ..()
- if(!embedded_flash || embedded_flash.broken)
- icon_state = "riot"
- item_state = "riot"
- else
- icon_state = "flashshield"
- item_state = "flashshield"
-
-/obj/item/shield/riot/flash/examine(mob/user, dist)
- . = ..()
- if (embedded_flash?.broken)
- . += "The mounted bulb has burnt out. You can try replacing it with a new one."
-
-/obj/item/shield/makeshift
- name = "metal shield"
- desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather, making it unwieldy."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "makeshift_shield"
- item_state = "metal"
- slot_flags = null
- damage_force = 10
- throw_force = 7
-
-/obj/item/shield/riot/tower
- name = "tower shield"
- desc = "An immense tower shield. Designed to ensure maximum protection to the user, at the expense of mobility."
- item_state = "metal"
- icon_state = "metal"
- damage_force = 16
- encumbrance = ITEM_ENCUMBRANCE_SHIELD_TOWER
- throw_force = 15 //Massive piece of metal
- w_class = WEIGHT_CLASS_HUGE
-
-/obj/item/shield/riot/tower/swat
- name = "swat shield"
-
-/* I don't know if I really want this in the game. I DO want the code though.
-/obj/item/shield/riot/implant
- name = "hardlight shield implant"
- desc = "A hardlight plane of force projected from the implant. While it is capable of withstanding immense amounts of abuse, it will eventually overload from sustained impacts, especially against energy attacks. Recharges while retracted."
- item_state = "holoshield"
- icon_state = "holoshield"
- slowdown = 1
- shield_flags = SHIELD_FLAGS_DEFAULT
- integrity_max = 100
- obj_integrity = 100
- can_shatter = FALSE
- clothing_flags = ITEM_CAN_BLOCK
- shield_flags = SHIELD_FLAGS_DEFAULT | SHIELD_KINETIC_STRONG | SHIELD_DISABLER_DISRUPTED
- var/recharge_timerid
- var/recharge_delay = 15 SECONDS
-
-/// Entirely overriden take_damage. This shouldn't exist outside of an implant (other than maybe christmas).
-/obj/item/shield/riot/implant/take_damage_legacy(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, armour_penetration = 0)
- obj_integrity -= damage_amount
- if(obj_integrity < 0)
- obj_integrity = 0
- if(obj_integrity == 0)
- if(ismob(loc))
- var/mob/living/L = loc
- playsound(src, /datum/soundbyte/grouped/sparks, 100, TRUE)
- L.visible_message("[src] overloads from the damage sustained!")
- L.dropItemToGround(src) //implant component catch hook will grab it.
-
-/obj/item/shield/riot/implant/Moved()
- . = ..()
- if(istype(loc, /obj/item/organ/cyberimp/arm/shield))
- recharge_timerid = addtimer(CALLBACK(src, PROC_REF(recharge)), recharge_delay, flags = TIMER_STOPPABLE)
- else //extending
- if(recharge_timerid)
- deltimer(recharge_timerid)
- recharge_timerid = null
-
-/obj/item/shield/riot/implant/proc/recharge()
- if(obj_integrity == integrity_max)
- return
- obj_integrity = integrity_max
- if(ismob(loc.loc)) //cyberimplant.user
- to_chat(loc, "[src] has recharged its reinforcement matrix and is ready for use!")
-*/
-
-/obj/item/shield/riot/energy_proof
- name = "energy resistant shield"
- desc = "An ablative shield designed to absorb and disperse energy attacks. This comes at significant cost to its ability to withstand ballistics and kinetics, breaking apart easily."
- icon_state = "riot_laser"
-
-/obj/item/shield/riot/kinetic_proof
- name = "kinetic resistant shield"
- desc = "A polymer and ceramic shield designed to absorb ballistic projectiles and kinetic force. It doesn't do very well into energy attacks, especially from weapons that inflict burns."
- icon_state = "riot_bullet"
-
-//Exotics/Costume Shields
-/obj/item/shield/riot/roman
- name = "scutum"
- desc = "A replica shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "roman_shield"
- slot_flags = SLOT_BACK
- materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000)
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
-
-/obj/item/shield/riot/buckler
- name = "buckler"
- desc = "A wrist mounted round shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "buckler"
- slot_flags = SLOT_BACK | SLOT_BELT
- materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000)
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
-
-/*
- * Energy Shield
- */
-
-/obj/item/shield/energy
- name = "energy combat shield"
- desc = "A shield capable of stopping most projectile and melee attacks. It can be retracted, expanded, and stored anywhere."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "eshield"
- item_state = "eshield"
- slot_flags = SLOT_EARS
- atom_flags = NOCONDUCT
- damage_force = 3.0
- throw_force = 5.0
- throw_speed = 1
- throw_range = 4
- w_class = WEIGHT_CLASS_SMALL
- var/lrange = 1.5
- var/lpower = 1.5
- var/lcolor = "#006AFF"
- origin_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_ILLEGAL = 4)
- attack_verb = list("shoved", "bashed")
- var/active = 0
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
- worth_intrinsic = 500 // op as balls
-
-/obj/item/shield/energy/handle_shield(mob/user)
- if(!active)
- return 0 //turn it on first!
- . = ..()
-
- if(.)
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
-
-/obj/item/shield/energy/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null)
- if(istype(damage_source, /obj/projectile))
- var/obj/projectile/P = damage_source
- if((is_sharp(P) && damage > 10) || istype(P, /obj/projectile/beam))
- return (base_block_chance - round(damage / 3)) //block bullets and beams using the old block chance
- return base_block_chance
-
-/obj/item/shield/energy/attack_self(mob/user)
- . = ..()
- if(.)
- return
- if ((MUTATION_CLUMSY in user.mutations) && prob(50))
- to_chat(user, "You beat yourself in the head with [src].")
- var/mob/living/carbon/human/H = ishuman(user)? user : null
- H?.take_random_targeted_damage(brute = 5)
- active = !active
- if (active)
- damage_force = 10
- update_icon()
- set_weight_class(WEIGHT_CLASS_BULKY)
- slot_flags = null
- playsound(user, 'sound/weapons/saberon.ogg', 50, 1)
- to_chat(user, "\The [src] is now active.")
-
- else
- damage_force = 3
- update_icon()
- set_weight_class(WEIGHT_CLASS_TINY)
- slot_flags = SLOT_EARS
- playsound(user, 'sound/weapons/saberoff.ogg', 50, 1)
- to_chat(user, "\The [src] can now be concealed.")
-
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
- add_fingerprint(user)
- return
-
-/obj/item/shield/energy/update_icon()
- var/mutable_appearance/blade_overlay = mutable_appearance(icon, "[icon_state]_blade")
- if(lcolor)
- blade_overlay.color = lcolor
- cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other
- if(active)
- add_overlay(blade_overlay)
- item_state = "[icon_state]_blade"
- set_light(lrange, lpower, lcolor)
- else
- set_light(0)
- item_state = "[icon_state]"
-
-/obj/item/shield/energy/AltClick(mob/living/user)
- if(!in_range(src, user)) //Basic checks to prevent abuse
- return
- if(user.incapacitated() || !istype(user))
- to_chat(user, "You can't do that right now!")
- return
- if(alert("Are you sure you want to recolor your shield?", "Confirm Recolor", "Yes", "No") == "Yes")
- var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null
- if(energy_color_input)
- lcolor = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1)
- update_icon()
-
-/obj/item/shield/energy/examine(mob/user, dist)
- . = ..()
- . += "Alt-click to recolor it."
-
-/obj/item/shield/riot/tele
- name = "telescopic shield"
- desc = "An advanced riot shield made of lightweight materials that collapses for easy storage."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "teleriot0"
- slot_flags = null
- damage_force = 3
- throw_force = 3
- throw_speed = 3
- throw_range = 4
- w_class = WEIGHT_CLASS_NORMAL
- var/active = 0
-/*
-/obj/item/shield/energy/IsShield()
- if(active)
- return 1
- else
- return 0
-*/
-/obj/item/shield/riot/tele/attack_self(mob/user)
- . = ..()
- if(.)
- return
- active = !active
- icon_state = "teleriot[active]"
- playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1)
-
- if(active)
- damage_force = 8
- throw_force = 5
- throw_speed = 2
- set_weight_class(WEIGHT_CLASS_BULKY)
- slot_flags = SLOT_BACK
- to_chat(user, "You extend \the [src].")
- else
- damage_force = 3
- throw_force = 3
- throw_speed = 3
- set_weight_class(WEIGHT_CLASS_NORMAL)
- slot_flags = null
- to_chat(user, "[src] can now be concealed.")
-
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
- add_fingerprint(user)
- return
-
-/obj/item/shield/energy/imperial
- name = "energy scutum"
- desc = "It's really easy to mispronounce the name of this shield if you've only read it in books."
- icon = 'icons/obj/weapons_vr.dmi'
- icon_state = "eshield0" // eshield1 for expanded
- item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi')
-
-/obj/item/shield/fluff/wolfgirlshield
- name = "Autumn Shield"
- desc = "A shiny silvery shield with a large red leaf symbol in the center."
- icon = 'icons/obj/weapons_vr.dmi'
- icon_state = "wolfgirlshield"
- slot_flags = SLOT_BACK | SLOT_OCLOTHING
- damage_force = 5.0
- throw_force = 5.0
- throw_speed = 2
- throw_range = 6
- item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', SLOT_ID_BACK = 'icons/vore/custom_items_vr.dmi', SLOT_ID_SUIT = 'icons/vore/custom_items_vr.dmi')
- attack_verb = list("shoved", "bashed")
- var/cooldown = 0 //shield bash cooldown. based on world.time
- allowed = list(/obj/item/melee/fluffstuff/wolfgirlsword)
-
-/obj/item/shield/fluff/roman
- name = "replica scutum"
- desc = "A replica shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "roman_shield"
- slot_flags = SLOT_BACK
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
- damage_force = 5.0
- throw_force = 5.0
- throw_speed = 2
- throw_range = 6
-
-//Foam Shield
-/obj/item/shield/riot/foam
- name = "foam riot shield"
- desc = "A shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more."
- icon = 'icons/obj/weapons.dmi'
- icon_state = "foamriot"
- slot_flags = SLOT_BACK
- base_block_chance = 5
- damage_force = 0
- throw_force = 0
- throw_speed = 2
- throw_range = 6
- materials_base = list(MAT_PLASTIC = 7500, "foam" = 1000)
- item_icons = list(
- SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi',
- SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi',
- )
diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm
index dd4676495514..b5c7a599daa3 100644
--- a/code/game/objects/items/weapons/swords_axes_etc.dm
+++ b/code/game/objects/items/weapons/swords_axes_etc.dm
@@ -38,7 +38,9 @@
icon_state = "tonfa"
item_state = "tonfa"
atom_flags = NOBLOODY
- defend_chance = 15
+ passive_parry = /datum/passive_parry{
+ parry_chance_melee = 15;
+ }
//Telescopic baton
/obj/item/melee/telebaton
@@ -145,12 +147,15 @@
)
item_state = "armblade"
damage_force = 15 // same damage_force as a drill
- defend_chance = 20 // did you know melee weapons have a default 5% chance to block frontal melee?
sharp = TRUE
edge = TRUE
var/SA_bonus_damage = 35 // 50 total against animals and aberrations.
var/SA_vulnerability = MOB_CLASS_ANIMAL | MOB_CLASS_ABERRATION
+ passive_parry = /datum/passive_parry{
+ parry_chance_melee = 20;
+ }
+
/obj/item/melee/disruptor/afterattack(atom/target, mob/user, clickchain_flags, list/params)
. = ..()
if(isliving(target))
diff --git a/code/game/objects/items/weapons/tanks/tank.dm b/code/game/objects/items/weapons/tanks/tank.dm
index 4a3291e1b993..24bb915b4fa1 100644
--- a/code/game/objects/items/weapons/tanks/tank.dm
+++ b/code/game/objects/items/weapons/tanks/tank.dm
@@ -455,7 +455,19 @@ var/list/global/tank_gauge_cache = list()
var/num_fragments = round(rand(8,10) * sqrt(strength * mult))
- src.fragmentate(T, num_fragments, rand(5) + 7, list(/obj/projectile/bullet/pellet/fragment/tank/small = 7,/obj/projectile/bullet/pellet/fragment/tank = 2,/obj/projectile/bullet/pellet/fragment/strong = 1))
+ shrapnel_explosion(
+ num_fragments,
+ rand(5, 7),
+ list(
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank,
+ /obj/projectile/bullet/pellet/fragment/tank,
+ /obj/projectile/bullet/pellet/fragment/strong = 1,
+ ),
+ )
if(istype(loc, /obj/item/transfer_valve))
var/obj/item/transfer_valve/TTV = loc
@@ -482,13 +494,25 @@ var/list/global/tank_gauge_cache = list()
visible_message("[icon2html(thing = src, target = world)] \The [src] flies apart!", "You hear a bang!")
T.hotspot_expose(air_contents.temperature, 70, 1)
-
var/strength = 1+((pressure-TANK_LEAK_PRESSURE)/TANK_FRAGMENT_SCALE)
var/mult = (air_contents.total_moles**2/3)/((29*0.64) **2/3) //tanks appear to be experiencing a reduction on scale of about 0.64 total moles
var/num_fragments = round(rand(6,8) * sqrt(strength * mult)) //Less chunks, but bigger
- src.fragmentate(T, num_fragments, 7, list(/obj/projectile/bullet/pellet/fragment/tank/small = 1,/obj/projectile/bullet/pellet/fragment/tank = 5,/obj/projectile/bullet/pellet/fragment/strong = 4))
+
+ shrapnel_explosion(
+ num_fragments,
+ rand(5, 7),
+ list(
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank/small,
+ /obj/projectile/bullet/pellet/fragment/tank,
+ /obj/projectile/bullet/pellet/fragment/tank,
+ /obj/projectile/bullet/pellet/fragment/strong = 1,
+ ),
+ )
if(istype(loc, /obj/item/transfer_valve))
var/obj/item/transfer_valve/TTV = loc
diff --git a/code/game/objects/obj-construction.dm b/code/game/objects/obj-construction.dm
new file mode 100644
index 000000000000..6b1ec1001e85
--- /dev/null
+++ b/code/game/objects/obj-construction.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Deconstruction *//
+
+/obj/drop_products(method, atom/where)
+ . = ..()
+ if(obj_storage?.drop_on_deconstruction_methods & method)
+ obj_storage.drop_everything_at(where)
diff --git a/code/game/objects/obj-defense.dm b/code/game/objects/obj-defense.dm
new file mode 100644
index 000000000000..4bbfcff3a177
--- /dev/null
+++ b/code/game/objects/obj-defense.dm
@@ -0,0 +1,118 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/obj/ex_act(power, dir, datum/automata/wave/explosion/E)
+ . = ..()
+ // todo: wave explosions
+ // no named arguments for speed reasons
+ run_damage_instance(power * (1 / 2.5) * (0.01 * rand(80, 120)), BRUTE, null, ARMOR_BOMB)
+
+/obj/legacy_ex_act(severity, target)
+ . = ..()
+ // todo: wave explosions
+ // no named arguments for speed reasons
+ run_damage_instance(global._legacy_ex_atom_damage[severity] * (0.01 * rand(80, 120)), BRUTE, null, ARMOR_BOMB)
+
+/obj/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain)
+ var/shieldcall_returns = atom_shieldcall_handle_item_melee(weapon, clickchain, FALSE, NONE)
+ if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ return CLICKCHAIN_FULL_BLOCKED
+ // todo: maybe the item side should handle this?
+ run_damage_instance(
+ weapon.damage_force * (clickchain ? clickchain.damage_multiplier : 1),
+ weapon.damtype,
+ weapon.damage_tier,
+ weapon.damage_flag,
+ weapon.damage_mode,
+ ATTACK_TYPE_MELEE,
+ weapon,
+ NONE,
+ target_zone,
+ null,
+ null,
+ )
+ return NONE
+
+/obj/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain)
+ var/shieldcall_returns = atom_shieldcall_handle_unarmed_melee(style, clickchain, FALSE, NONE)
+ if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ 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.damage_type,
+ style.damage_tier,
+ style.damage_flag,
+ style.damage_mode,
+ ATTACK_TYPE_UNARMED,
+ style,
+ NONE,
+ target_zone,
+ null,
+ null,
+ )
+ return NONE
+
+/obj/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(!(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE)))
+ // todo: maybe the projectile side should handle this?
+ run_damage_instance(
+ proj.get_structure_damage() * bullet_act_args[BULLET_ACT_ARG_EFFICIENCY],
+ proj.damage_type,
+ proj.damage_tier,
+ proj.damage_flag,
+ proj.damage_mode,
+ ATTACK_TYPE_PROJECTILE,
+ proj,
+ NONE,
+ bullet_act_args[BULLET_ACT_ARG_ZONE],
+ null,
+ null,
+ )
+ if(QDELETED(src))
+ impact_flags |= PROJECTILE_IMPACT_TARGET_DELETED
+ return ..()
+
+/obj/throw_impacted(atom/movable/AM, datum/thrownthing/TT)
+ . = ..()
+ if(TT.throw_flags & THROW_AT_IS_GENTLE)
+ return
+ // todo: /atom/movable/proc/throw_impact_attack(atom/target)
+ if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs)
+ return
+ if(isitem(AM))
+ var/obj/item/I = AM
+ inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM)
+ else
+ inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM)
+ // if we got destroyed
+ if(QDELETED(src) && (obj_flags & OBJ_ALLOW_THROW_THROUGH))
+ . |= COMPONENT_THROW_HIT_PIERCE
+
+/obj/blob_act(obj/structure/blob/blob)
+ . = ..()
+ inflict_atom_damage(100, damage_flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE)
+
+/obj/hitsound_melee(obj/item/I)
+ if(!isnull(material_primary))
+ var/datum/material/primary = get_primary_material()
+ . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute
+ if(!isnull(.))
+ return
+ return ..()
+
+/obj/hitsound_throwhit(obj/item/I)
+ if(!isnull(material_primary))
+ var/datum/material/primary = get_primary_material()
+ . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute
+ if(!isnull(.))
+ return
+ return ..()
+
+/obj/hitsound_unarmed(mob/M, datum/unarmed_attack/style)
+ if(!isnull(material_primary))
+ var/datum/material/primary = get_primary_material()
+ . = style.damage_type == BURN? primary.sound_melee_burn : primary.sound_melee_brute
+ if(!isnull(.))
+ return
+ return ..()
diff --git a/code/game/objects/objs.dm b/code/game/objects/obj.dm
similarity index 100%
rename from code/game/objects/objs.dm
rename to code/game/objects/obj.dm
diff --git a/code/game/objects/random/mapping.dm b/code/game/objects/random/mapping.dm
index d6e34c541f6a..dfda3c019e94 100644
--- a/code/game/objects/random/mapping.dm
+++ b/code/game/objects/random/mapping.dm
@@ -405,11 +405,11 @@
// /obj/item/archaeological_find
//),
prob(1);list(
- /obj/item/melee/energy/sword,
- /obj/item/melee/energy/sword,
- /obj/item/melee/energy/sword,
- /obj/item/shield/energy,
- /obj/item/shield/energy,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/shield/transforming/energy,
+ /obj/item/shield/transforming/energy,
/obj/structure/closet/crate/science
),
prob(1);list(
diff --git a/code/game/objects/random/misc.dm b/code/game/objects/random/misc.dm
index 6a874a3abaed..a7ec6ec77a90 100644
--- a/code/game/objects/random/misc.dm
+++ b/code/game/objects/random/misc.dm
@@ -811,7 +811,7 @@
prob(8);/obj/item/gun/energy/gun/eluger,
prob(8);/obj/item/gun/energy/xray,
prob(8);/obj/item/gun/ballistic/automatic/c20r,
- prob(8);/obj/item/melee/energy/sword,
+ prob(8);/obj/item/melee/transforming/energy/sword,
prob(8);/obj/item/gun/ballistic/derringer,
prob(8);/obj/item/gun/ballistic/konigin,
prob(8);/obj/item/gun/ballistic/revolver/lemat,
@@ -835,7 +835,7 @@
prob(4);/obj/item/gun/ballistic/deagle,
prob(4);/obj/item/gun/ballistic/deagle/taj,
prob(4);/obj/item/material/knife/tacknife/combatknife,
- prob(4);/obj/item/melee/energy/sword,
+ prob(4);/obj/item/melee/transforming/energy/sword,
prob(2);/obj/item/gun/ballistic/automatic/mini_uzi,
prob(2);/obj/item/gun/ballistic/automatic/mini_uzi/taj,
prob(4);/obj/item/gun/ballistic/automatic/wt274,
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index e5cfe97fa403..f34b8a2dfba4 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -194,7 +194,7 @@
if(6 to 50)
inflict_atom_damage(
rand(10, 20),
- flag = ARMOR_MELEE,
+ damage_flag = ARMOR_MELEE,
)
visible_message("The planks creak and groan as they're crossed.")
if(51 to 100)
diff --git a/code/game/objects/structures/cliff.dm b/code/game/objects/structures/cliff.dm
index 8dae834f31c5..f649caacdfc3 100644
--- a/code/game/objects/structures/cliff.dm
+++ b/code/game/objects/structures/cliff.dm
@@ -141,13 +141,14 @@ two tiles on initialization, and which way a cliff is facing may change during m
return ..()
// Projectiles and objects flying 'upward' have a chance to hit the cliff instead, wasting the shot.
- else if(istype(mover, /obj))
- var/obj/O = mover
- if(check_shield_arc(src, dir, O)) // This is actually for mobs but it will work for our purposes as well.
+ else if(istype(mover, /obj/projectile) || mover.throwing)
+ if(get_dir(mover, src) & dir)
if(prob(uphill_penalty / (1 + is_double_cliff) )) // Firing upwards facing NORTH means it will likely have to pass through two cliffs, so the chance is halved.
return FALSE
return TRUE
+ return ..()
+
/obj/structure/cliff/Bumped(atom/A)
if(isliving(A))
var/mob/living/L = A
diff --git a/code/game/objects/structures/crates_lockers/__closet.dm b/code/game/objects/structures/crates_lockers/__closet.dm
index 979cae2af26c..d44188937940 100644
--- a/code/game/objects/structures/crates_lockers/__closet.dm
+++ b/code/game/objects/structures/crates_lockers/__closet.dm
@@ -314,7 +314,7 @@
return
if(!user.attempt_insert_item_for_installation(I, opened? loc : src))
return
- else if(istype(I, /obj/item/melee/energy/blade))
+ else if(istype(I, /obj/item/melee/ninja_energy_blade))
if(emag_act(INFINITY, user, "The locker has been sliced open by [user] with \an [I]!", "You hear metal being sliced and sparks flying."))
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, loc)
diff --git a/code/game/objects/structures/crates_lockers/closets/coffin.dm b/code/game/objects/structures/crates_lockers/closets/coffin.dm
index 4c1e44438b8c..fcf23cf8bdeb 100644
--- a/code/game/objects/structures/crates_lockers/closets/coffin.dm
+++ b/code/game/objects/structures/crates_lockers/closets/coffin.dm
@@ -32,6 +32,7 @@
opened = 1
color = "#c2b29f"
use_old_icon_update = TRUE
+ obj_flags = OBJ_MELEE_TARGETABLE
/obj/structure/closet/grave/attack_hand(mob/user, list/params)
if(opened)
@@ -146,9 +147,6 @@
.=..()
alpha = 255 // Needed because of grave hiding
-/obj/structure/closet/grave/bullet_act(var/obj/projectile/P)
- return PROJECTILE_CONTINUE // It's a hole in the ground, doesn't usually stop or even care about bullets
-
/obj/structure/closet/grave/return_air_for_internal_lifeform(var/mob/living/L)
var/gasid = GAS_ID_CARBON_DIOXIDE
if(ishuman(L))
diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm
index ed1c5f42e035..3f3901f4a740 100644
--- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm
+++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm
@@ -57,7 +57,7 @@
starts_with = list(
/obj/item/clothing/suit/armor/tdome/red = 3,
- /obj/item/melee/energy/sword = 3,
+ /obj/item/melee/transforming/energy/sword = 3,
/obj/item/gun/energy/laser = 3,
/obj/item/melee/baton = 3,
/obj/item/storage/box/flashbangs = 3,
@@ -71,7 +71,7 @@
starts_with = list(
/obj/item/clothing/suit/armor/tdome/green = 3,
- /obj/item/melee/energy/sword = 3,
+ /obj/item/melee/transforming/energy/sword = 3,
/obj/item/gun/energy/laser = 3,
/obj/item/melee/baton = 3,
/obj/item/storage/box/flashbangs = 3,
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
index 24ea0ce03cfe..ad445635d1b4 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
@@ -50,7 +50,7 @@
update_icon()
else
to_chat(user, "Access Denied")
- else if(istype(W, /obj/item/melee/energy/blade))
+ else if(istype(W, /obj/item/melee/ninja_energy_blade))
if(emag_act(INFINITY, user, "The locker has been sliced open by [user] with \an [W]!", "You hear metal being sliced and sparks flying."))
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, loc)
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 dfe4064a21ea..1b53f9f27386 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -97,7 +97,7 @@
/obj/item/clothing/glasses/sunglasses/sechud,
/obj/item/barrier_tape_roll/police,
/obj/item/shield/riot,
- /obj/item/shield/riot/tele,
+ /obj/item/shield/transforming/telescopic,
/obj/item/storage/box/holobadge/hos,
/obj/item/storage/box/firingpins,
/obj/item/clothing/accessory/badge/holo/hos,
@@ -173,7 +173,7 @@
/obj/item/radio/headset/heads/hos/alt,
/obj/item/clothing/accessory/armor/helmetcamera/security,
/obj/item/clothing/accessory/armor/helmetcamera/security/body,
- /obj/item/shield/riot/tele,
+ /obj/item/shield/transforming/telescopic,
/obj/item/storage/box/holobadge/hos,
/obj/item/clothing/accessory/badge/holo/hos,
/obj/item/reagent_containers/spray/pepper,
@@ -500,7 +500,7 @@ GLOBAL_LIST_BOILERPLATE(all_brig_closets, /obj/structure/closet/secure_closet/br
/obj/item/clothing/glasses/sunglasses/sechud,
/obj/item/barrier_tape_roll/police,
/obj/item/shield/riot,
- /obj/item/shield/riot/tele,
+ /obj/item/shield/transforming/telescopic,
/obj/item/storage/box/holobadge/hos,
/obj/item/clothing/accessory/badge/holo/hos,
/obj/item/reagent_containers/spray/pepper,
diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
index e0b788d4508b..513aeb888ce4 100644
--- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm
+++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
@@ -16,7 +16,7 @@
/obj/item/cell/high,
/obj/item/card/id/syndicate,
/obj/item/multitool,
- /obj/item/shield/energy,
+ /obj/item/shield/transforming/energy,
/obj/item/clothing/shoes/magboots)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 5f5d7baf0312..04e971a25f02 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -211,7 +211,7 @@
/obj/structure/closet/crate/secure/attackby(obj/item/W as obj, mob/user as mob)
if(is_type_in_list(W, list(/obj/item/packageWrap, /obj/item/stack/cable_coil, /obj/item/radio/electropack, /obj/item/tool/wirecutters)))
return ..()
- if(istype(W, /obj/item/melee/energy/blade))
+ if(istype(W, /obj/item/melee/ninja_energy_blade))
emag_act(INFINITY, user)
if(!opened)
src.togglelock(user)
@@ -261,17 +261,17 @@
req_access += pick(get_all_station_access())
..()
-/obj/structure/closet/crate/secure/bullet_act(var/obj/projectile/Proj)
- if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN))
- return
+/obj/structure/closet/crate/secure/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(!(proj.damage_type == BRUTE || proj.damage_type == BURN))
+ return ..()
- if(locked && tamper_proof && integrity <= Proj.damage)
+ if(locked && tamper_proof && integrity <= proj.damage)
if(tamper_proof == 2) // Mainly used for events to prevent any chance of opening the box improperly.
visible_message("The anti-tamper mechanism of [src] triggers an explosion!")
var/turf/T = get_turf(src.loc)
explosion(T, 0, 0, 0, 1) // Non-damaging, but it'll alert security.
qdel(src)
- return
+ return impact_flags
var/open_chance = rand(1,5)
switch(open_chance)
if(1)
@@ -287,12 +287,9 @@
qdel(src)
if(5)
visible_message("The anti-tamper mechanism of [src] fails!")
- return
-
- ..()
-
- return
+ return impact_flags
+ return ..()
/obj/structure/closet/crate/plastic
name = "plastic crate"
diff --git a/code/game/objects/structures/curtains.dm b/code/game/objects/structures/curtains.dm
index 8dfecee5121e..b3f82578d8af 100644
--- a/code/game/objects/structures/curtains.dm
+++ b/code/game/objects/structures/curtains.dm
@@ -7,6 +7,10 @@
opacity = 1
density = 0
anchored = TRUE
+ integrity = 40
+ integrity_failure = 30
+ integrity_max = 40
+
var/obj/item/stack/mat = /obj/item/stack/material/plastic
/obj/structure/curtain/open
@@ -15,13 +19,6 @@
layer = 3.3 //3.3 so its above windows, not the same as them. anything below 3.3 puts the curtain beneath the window sprite in current build
opacity = 0
-/obj/structure/curtain/bullet_act(obj/projectile/P, def_zone)
- if(!P.nodamage)
- visible_message("[P] tears [src] down!")
- qdel(src)
- else
- ..(P, def_zone)
-
/obj/structure/curtain/attack_hand(mob/user, list/params)
playsound(get_turf(loc), "rustle", 15, 1, -5)
toggle()
diff --git a/code/game/objects/structures/decorations.dm b/code/game/objects/structures/decorations.dm
index f7c5260e74e9..bcbf68b08f0f 100644
--- a/code/game/objects/structures/decorations.dm
+++ b/code/game/objects/structures/decorations.dm
@@ -3,15 +3,10 @@
desc = "Ornately twisted rope holding up a religious seal."
icon = 'icons/obj/decals.dmi'
icon_state = "shrine_seal"
- layer = 3.3 //3.3 so its above windows, not the same as them. anything below 3.3 puts the curtain beneath the window sprite in current build
- opacity = 0
-
-/obj/structure/shrine_seal/bullet_act(obj/projectile/P, def_zone)
- if(!P.nodamage)
- visible_message("[P] tears [src] down!")
- qdel(src)
- else
- ..(P, def_zone)
+ layer = ABOVE_WINDOW_LAYER
+ integrity = 40
+ integrity_max = 40
+ integrity_failure = 30
/obj/structure/shrine_seal/attackby(obj/item/P, mob/user)
if(P.is_wirecutter())
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index 147f5d25ff76..87795c2998e8 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -324,7 +324,7 @@
// Airlock frames are indestructable, so bullets hitting them would always be stopped.
// To fix this, airlock assemblies will sometimes let bullets pass through, since generally the sprite shows them partially open.
-/obj/structure/door_assembly/bullet_act(var/obj/projectile/P)
- if(prob(40)) // Chance for the frame to let the bullet keep going.
- return PROJECTILE_CONTINUE
+/obj/structure/door_assembly/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(prob(40))
+ return PROJECTILE_IMPACT_PASSTHROUGH
return ..()
diff --git a/code/game/objects/structures/flora/rocks.dm b/code/game/objects/structures/flora/rocks.dm
index 74528dfd6a46..63ec2bbd4ed1 100644
--- a/code/game/objects/structures/flora/rocks.dm
+++ b/code/game/objects/structures/flora/rocks.dm
@@ -38,11 +38,12 @@
return
. = ..()
-/obj/structure/flora/rock/bullet_act(obj/projectile/P, def_zone)
- if(P.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks.
- GetDrilled()
- return
+/obj/structure/flora/rock/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT)
+ return
+ if(proj.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks.
+ GetDrilled()
/obj/structure/flora/rock/proc/GetDrilled()
new outcropdrop(get_turf(src),rand(mindrop,upperdrop))
diff --git a/code/game/objects/structures/flora/trees.dm b/code/game/objects/structures/flora/trees.dm
index 3b6f73af5e2b..75898d9168ce 100644
--- a/code/game/objects/structures/flora/trees.dm
+++ b/code/game/objects/structures/flora/trees.dm
@@ -65,10 +65,10 @@
animate(src, transform=turn(M, shake_animation_degrees * shake_dir), pixel_x=init_px + 2*shake_dir, time=1)
animate(transform=M, pixel_x=init_px, time=6, easing=ELASTIC_EASING)
-/obj/structure/flora/tree/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual)
+/obj/structure/flora/tree/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon)
. = ..()
// ruins some of the wood if you use high power modes or types
- if(. > 5 && ((mode & (DAMAGE_MODE_ABLATING | DAMAGE_MODE_PIERCE | DAMAGE_MODE_SHRED)) || (flag == ARMOR_BOMB)))
+ if(. > 5 && ((damage_mode & (DAMAGE_MODE_ABLATING | DAMAGE_MODE_PIERCE | DAMAGE_MODE_SHRED)) || (damage_flag == ARMOR_BOMB)))
product_amount -= round((. * 0.5) / integrity_max * initial(product_amount))
/obj/structure/flora/tree/atom_break()
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 4d97e12a8f6a..58a1913f12f9 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -43,36 +43,14 @@
return TRUE
return ..()
-/obj/structure/grille/bullet_act(var/obj/projectile/Proj)
- //Flimsy grilles aren't so great at stopping projectiles. However they can absorb some of the impact
- var/damage = Proj.get_structure_damage()
- var/passthrough = 0
-
- if(!damage) return
-
- //20% chance that the grille provides a bit more cover than usual. Support structure for example might take up 20% of the grille's area.
- //If they click on the grille itself then we assume they are aiming at the grille itself and the extra cover behaviour is always used.
- switch(Proj.damage_type)
- if(BRUTE)
- //bullets
- if(Proj.original == src || prob(20))
- Proj.damage *= clamp( Proj.damage/60, 0, 0.5)
- if(prob(max((damage-10)/25, 0))*100)
- passthrough = 1
- else
- Proj.damage *= clamp( Proj.damage/60, 0, 1)
- passthrough = 1
- if(BURN)
- //beams and other projectiles are either blocked completely by grilles or stop half the damage.
- if(!(Proj.original == src || prob(20)))
- Proj.damage *= 0.5
- passthrough = 1
-
- if(passthrough)
- . = PROJECTILE_CONTINUE
- damage = between(0, (damage - Proj.damage)*(Proj.damage_type == BRUTE? 0.4 : 1), 10) //if the bullet passes through then the grille avoids most of the damage
-
- inflict_atom_damage(damage, Proj.damage_tier, Proj.damage_flag, Proj.damage_mode, ATTACK_TYPE_PROJECTILE, Proj)
+/obj/structure/grille/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(proj.original_target == src)
+ impact_flags |= PROJECTILE_IMPACT_TRIVIAL
+ else
+ impact_flags |= PROJECTILE_IMPACT_TRIVIAL | PROJECTILE_IMPACT_PIERCE
+ . = ..()
+ if(impact_flags & PROJECTILE_IMPACT_PIERCE)
+ proj.dampen_on_pierce_experimental(src, 10, ARMOR_TIER_ABOVE)
/obj/structure/grille/attackby(obj/item/W as obj, mob/user as mob)
if(!istype(W))
@@ -120,12 +98,12 @@
WD.update_appearance()
return ..()
-/obj/structure/grille/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult)
+/obj/structure/grille/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain)
if(shock(attacker, 70))
return FALSE
return ..()
-/obj/structure/grille/melee_act(mob/user, obj/item/weapon, target_zone, mult)
+/obj/structure/grille/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain)
if(shock(user, 70, weapon))
return FALSE
return ..()
@@ -174,7 +152,7 @@
/obj/structure/grille/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(!destroyed)
if(exposed_temperature > T0C + 1500)
- inflict_atom_damage(1, flag = ARMOR_FIRE)
+ inflict_atom_damage(1, damage_flag = ARMOR_FIRE)
..()
/obj/structure/grille/proc/is_on_frame()
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm
index a231a24ab6db..cd4858dd972a 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/janicart.dm
@@ -177,6 +177,7 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart)
anchored = 1
density = 1
atom_flags = OPENCONTAINER
+ integrity_flags = INTEGRITY_INDESTRUCTIBLE
//copypaste sorry
var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite
var/obj/item/storage/bag/trash/mybag = null
@@ -279,13 +280,10 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart)
L.pixel_x = -13
L.pixel_y = 7
-/obj/structure/bed/chair/janicart/bullet_act(var/obj/projectile/Proj)
- if(has_buckled_mobs())
- if(prob(85))
- var/mob/living/L = pick(buckled_mobs)
- return L.bullet_act(Proj)
- visible_message("[Proj] ricochets off the [callme]!")
-
+/obj/structure/bed/chair/janicart/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(has_buckled_mobs() && prob(85))
+ return proj.impact_redirect(pick(buckled_mobs), args)
+ return ..()
/obj/item/key
name = "key"
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index df8f872b1056..9e12dc21cec7 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -39,14 +39,13 @@
desc = "Oh no, seven years of bad luck!"
-/obj/structure/mirror/bullet_act(var/obj/projectile/Proj)
-
- if(prob(Proj.get_structure_damage() * 2))
+/obj/structure/mirror/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(prob(proj.get_structure_damage() * 2))
if(!shattered)
shatter()
else if(glass)
playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
- ..()
/obj/structure/mirror/attackby(obj/item/I as obj, mob/user as mob)
if(I.is_wrench())
diff --git a/code/game/objects/structures/props/beam_prism.dm b/code/game/objects/structures/props/beam_prism.dm
index fad0ecd0328f..8a2d8b095fde 100644
--- a/code/game/objects/structures/props/beam_prism.dm
+++ b/code/game/objects/structures/props/beam_prism.dm
@@ -1,5 +1,6 @@
//A series(?) of prisms for PoIs. The base one only works for beams.
+// todo: refactor to /obj/structure/reflector
/obj/structure/prop/prism
name = "prismatic turret"
desc = "A raised, externally powered 'turret'. It seems to have a massive crystal ring around its base."
@@ -12,6 +13,12 @@
layer = 3.1 //Layer over projectiles.
plane = -10 //Layer over projectiles.
+ /// projectile_type's to reflect
+ /// all of these must be on a projectile
+ var/projectile_type = PROJECTILE_TYPE_BEAM | PROJECTILE_TYPE_PHOTONIC
+ /// can't reflect these
+ var/projectile_type_cant = NONE
+
var/rotation_lock = 0 // Can you rotate the prism at all?
var/free_rotate = 1 // Does the prism rotate in any direction, or only in the eight standard compass directions?
var/external_control_lock = 0 // Does the prism only rotate from the controls of an external switch?
@@ -19,8 +26,6 @@
var/compass_directions = list("North" = 0, "South" = 180, "East" = 90, "West" = 270, "Northwest" = 315, "Northeast" = 45, "Southeast" = 135, "Southwest" = 225)
var/interaction_sound = 'sound/mecha/mechmove04.ogg'
- var/redirect_type = /obj/projectile/beam
-
var/dialID = null
var/obj/structure/prop/prismcontrol/remote_dial = null
@@ -119,19 +124,17 @@
else
animate(src, transform = turn(src.transform, rotate_degrees), time = 6)
-/obj/structure/prop/prism/bullet_act(var/obj/projectile/Proj)
- if(istype(Proj, redirect_type))
- if(!silent)
- visible_message("\The [src] redirects \the [Proj]!")
- flick("[initial(icon_state)]+glow", src)
-
- var/new_x = (1 * round(10 * cos(degrees_from_north - 90))) + x //Vectors vectors vectors.
- var/new_y = (-1 * round(10 * sin(degrees_from_north - 90))) + y
- var/turf/curloc = get_turf(src)
-
- Proj.penetrating += 1 // Needed for the beam to get out of the turret.
-
- Proj.redirect(new_x, new_y, curloc, null)
+/obj/structure/prop/prism/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(((proj.projectile_type & projectile_type) != projectile_type) || (proj.projectile_type & projectile_type_cant))
+ return ..()
+ if(!silent)
+ visible_message("\The [src] redirects \the [proj]!")
+ flick("[initial(icon_state)]+glow", src)
+
+ // todo: this is not the right way; visuals are weird and the raycast is :/
+ proj.physics_kick_forwards(16)
+ proj.set_angle(degrees_from_north)
+ return PROJECTILE_IMPACT_REFLECT
/obj/structure/prop/prism/incremental
free_rotate = 0
diff --git a/code/game/objects/structures/props/projectile_lock.dm b/code/game/objects/structures/props/projectile_lock.dm
index 1bd5eaad95da..110d54c74a58 100644
--- a/code/game/objects/structures/props/projectile_lock.dm
+++ b/code/game/objects/structures/props/projectile_lock.dm
@@ -28,9 +28,11 @@
else
icon_state = "[initial(icon_state)]"
+// todo: this is shitcode rework this
/obj/structure/prop/lock/projectile
name = "beam lock"
desc = "An esoteric object that responds to high intensity light."
+ integrity_flags = INTEGRITY_INDESTRUCTIBLE
var/projectile_key = /obj/projectile/beam
var/timed = 0
@@ -39,11 +41,14 @@
interaction_message = "The object remains inert to your touch."
-/obj/structure/prop/lock/projectile/bullet_act(var/obj/projectile/Proj)
- if(!istype(Proj, projectile_key) || timing)
- return
+/obj/structure/prop/lock/projectile/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(!istype(proj, projectile_key))
+ return ..()
- if(istype(Proj, /obj/projectile/beam/heavylaser/cannon) || istype(Proj, /obj/projectile/beam/emitter) || (Proj.damage >= 80 && Proj.damtype == BURN))
+ if(timing)
+ return PROJECTILE_IMPACT_DELETE
+
+ if(istype(proj, /obj/projectile/beam/heavylaser/cannon) || istype(proj, /obj/projectile/beam/emitter) || (proj.damage >= 80 && proj.damtype == BURN))
toggle_lock()
visible_message("\The [src] [enabled ? "disengages" : "engages"] its locking mechanism.")
@@ -51,3 +56,5 @@
timing = 1
spawn(time_limit)
toggle_lock()
+
+ return PROJECTILE_IMPACT_DELETE
diff --git a/code/game/objects/structures/props/puzzledoor.dm b/code/game/objects/structures/props/puzzledoor.dm
index 42416e90c3f2..8f429ac12f7b 100644
--- a/code/game/objects/structures/props/puzzledoor.dm
+++ b/code/game/objects/structures/props/puzzledoor.dm
@@ -26,10 +26,9 @@
return 0
return 1
-/obj/machinery/door/blast/puzzle/bullet_act(var/obj/projectile/Proj)
- if(!istype(Proj, /obj/projectile/test))
- visible_message("\The [src] is completely unaffected by \the [Proj].")
- qdel(Proj) //No piercing. No.
+/obj/machinery/door/blast/puzzle/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ impact_flags &= ~PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH
+ return ..()
/obj/machinery/door/blast/puzzle/legacy_ex_act(severity)
visible_message("\The [src] is completely unaffected by the blast.")
diff --git a/code/game/objects/structures/tables/interactions.dm b/code/game/objects/structures/tables/interactions.dm
index 4f20ef5225db..6d48e296228e 100644
--- a/code/game/objects/structures/tables/interactions.dm
+++ b/code/game/objects/structures/tables/interactions.dm
@@ -33,10 +33,10 @@
return 1
if (get_dist(P.starting, loc) <= 1) //Tables won't help you if people are THIS close
return 1
- if (get_turf(P.original) == cover)
+ if (get_turf(P.original_target) == cover)
var/chance = 20
- if (ismob(P.original))
- var/mob/M = P.original
+ if (ismob(P.original_target))
+ var/mob/M = P.original_target
if (M.lying)
chance += 20 //Lying down lets you catch less bullets
if(flipped==1)
@@ -71,7 +71,7 @@
else
playsound(loc, 'sound/weapons/tablehit1.ogg', 50, 1)
var/turf/old_loc = loc
- inflict_atom_damage(40, flag = ARMOR_MELEE)
+ inflict_atom_damage(40, damage_flag = ARMOR_MELEE)
if(QDELETED(src))
// got broken
visible_message(SPAN_DANGER("[src] shatters under the impact!"))
diff --git a/code/game/objects/structures/target_stake.dm b/code/game/objects/structures/target_stake.dm
index ab9122056f71..136ba2bc219c 100644
--- a/code/game/objects/structures/target_stake.dm
+++ b/code/game/objects/structures/target_stake.dm
@@ -47,8 +47,7 @@
else
return ..()
-/obj/structure/target_stake/bullet_act(obj/projectile/P, def_zone)
+/obj/structure/target_stake/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(pinned_target)
- return pinned_target.bullet_act(P, def_zone)
- else
- return ..()
+ return proj.impact_redirect(pinned_target, args)
+ return ..()
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index b1f77bcbba06..4f5081b13d04 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -291,7 +291,7 @@
/obj/structure/window/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
. = ..()
if (exposed_temperature > maximal_heat)
- inflict_atom_damage(damage_per_fire_tick, flag = ARMOR_FIRE, gradual = TRUE)
+ inflict_atom_damage(damage_per_fire_tick, damage_flag = ARMOR_FIRE, damage_mode = DAMAGE_MODE_GRADUAL)
/obj/structure/window/drop_products(method, atom/where)
. = ..()
diff --git a/code/game/turfs/defense.dm b/code/game/turfs/defense.dm
deleted file mode 100644
index 34a90ac239ac..000000000000
--- a/code/game/turfs/defense.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/turf/break_apart(method)
- ScrapeAway(1, CHANGETURF_INHERIT_AIR)
diff --git a/code/game/turfs/simulated/wall/defense.dm b/code/game/turfs/simulated/wall/defense.dm
deleted file mode 100644
index 8811811f9492..000000000000
--- a/code/game/turfs/simulated/wall/defense.dm
+++ /dev/null
@@ -1,48 +0,0 @@
-/turf/simulated/wall/throw_impacted(atom/movable/AM, datum/thrownthing/TT)
- . = ..()
- if(TT.throw_flags & THROW_AT_IS_GENTLE)
- return
-
- // todo: /atom/movable/proc/throw_impact_attack(atom/target)
- if(isitem(AM))
- var/obj/item/I = AM
- inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), I.damage_tier, I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM)
- else
- inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), MELEE_TIER_LIGHT, ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM)
-
-/turf/simulated/wall/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult)
- // todo: this should just be style.attack(attacker, src)
- inflict_atom_damage(style.get_unarmed_damage(attacker, src), style.damage_tier, style.damage_flag, style.damage_mode, ATTACK_TYPE_UNARMED, attacker)
- return NONE
-
-/turf/simulated/wall/melee_act(mob/user, obj/item/weapon, target_zone, mult)
- inflict_atom_damage(weapon.damage_force, weapon.damage_tier, weapon.damage_flag, weapon.damage_mode, ATTACK_TYPE_MELEE, weapon)
- return NONE
-
-/turf/simulated/wall/bullet_act(var/obj/projectile/Proj)
- if(istype(Proj,/obj/projectile/beam))
- burn(2500)
- else if(istype(Proj,/obj/projectile/ion))
- burn(500)
-
- if(Proj.damage_type == BURN && Proj.damage > 0)
- if(thermite)
- thermitemelt()
-
- if(Proj.ricochet_sounds && prob(15))
- playsound(src, pick(Proj.ricochet_sounds), 100, 1)
-
- inflict_atom_damage(Proj.get_structure_damage(), Proj.damage_tier, Proj.damage_flag, Proj.damage_mode, ATTACK_TYPE_PROJECTILE, Proj)
-
-/turf/simulated/wall/break_apart(method)
- dismantle_wall()
-
-/turf/simulated/wall/damage_integrity(amount, gradual, do_not_break)
- . = ..()
- // todo: optimize
- update_appearance()
-
-/turf/simulated/wall/heal_integrity(amount, gradual, do_not_fix)
- . = ..()
- // todo: optimize
- update_appearance()
diff --git a/code/game/turfs/simulated/wall/wall-construction.dm b/code/game/turfs/simulated/wall/wall-construction.dm
new file mode 100644
index 000000000000..7633c8afaafb
--- /dev/null
+++ b/code/game/turfs/simulated/wall/wall-construction.dm
@@ -0,0 +1,7 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Deconstruction *//
+
+/turf/simulated/wall/break_apart(method)
+ dismantle_wall() // this handles deletion too
diff --git a/code/game/turfs/simulated/wall/wall-damage.dm b/code/game/turfs/simulated/wall/wall-damage.dm
new file mode 100644
index 000000000000..e46661a6902b
--- /dev/null
+++ b/code/game/turfs/simulated/wall/wall-damage.dm
@@ -0,0 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Integrity - Direct Manipulation *//
+
+/turf/simulated/wall/damage_integrity(amount, gradual, do_not_break)
+ . = ..()
+ // todo: optimize
+ update_appearance()
+
+/turf/simulated/wall/heal_integrity(amount, gradual, do_not_fix)
+ . = ..()
+ // todo: optimize
+ update_appearance()
diff --git a/code/game/turfs/simulated/wall/wall-defense.dm b/code/game/turfs/simulated/wall/wall-defense.dm
new file mode 100644
index 000000000000..eeed876c8ccc
--- /dev/null
+++ b/code/game/turfs/simulated/wall/wall-defense.dm
@@ -0,0 +1,97 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/turf/simulated/wall/throw_impacted(atom/movable/AM, datum/thrownthing/TT)
+ . = ..()
+ if(TT.throw_flags & THROW_AT_IS_GENTLE)
+ return
+ // todo: this method of detecting destruction is shitcode but turf refs don't change so qdeleted() won't work
+ var/old_type = type
+ // todo: /atom/movable/proc/throw_impact_attack(atom/target)
+ if(isitem(AM))
+ var/obj/item/I = AM
+ inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), I.damage_tier, I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM)
+ else
+ inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), MELEE_TIER_LIGHT, ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM)
+ // turf refs don't change so while QDELETED() doesn't work this is a close approximate
+ // until we have a better system or we decide to pay some overhead to track with a number or something
+ if(old_type != type)
+ if(!density)
+ . |= COMPONENT_THROW_HIT_PIERCE // :trol:
+ return
+
+/turf/simulated/wall/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain)
+ var/shieldcall_returns = atom_shieldcall_handle_unarmed_melee(style, clickchain, FALSE, NONE)
+ if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ 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_type,
+ style.damage_tier,
+ style.damage_flag,
+ style.damage_mode,
+ ATTACK_TYPE_UNARMED,
+ style,
+ NONE,
+ target_zone,
+ null,
+ null,
+ )
+ return NONE
+
+/turf/simulated/wall/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain)
+ var/shieldcall_returns = atom_shieldcall_handle_item_melee(weapon, clickchain, FALSE, NONE)
+ if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ return CLICKCHAIN_FULL_BLOCKED
+ // todo: maybe the item side should handle this?
+ run_damage_instance(
+ weapon.damage_force * (clickchain ? clickchain.damage_multiplier : 1),
+ weapon.damtype,
+ weapon.damage_tier,
+ weapon.damage_flag,
+ weapon.damage_mode,
+ ATTACK_TYPE_MELEE,
+ weapon,
+ NONE,
+ target_zone,
+ null,
+ null,
+ )
+ return NONE
+
+/turf/simulated/wall/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ // todo: this method of detecting destruction is shitcode but turf refs don't change so qdeleted() won't work
+ var/old_type = type
+ if(!(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE)))
+ // todo: maybe the projectile side should handle this?
+ run_damage_instance(
+ proj.get_structure_damage() * bullet_act_args[BULLET_ACT_ARG_EFFICIENCY],
+ proj.damage_type,
+ proj.damage_tier,
+ proj.damage_flag,
+ proj.damage_mode,
+ ATTACK_TYPE_PROJECTILE,
+ proj,
+ NONE,
+ bullet_act_args[BULLET_ACT_ARG_ZONE],
+ null,
+ null,
+ )
+ // turf refs don't change so while QDELETED() doesn't work this is a close approximate
+ // until we have a better system or we decide to pay some overhead to track with a number or something
+ if(old_type != type)
+ impact_flags |= PROJECTILE_IMPACT_TARGET_DELETED
+ return ..()
+
+ //! legacy code handling
+ if((proj.projectile_type & (PROJECTILE_TYPE_ENERGY | PROJECTILE_TYPE_BEAM)) && !proj.nodamage && proj.damage)
+ burn(2500)
+ if(proj.damage_type == BURN && proj.damage && !proj.nodamage)
+ if(thermite)
+ thermitemelt()
+ if(proj.ricochet_sounds && prob(15))
+ playsound(src, pick(proj.ricochet_sounds), 75, TRUE)
+ //! end
+
+ return ..()
diff --git a/code/game/turfs/simulated/wall/wall.dm b/code/game/turfs/simulated/wall/wall.dm
index c0e7a557702c..2112e988f2f5 100644
--- a/code/game/turfs/simulated/wall/wall.dm
+++ b/code/game/turfs/simulated/wall/wall.dm
@@ -150,7 +150,7 @@
/turf/simulated/wall/adjacent_fire_act(turf/simulated/floor/adj_turf, datum/gas_mixture/adj_air, adj_temp, adj_volume)
burn(adj_temp)
if(adj_temp > material_outer.melting_point)
- inflict_atom_damage(log(RAND_F(0.9, 1.1) * (adj_temp - material_outer.melting_point)), flag = ARMOR_FIRE, gradual = TRUE)
+ inflict_atom_damage(log(RAND_F(0.9, 1.1) * (adj_temp - material_outer.melting_point)), damage_flag = ARMOR_FIRE, damage_mode = DAMAGE_MODE_GRADUAL)
return ..()
@@ -182,11 +182,11 @@
ScrapeAway()
if(2.0)
if(prob(75))
- inflict_atom_damage(rand(150, 250), flag = ARMOR_BOMB)
+ inflict_atom_damage(rand(150, 250), damage_flag = ARMOR_BOMB)
else
dismantle_wall(1,1)
if(3.0)
- inflict_atom_damage(rand(0, 150), flag = ARMOR_BOMB)
+ inflict_atom_damage(rand(0, 150), damage_flag = ARMOR_BOMB)
/turf/simulated/wall/proc/can_melt()
return material_outer?.material_flags & MATERIAL_FLAG_UNMELTABLE
diff --git a/code/game/turfs/simulated/wall/wall_attacks.dm b/code/game/turfs/simulated/wall/wall_attacks.dm
index fcd2707cdfda..9737790e6ac2 100644
--- a/code/game/turfs/simulated/wall/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall/wall_attacks.dm
@@ -170,8 +170,8 @@
thermitemelt(user)
return
- else if( istype(I, /obj/item/melee/energy/blade) )
- var/obj/item/melee/energy/blade/EB = I
+ else if( istype(I, /obj/item/melee/ninja_energy_blade) )
+ var/obj/item/melee/ninja_energy_blade/EB = I
EB.spark_system.start()
to_chat(user, "You slash \the [src] with \the [EB]; the thermite ignites!")
@@ -222,7 +222,7 @@
dismantle_verb = "cutting"
dismantle_sound = I.tool_sound
// cut_delay *= 0.7 // Tools themselves now can shorten the time it takes.
- else if(istype(I,/obj/item/melee/energy/blade))
+ else if(istype(I,/obj/item/melee/ninja_energy_blade))
dismantle_sound = /datum/soundbyte/grouped/sparks
dismantle_verb = "slicing"
cut_delay *= 0.5
diff --git a/code/game/turfs/turf-construction.dm b/code/game/turfs/turf-construction.dm
new file mode 100644
index 000000000000..d98289771d89
--- /dev/null
+++ b/code/game/turfs/turf-construction.dm
@@ -0,0 +1,7 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Deconstruction *//
+
+/turf/break_apart(method)
+ ScrapeAway(1, CHANGETURF_INHERIT_AIR)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index b5b07d730e7e..1ad138f9faef 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1659,7 +1659,7 @@
if(!check_rights(R_FUN,0))
removed_paths += dirty_path
continue
- else if(ispath(path, /obj/item/melee/energy/blade))//Not an item one should be able to spawn./N
+ else if(ispath(path, /obj/item/melee/ninja_energy_blade))//Not an item one should be able to spawn./N
if(!check_rights(R_FUN,0))
removed_paths += dirty_path
continue
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
index b8c9b9ee87aa..62daecd72cc6 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
@@ -1057,8 +1057,8 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
v = SSresearch
if("SSprojectiles")
v = SSprojectiles
- if("SSfastprocess")
- v = SSfastprocess
+ if("SSprocess_5fps")
+ v = SSprocess_5fps
if("SSticker")
v = SSticker
if("SStimer")
diff --git a/code/modules/admin/verbs/smite.dm b/code/modules/admin/verbs/smite.dm
index 6430a74a451a..6163ca1f500b 100644
--- a/code/modules/admin/verbs/smite.dm
+++ b/code/modules/admin/verbs/smite.dm
@@ -130,13 +130,6 @@
to_chat(target,"You've been hit by bluespace artillery!")
log_and_message_admins("[key_name(target)] has been hit by Bluespace Artillery fired by [key_name(user ? user : usr)]")
- var/obj/effect/stop/S
- S = new /obj/effect/stop
- S.victim = target
- S.loc = target.loc
- spawn(20)
- qdel(S)
-
var/turf/simulated/floor/T = get_turf(target)
if(istype(T))
if(prob(80)) T.break_tile_to_plating()
diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm
index b7520338a0e7..ac03d8543b71 100644
--- a/code/modules/admin/view_variables/view_variables.dm
+++ b/code/modules/admin/view_variables/view_variables.dm
@@ -108,7 +108,7 @@
names = sortList(names)
for(var/V in names)
if(D.can_vv_get(V))
- variable_html += D.vv_get_var(V)
+ variable_html += D.vv_get_var(V, TRUE)
if(VVING_A_LIST)
var/list/L = D
for(var/i in 1 to L.len)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 2f95cd1aadf5..48e03c8cbd8b 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -47,7 +47,7 @@
on = TRUE
Rebuild()
update_icon()
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
/obj/item/assembly/infra/proc/turn_off()
if(!on)
@@ -58,7 +58,7 @@
on = FALSE
Rebuild()
update_icon()
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
/obj/item/assembly/infra/update_icon()
cut_overlays()
diff --git a/code/modules/atmospherics/environmental/zas/debug.dm b/code/modules/atmospherics/environmental/zas/debug.dm
index 40ea525478b0..c4c31b9f9d17 100644
--- a/code/modules/atmospherics/environmental/zas/debug.dm
+++ b/code/modules/atmospherics/environmental/zas/debug.dm
@@ -1,11 +1,11 @@
-var/image/assigned = image('icons/testing/Zone.dmi', icon_state = "assigned")
-var/image/created = image('icons/testing/Zone.dmi', icon_state = "created")
-var/image/merged = image('icons/testing/Zone.dmi', icon_state = "merged")
-var/image/invalid_zone = image('icons/testing/Zone.dmi', icon_state = "invalid")
-var/image/air_blocked = image('icons/testing/Zone.dmi', icon_state = "block")
-var/image/zone_blocked = image('icons/testing/Zone.dmi', icon_state = "zoneblock")
-var/image/blocked = image('icons/testing/Zone.dmi', icon_state = "fullblock")
-var/image/mark = image('icons/testing/Zone.dmi', icon_state = "mark")
+GLOBAL_DATUM_INIT(zas_debug_image_assigned, /image, image('icons/testing/Zone.dmi', icon_state = "assigned"))
+GLOBAL_DATUM_INIT(zas_debug_image_created, /image, image('icons/testing/Zone.dmi', icon_state = "created"))
+GLOBAL_DATUM_INIT(zas_debug_image_merged, /image, image('icons/testing/Zone.dmi', icon_state = "merged"))
+GLOBAL_DATUM_INIT(zas_debug_image_invalid_zone, /image, image('icons/testing/Zone.dmi', icon_state = "invalid"))
+GLOBAL_DATUM_INIT(zas_debug_image_air_blocked, /image, image('icons/testing/Zone.dmi', icon_state = "block"))
+GLOBAL_DATUM_INIT(zas_debug_image_zone_blocked, /image, image('icons/testing/Zone.dmi', icon_state = "zoneblock"))
+GLOBAL_DATUM_INIT(zas_debug_image_blocked, /image, image('icons/testing/Zone.dmi', icon_state = "fullblock"))
+GLOBAL_DATUM_INIT(zas_debug_image_mark, /image, image('icons/testing/Zone.dmi', icon_state = "mark"))
/datum/zas_edge/var/dbg_out = 0
diff --git a/code/modules/awaymissions/loot_vr.dm b/code/modules/awaymissions/loot_vr.dm
index 504fd2d144d5..7b50df091c34 100644
--- a/code/modules/awaymissions/loot_vr.dm
+++ b/code/modules/awaymissions/loot_vr.dm
@@ -128,7 +128,7 @@
prob(10);/obj/item/melee/baton,\
prob(10);/obj/item/melee/telebaton,\
prob(10);/obj/item/melee/classic_baton,\
- prob(10);/obj/item/melee/energy/sword,\
+ prob(10);/obj/item/melee/transforming/energy/sword,\
prob(9);/obj/item/gun/ballistic/automatic/wt550/lethal,\
prob(9);/obj/item/gun/ballistic/automatic/pdw,\
prob(9);/obj/item/gun/ballistic/derringer,\
@@ -145,7 +145,7 @@
prob(8);/obj/item/gun/energy/xray,\
prob(8);/obj/item/gun/ballistic/automatic/c20r,\
prob(8);/obj/item/gun/ballistic/automatic/stg,\
- prob(8);/obj/item/melee/energy/sword,\
+ prob(8);/obj/item/melee/transforming/energy/sword,\
/* prob(8);/obj/item/gun/ballistic/automatic/m41a,\ */
prob(7);/obj/item/gun/energy/captain,\
prob(7);/obj/item/gun/energy/sniperrifle,\
diff --git a/code/modules/blob2/blobs/base_blob.dm b/code/modules/blob2/blobs/base_blob.dm
index 19e8ade9f76a..6b44bb0e4828 100644
--- a/code/modules/blob2/blobs/base_blob.dm
+++ b/code/modules/blob2/blobs/base_blob.dm
@@ -250,18 +250,17 @@ var/list/blobs = list()
adjust_integrity_blob(-damage)
return
-/obj/structure/blob/bullet_act(var/obj/projectile/P)
- if(!P)
- return
+/obj/structure/blob/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
- if(istype(P.firer) && P.firer.faction == "blob")
+ if(istype(proj.firer) && proj.firer.faction == "blob")
return
- var/damage = P.get_structure_damage() // So tasers don't hurt the blob.
+ var/damage = proj.get_structure_damage() // So tasers don't hurt the blob.
if(!damage)
return
- switch(P.damage_type)
+ switch(proj.damage_type)
if(BRUTE)
if(overmind)
damage *= overmind.blob_type.brute_multiplier
@@ -270,12 +269,10 @@ var/list/blobs = list()
damage *= overmind.blob_type.burn_multiplier
if(overmind)
- damage = overmind.blob_type.on_received_damage(src, damage, P.damage_type, P.firer)
+ damage = overmind.blob_type.on_received_damage(src, damage, proj.damage_type, proj.firer)
adjust_integrity_blob(-damage)
- return ..()
-
/obj/structure/blob/water_act(amount)
if(overmind)
overmind.blob_type.on_water(src, amount)
@@ -304,4 +301,4 @@ var/list/blobs = list()
qdel(src)
/turf/simulated/wall/blob_act()
- inflict_atom_damage(100, flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE)
+ inflict_atom_damage(100, damage_flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE)
diff --git a/code/modules/clothing/clothing_accessories.dm b/code/modules/clothing/clothing_accessories.dm
index 40fc520530b5..74d5a627e049 100644
--- a/code/modules/clothing/clothing_accessories.dm
+++ b/code/modules/clothing/clothing_accessories.dm
@@ -288,16 +288,6 @@
A.emp_act(severity)
..()
-/obj/item/clothing/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- . = ..()
- if((. == 0) && LAZYLEN(accessories))
- for(var/obj/item/I in accessories)
- var/check = I.handle_shield(user, damage, damage_source, attacker, def_zone, attack_text)
-
- if(check != 0) // Projectiles sometimes use negatives IIRC, 0 is only returned if something is not blocked.
- . = check
- break
-
/obj/item/clothing/strip_menu_options(mob/user)
. = ..()
if(!length(accessories))
diff --git a/code/modules/clothing/gloves/arm_guards.dm b/code/modules/clothing/gloves/arm_guards.dm
index 55642593b120..319237e3d25b 100644
--- a/code/modules/clothing/gloves/arm_guards.dm
+++ b/code/modules/clothing/gloves/arm_guards.dm
@@ -28,14 +28,6 @@
return FALSE
return TRUE
-/obj/item/clothing/gloves/arm_guard/laserproof
- name = "ablative arm guards"
- desc = "These arm guards will protect your hands and arms from energy weapons."
- icon_state = "arm_guards_laser"
- item_state_slots = list(SLOT_ID_RIGHT_HAND = "swat", SLOT_ID_LEFT_HAND = "swat")
- siemens_coefficient = 0.4 //This is worse than the other ablative pieces, to avoid this from becoming the poor warden's insulated gloves.
- armor_type = /datum/armor/station/ablative
-
/obj/item/clothing/gloves/arm_guard/bulletproof
name = "ballistic arm guards"
desc = "These arm guards will protect your hands and arms from ballistic weapons."
diff --git a/code/modules/clothing/sets/armor/ablative.dm b/code/modules/clothing/sets/armor/ablative.dm
new file mode 100644
index 000000000000..f8c39360c514
--- /dev/null
+++ b/code/modules/clothing/sets/armor/ablative.dm
@@ -0,0 +1,64 @@
+// todo: this shouldn't be a signalled shieldcall, as
+/obj/item/clothing/suit/armor/laserproof
+ name = "ablative armor vest"
+ desc = "A vest that excels in protecting the wearer against energy projectiles."
+ icon_state = "armor_reflec"
+ blood_overlay_type = "armor"
+ armor_type = /datum/armor/station/ablative
+ siemens_coefficient = 0.1
+
+/obj/item/clothing/suit/armor/laserproof/equipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/clothing/suit/armor/laserproof/unequipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/clothing/suit/armor/laserproof/proc/shieldcall(mob/defending, list/shieldcall_args, fake_attack)
+ var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ var/def_zone = shieldcall_args[SHIELDCALL_ARG_HIT_ZONE]
+ if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam))
+ var/obj/projectile/P = damage_source
+
+ if(P.reflected) // Can't reflect twice
+ return
+
+ var/reflectchance = 50 - round(shieldcall_args[SHIELDCALL_ARG_DAMAGE]/3)
+ if(!(def_zone in list(BP_TORSO, BP_GROIN)))
+ reflectchance /= 2
+ if(P.starting && prob(reflectchance))
+ visible_message("\The [defending]'s [src.name] reflects [P]!")
+
+ // Find a turf near or on the original location to bounce to
+ var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
+ var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
+ var/turf/curloc = get_turf(defending)
+
+ // redirect the projectile
+ P.legacy_redirect(new_x, new_y, curloc, defending)
+ P.reflected = 1
+ shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_PASSTHROUGH | SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_TERMINATE
+
+/obj/item/clothing/gloves/arm_guard/laserproof
+ name = "ablative arm guards"
+ desc = "These arm guards will protect your hands and arms from energy weapons."
+ icon_state = "arm_guards_laser"
+ item_state_slots = list(SLOT_ID_RIGHT_HAND = "swat", SLOT_ID_LEFT_HAND = "swat")
+ siemens_coefficient = 0.4 //This is worse than the other ablative pieces, to avoid this from becoming the poor warden's insulated gloves.
+ armor_type = /datum/armor/station/ablative
+
+/obj/item/clothing/shoes/leg_guard/laserproof
+ name = "ablative leg guards"
+ desc = "These will protect your legs and feet from energy weapons."
+ icon_state = "leg_guards_laser"
+ item_state_slots = list(SLOT_ID_RIGHT_HAND = "jackboots", SLOT_ID_LEFT_HAND = "jackboots")
+ siemens_coefficient = 0.1
+ armor_type = /datum/armor/station/ablative
diff --git a/code/modules/clothing/shoes/leg_guards.dm b/code/modules/clothing/shoes/leg_guards.dm
index b88fed8ce0a0..e77aa2e7e416 100644
--- a/code/modules/clothing/shoes/leg_guards.dm
+++ b/code/modules/clothing/shoes/leg_guards.dm
@@ -31,14 +31,6 @@
return FALSE
return TRUE
-/obj/item/clothing/shoes/leg_guard/laserproof
- name = "ablative leg guards"
- desc = "These will protect your legs and feet from energy weapons."
- icon_state = "leg_guards_laser"
- item_state_slots = list(SLOT_ID_RIGHT_HAND = "jackboots", SLOT_ID_LEFT_HAND = "jackboots")
- siemens_coefficient = 0.1
- armor_type = /datum/armor/station/ablative
-
/obj/item/clothing/shoes/leg_guard/bulletproof
name = "ballistic leg guards"
desc = "These will protect your legs and feet from ballistic weapons."
diff --git a/code/modules/clothing/spacesuits/alien.dm b/code/modules/clothing/spacesuits/alien.dm
index bf61251514e8..6821a426ccec 100644
--- a/code/modules/clothing/spacesuits/alien.dm
+++ b/code/modules/clothing/spacesuits/alien.dm
@@ -33,7 +33,7 @@
w_class = WEIGHT_CLASS_NORMAL
atom_flags = PHORONGUARD
clothing_flags = CLOTHING_THICK_MATERIAL | CLOTHING_INJECTION_PORT
- allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs,/obj/item/tank)
+ allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs,/obj/item/tank)
armor_type = /datum/armor/vox/space/armored
siemens_coefficient = 0.2
heat_protection_cover = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm
index 51f0fdf74159..313e9fab76c8 100644
--- a/code/modules/clothing/spacesuits/syndi.dm
+++ b/code/modules/clothing/spacesuits/syndi.dm
@@ -11,7 +11,7 @@
icon_state = "syndicate"
desc = "A crimson spacesuit sporting clean lines and durable plating. Robust, reliable, and slightly suspicious."
w_class = WEIGHT_CLASS_NORMAL
- allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs,/obj/item/tank/emergency/oxygen)
+ allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs,/obj/item/tank/emergency/oxygen)
armor_type = /datum/armor/agent/space
siemens_coefficient = 0.6
diff --git a/code/modules/clothing/spacesuits/void/merc.dm b/code/modules/clothing/spacesuits/void/merc.dm
index be3f3ab703db..b9252aa9e2f8 100644
--- a/code/modules/clothing/spacesuits/void/merc.dm
+++ b/code/modules/clothing/spacesuits/void/merc.dm
@@ -20,7 +20,7 @@
weight = ITEM_WEIGHT_VOIDSUIT
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/merc/space
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
/obj/item/clothing/head/helmet/space/void/merc/fire
@@ -38,7 +38,7 @@
desc = "A blackened suit that has had many of its protective plates coated in or replaced with high-grade thermal insulation, to protect against incineration. Property of Gorlex Marauders."
armor_type = /datum/armor/merc/space/ghostrider
max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs,/obj/item/material/twohanded/fireaxe,/obj/item/flamethrower)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs,/obj/item/material/twohanded/fireaxe,/obj/item/flamethrower)
siemens_coefficient = 0.7
//Soviet Void Suit
@@ -91,7 +91,7 @@
item_state_slots = list(SLOT_ID_RIGHT_HAND = "syndie_voidsuit", SLOT_ID_LEFT_HAND = "syndie_voidsuit")
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/merc/space/clown
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
//Four below avalible through cargo
@@ -111,7 +111,7 @@
desc = "One of the few combat-grade suits avalible in the frontier, and the poster-child of Hephaestus Industries. Comes equipped with a wrist-bound oxygen timer."
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/station/secsuit
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
species_restricted = null
helmet_type = /obj/item/clothing/head/helmet/space/void/odst
@@ -131,7 +131,7 @@
desc = "A standard Icarus line suit that has been repourposed to protect from heavier biohazards."
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/exploration/space
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
species_restricted = null
helmet_type = /obj/item/clothing/head/helmet/space/void/odst_med
@@ -151,7 +151,7 @@
desc = "Favoured suit of deep-space engineers, comfortable and comparable to suits avalible to Nanotrasen Engineers. Comes equipped with a wrist-bound oxygen timer."
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/engineering/space
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
species_restricted = null
helmet_type = /obj/item/clothing/head/helmet/space/void/odst_eng
@@ -171,7 +171,7 @@
desc = "Cheaper version of the main Icarus line, often marketed to Frontier settlements. Perfect for Expeditions."
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/exploration/space
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
species_restricted = null
helmet_type = /obj/item/clothing/head/helmet/space/void/odst_exp
@@ -195,7 +195,7 @@
item_state_slots = list(SLOT_ID_RIGHT_HAND = "syndie_voidsuit", SLOT_ID_LEFT_HAND = "syndie_voidsuit")
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/merc/space
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
species_restricted = null
helmet_type = /obj/item/clothing/head/helmet/space/void/odst_necro
@@ -207,6 +207,6 @@
item_state_slots = list(SLOT_ID_RIGHT_HAND = "syndie_voidsuit", SLOT_ID_LEFT_HAND = "syndie_voidsuit")
w_class = WEIGHT_CLASS_NORMAL
armor_type = /datum/armor/merc/space
- allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs)
+ allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs)
siemens_coefficient = 0.6
species_restricted = null
diff --git a/code/modules/clothing/spacesuits/void/xeno/tajara.dm b/code/modules/clothing/spacesuits/void/xeno/tajara.dm
index 44e08971eed1..2c1c2e0bced0 100644
--- a/code/modules/clothing/spacesuits/void/xeno/tajara.dm
+++ b/code/modules/clothing/spacesuits/void/xeno/tajara.dm
@@ -15,7 +15,7 @@
/obj/item/ammo_magazine,
/obj/item/ammo_casing,
/obj/item/melee/baton,
- /obj/item/melee/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
/obj/item/handcuffs
)
species_restricted = list(SPECIES_TAJ)
@@ -46,7 +46,7 @@
/obj/item/ammo_magazine,
/obj/item/ammo_casing,
/obj/item/melee/baton,
- /obj/item/melee/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
/obj/item/handcuffs
)
species_restricted = list(SPECIES_TAJ)
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index 99728ffc838b..5914082a6418 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -79,37 +79,6 @@
item_state_slots = list(SLOT_ID_RIGHT_HAND = "bulletproof_new", SLOT_ID_LEFT_HAND = "bulletproof_new")
blood_overlay_type = "armor"
-/obj/item/clothing/suit/armor/laserproof
- name = "ablative armor vest"
- desc = "A vest that excels in protecting the wearer against energy projectiles."
- icon_state = "armor_reflec"
- blood_overlay_type = "armor"
- armor_type = /datum/armor/station/ablative
- siemens_coefficient = 0.1
-
-/obj/item/clothing/suit/armor/laserproof/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam))
- var/obj/projectile/P = damage_source
-
- if(P.reflected) // Can't reflect twice
- return ..()
-
- var/reflectchance = 40 - round(damage/3)
- if(!(def_zone in list(BP_TORSO, BP_GROIN)))
- reflectchance /= 2
- if(P.starting && prob(reflectchance))
- visible_message("\The [user]'s [src.name] reflects [attack_text]!")
-
- // Find a turf near or on the original location to bounce to
- var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
- var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
- var/turf/curloc = get_turf(user)
-
- // redirect the projectile
- P.redirect(new_x, new_y, curloc, user)
- P.reflected = 1
-
- return PROJECTILE_CONTINUE // complete projectile permutation
/obj/item/clothing/suit/armor/combat
name = "combat vest"
@@ -193,7 +162,22 @@
blood_overlay_type = "armor"
armor_type = /datum/armor/none
-/obj/item/clothing/suit/armor/reactive/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+/obj/item/clothing/suit/armor/reactive/equipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/clothing/suit/armor/reactive/unequipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/clothing/suit/armor/reactive/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack)
if(prob(50))
user.visible_message("The reactive teleport system flings [user] clear of the attack!")
var/list/turfs = new/list()
@@ -211,10 +195,8 @@
spark_system.set_up(5, 0, user.loc)
spark_system.start()
playsound(user.loc, /datum/soundbyte/grouped/sparks, 50, 1)
-
- user.loc = picked
- return PROJECTILE_FORCE_MISS
- return 0
+ user.forceMove(picked)
+ shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_ATTACK_PASSTHROUGH
/obj/item/clothing/suit/armor/reactive/attack_self(mob/user)
. = ..()
@@ -250,6 +232,13 @@
valid_accessory_slots = null
var/block_chance = 20
+/obj/item/clothing/suit/armor/alien/mob_armorcall(mob/defending, list/shieldcall_args, fake_attack)
+ if(prob(block_chance))
+ defending.visible_message(SPAN_DANGER("[src] completely absorbs [RESOLVE_SHIELDCALL_ATTACK_TEXT(shieldcall_args)]!"))
+ shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAGS_FOR_COMPLETE_BLOCK
+ return
+ return ..()
+
/obj/item/clothing/suit/armor/alien/tank
name = "alien protection suit"
desc = "It's really resilient yet lightweight, so it's probably meant to be armor. Strangely enough it seems to have been designed for a humanoid shape."
@@ -260,12 +249,6 @@
armor_type = /datum/armor/alien/heavy
block_chance = 40
-/obj/item/clothing/suit/armor/alien/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(prob(block_chance))
- user.visible_message("\The [src] completely absorbs [attack_text]!")
- return TRUE
- return FALSE
-
//Non-hardsuit ERT armor.
/obj/item/clothing/suit/armor/vest/ert
name = "emergency response team armor"
diff --git a/code/modules/clothing/under/accessories/armor.dm b/code/modules/clothing/under/accessories/armor.dm
index 612445c6eb7b..f42b06b1c0a6 100644
--- a/code/modules/clothing/under/accessories/armor.dm
+++ b/code/modules/clothing/under/accessories/armor.dm
@@ -178,28 +178,44 @@
armor_type = /datum/armor/station/ablative
siemens_coefficient = 0.2
-/obj/item/clothing/accessory/armor/armorplate/ablative/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+/obj/item/clothing/accessory/armor/armorplate/ablative/equipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ // if you're reading this: this is not the right way to do shieldcalls
+ // this is just a lazy implementation
+ // signals have highest priority, this as a piece of armor shouldn't have that.
+ RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall))
+
+/obj/item/clothing/accessory/armor/armorplate/ablative/unequipped(mob/user, slot, flags)
+ . = ..()
+ if(slot == SLOT_ID_HANDS)
+ return
+ UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL)
+
+/obj/item/clothing/accessory/armor/armorplate/ablative/proc/shieldcall(mob/defending, list/shieldcall_args, fake_attack)
+ var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON]
+ var/def_zone = shieldcall_args[SHIELDCALL_ARG_HIT_ZONE]
if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam))
var/obj/projectile/P = damage_source
if(P.reflected)
- return ..()
+ return
- var/reflectchance = 20 - round(damage/3)
+ var/reflectchance = 20 - round(shieldcall_args[SHIELDCALL_ARG_DAMAGE]/3)
if(!(def_zone in list(BP_TORSO, BP_GROIN)))
reflectchance /= 2
if(P.starting && prob(reflectchance))
- visible_message("\The [user]'s [src.name] reflects [attack_text]!")
+ visible_message("\The [defending]'s [src.name] reflects [P]!")
var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
- var/turf/curloc = get_turf(user)
+ var/turf/curloc = get_turf(defending)
- P.redirect(new_x, new_y, curloc, user)
+ P.legacy_redirect(new_x, new_y, curloc, defending)
P.reflected = 1
-
- return PROJECTILE_CONTINUE
+ shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAGS_FOR_PROJECTILE_DEFLECT
//////////////
//Arm guards
diff --git a/code/modules/clothing/under/accessories/holster.dm b/code/modules/clothing/under/accessories/holster.dm
index 4107b17d5459..20d169cc85ea 100644
--- a/code/modules/clothing/under/accessories/holster.dm
+++ b/code/modules/clothing/under/accessories/holster.dm
@@ -157,7 +157,7 @@
desc = "A handsome synthetic leather scabbard with matching belt."
icon_state = "holster_machete"
concealed_holster = 0
- can_hold = list(/obj/item/material/knife/machete, /obj/item/melee/energy/hfmachete, /obj/item/reagent_containers/spray, /obj/item/soap,
+ can_hold = list(/obj/item/material/knife/machete, /obj/item/melee/transforming/hfmachete, /obj/item/reagent_containers/spray, /obj/item/soap,
/obj/item/c_tube, /obj/item/bikehorn)
cant_hold = list(/obj/item/material/knife/machete/armblade)
sound_in = 'sound/effects/holster/sheathin.ogg'
diff --git a/code/modules/examine/descriptions/weapons.dm b/code/modules/examine/descriptions/weapons.dm
index 835d35882dcc..1120bdcfb6ce 100644
--- a/code/modules/examine/descriptions/weapons.dm
+++ b/code/modules/examine/descriptions/weapons.dm
@@ -66,7 +66,7 @@
set to 'harm', you will inflict damage when using it, regardless if it is on or not. Each stun reduces the baton's charge, which can be replenished by \
putting it inside a weapon recharger."
-/obj/item/melee/energy/sword
+/obj/item/melee/transforming/energy/sword
description_antag = "The energy sword is a very strong melee weapon, capable of severing limbs easily, if they are targeted. It can also has a chance \
to block projectiles and melee attacks while it is on and being held. The sword can be toggled on or off by using it in your hand. While it is off, \
it can be concealed in your pocket or bag."
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index e553eba475a4..93f79ff03da4 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -328,7 +328,7 @@ proc/check_panel(mob/M)
return
GLOBAL_LIST_INIT(non_fakeattack_weapons, list(/obj/item/gun/ballistic, /obj/item/ammo_magazine/a357/speedloader,\
- /obj/item/gun/energy/crossbow, /obj/item/melee/energy/sword,\
+ /obj/item/gun/energy/crossbow, /obj/item/melee/transforming/energy/sword,\
/obj/item/storage/box/syndicate, /obj/item/storage/box/emps,\
/obj/item/cartridge/syndicate, /obj/item/clothing/under/chameleon,\
/obj/item/clothing/shoes/syndigaloshes, /obj/item/card/id/syndicate,\
diff --git a/code/modules/food/drinks/bottle.dm b/code/modules/food/drinks/bottle.dm
index ac9f648ac278..606531dcaec3 100644
--- a/code/modules/food/drinks/bottle.dm
+++ b/code/modules/food/drinks/bottle.dm
@@ -162,7 +162,7 @@
if(target_zone == "head" && istype(L, /mob/living/carbon/))
user.visible_message("\The [user] smashes [src] over [L]'s head!")
if(weaken_duration)
- L.apply_effect(min(weaken_duration, 5), WEAKEN, blocked) // Never weaken more than a flash!
+ L.apply_effect(min(weaken_duration, 5), WEAKEN) // Never weaken more than a flash!
else
user.visible_message("\The [user] smashes [src] into [L]!")
diff --git a/code/modules/gamemaster/actions/electrified_door.dm b/code/modules/gamemaster/actions/electrified_door.dm
index e4ba9c2efb34..c1d4ff1ad914 100644
--- a/code/modules/gamemaster/actions/electrified_door.dm
+++ b/code/modules/gamemaster/actions/electrified_door.dm
@@ -63,7 +63,7 @@
if(!chosen_door || !chosen_door.arePowerSystemsOn())
return
chosen_door.visible_message("\The [chosen_door]'s hydraulics detonate!")
- chosen_door.fragmentate(get_turf(chosen_door), rand(5, 10), rand(3, 5), list(/obj/projectile/bullet/pellet/fragment/tank/small))
+ chosen_door.shrapnel_explosion(rand(5, 10), rand(3, 5), /obj/projectile/bullet/pellet/fragment/tank/small)
explosion(get_turf(chosen_door),-1,-1,2,3)
chosen_door.lock()
diff --git a/code/modules/ghostroles/spawnpoint.dm b/code/modules/ghostroles/spawnpoint.dm
index 5d424b155bbf..7439c71193ef 100644
--- a/code/modules/ghostroles/spawnpoint.dm
+++ b/code/modules/ghostroles/spawnpoint.dm
@@ -20,7 +20,7 @@ GLOBAL_LIST_EMPTY(ghostrole_spawnpoints)
var/spawntext
/datum/component/ghostrole_spawnpoint/Initialize(role_type, allowed_spawns = INFINITY, list/params, datum/callback/proc_to_call_or_callback, notify_ghosts = TRUE, spawntext)
- if((. = ..()) & COMPONENT_INCOMPATIBLE)
+ if((. = ..()) == COMPONENT_INCOMPATIBLE)
return
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
diff --git a/code/modules/hardsuits/_rig.dm b/code/modules/hardsuits/_rig.dm
index 44d584ac1691..a0283f4234a3 100644
--- a/code/modules/hardsuits/_rig.dm
+++ b/code/modules/hardsuits/_rig.dm
@@ -1136,11 +1136,6 @@
if(!CHECK_MOBILITY(user, MOBILITY_CAN_MOVE))
return
- if(locate(/obj/effect/stop/, wearer.loc))
- for(var/obj/effect/stop/S in wearer.loc)
- if(S.victim == wearer)
- return
-
if(!wearer.lastarea)
wearer.lastarea = get_area(wearer.loc)
diff --git a/code/modules/hardsuits/modules/combat.dm b/code/modules/hardsuits/modules/combat.dm
index dc2d4874ef62..66cdec37be3d 100644
--- a/code/modules/hardsuits/modules/combat.dm
+++ b/code/modules/hardsuits/modules/combat.dm
@@ -202,7 +202,7 @@
/obj/item/hardsuit_module/mounted/energy_blade/process(delta_time)
if(holder && holder.wearer)
- if(!(locate(/obj/item/melee/energy/blade) in holder.wearer))
+ if(!(locate(/obj/item/melee/ninja_energy_blade) in holder.wearer))
deactivate()
return 0
@@ -219,7 +219,7 @@
deactivate()
return
- var/obj/item/melee/energy/blade/blade = new(M)
+ var/obj/item/melee/ninja_energy_blade/blade = new(M)
blade.creator = M
M.put_in_hands(blade)
@@ -232,7 +232,7 @@
if(!M)
return
- for(var/obj/item/melee/energy/blade/blade in M.contents)
+ for(var/obj/item/melee/ninja_energy_blade/blade in M.contents)
qdel(blade)
/obj/item/hardsuit_module/fabricator
diff --git a/code/modules/hardsuits/suits/merc.dm b/code/modules/hardsuits/suits/merc.dm
index ce1877f6d356..956e6a901cc2 100644
--- a/code/modules/hardsuits/suits/merc.dm
+++ b/code/modules/hardsuits/suits/merc.dm
@@ -31,7 +31,7 @@
/obj/item/ammo_magazine,
/obj/item/ammo_casing,
/obj/item/melee/baton,
- /obj/item/melee/energy/sword,
+ /obj/item/melee/transforming/energy/sword,
/obj/item/handcuffs,
/obj/item/bluespace_radio,
)
diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm
index 0854a36c59cf..0afe78ce86f7 100644
--- a/code/modules/holodeck/HolodeckObjects.dm
+++ b/code/modules/holodeck/HolodeckObjects.dm
@@ -239,16 +239,7 @@
/obj/item/holo/esword/red
lcolor = "#FF0000"
-/obj/item/holo/esword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(active && default_parry_check(user, attacker, damage_source) && prob(50))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
-
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.set_up(5, 0, user.loc)
- spark_system.start()
- playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
- return TRUE
- return FALSE
+// todo: the parry system was removed from this because that sucks maybe readd it later lol
/obj/item/holo/esword/attack_self(mob/user)
. = ..()
diff --git a/code/modules/holomap/holomap_datum.dm b/code/modules/holomap/holomap_datum.dm
index f6d4f61bba54..df8f754888d9 100644
--- a/code/modules/holomap/holomap_datum.dm
+++ b/code/modules/holomap/holomap_datum.dm
@@ -14,8 +14,8 @@
if(isAI)
T = get_turf(user.client.eye)
- cursor.pixel_x = (T.x - 6 + HOLOMAP_PIXEL_OFFSET_X(T.z)) * PIXEL_MULTIPLIER
- cursor.pixel_y = (T.y - 6 + HOLOMAP_PIXEL_OFFSET_Y(T.z)) * PIXEL_MULTIPLIER
+ cursor.pixel_x = (T.x - 6 + HOLOMAP_PIXEL_OFFSET_X(T.z)) * (WORLD_ICON_SIZE / 32)
+ cursor.pixel_y = (T.y - 6 + HOLOMAP_PIXEL_OFFSET_Y(T.z)) * (WORLD_ICON_SIZE / 32)
legend.pixel_x = HOLOMAP_LEGEND_X(T.z)
legend.pixel_y = HOLOMAP_LEGEND_Y(T.z)
diff --git a/code/modules/hydroponics/trays/tray.dm b/code/modules/hydroponics/trays/tray.dm
index e1689c458cbf..6003b3b9eabb 100644
--- a/code/modules/hydroponics/trays/tray.dm
+++ b/code/modules/hydroponics/trays/tray.dm
@@ -201,26 +201,22 @@
check_health()
update_icon()
-/obj/machinery/portable_atmospherics/hydroponics/bullet_act(var/obj/projectile/Proj)
-
+/obj/machinery/portable_atmospherics/hydroponics/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
//Don't act on seeds like dionaea that shouldn't change.
if(seed && seed.get_trait(TRAIT_IMMUTABLE) > 0)
return
//Override for somatoray projectiles.
- if(istype(Proj ,/obj/projectile/energy/floramut)&& prob(20))
- if(istype(Proj, /obj/projectile/energy/floramut/gene))
- var/obj/projectile/energy/floramut/gene/G = Proj
+ if(istype(proj ,/obj/projectile/energy/floramut)&& prob(20))
+ if(istype(proj, /obj/projectile/energy/floramut/gene))
+ var/obj/projectile/energy/floramut/gene/G = proj
if(seed)
seed = seed.diverge_mutate_gene(G.gene, get_turf(loc)) //get_turf just in case it's not in a turf.
else
mutate(1)
- return
- else if(istype(Proj ,/obj/projectile/energy/florayield) && prob(20))
+ else if(istype(proj ,/obj/projectile/energy/florayield) && prob(20))
yield_mod = min(10,yield_mod+rand(1,2))
- return
-
- ..()
/obj/machinery/portable_atmospherics/hydroponics/proc/check_health()
if(seed && !dead && health <= 0)
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index 28db56a4ff61..94a68fb24cbd 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -1080,7 +1080,7 @@ GLOBAL_DATUM_INIT(circuit_translation_context, /datum/translation_context/simple
if(signlang)
activate_pin(2)
-/obj/item/integrated_circuit/input/microphone/sign/hear_signlang(text, verb, datum/language/speaking, mob/M as mob)
+/obj/item/integrated_circuit/input/microphone/sign/hear_signlang(mob/M as mob, text, verb, datum/language/speaking)
hear_talk(M, text, verb, speaking)
return
diff --git a/code/modules/integrated_electronics/subtypes/time.dm b/code/modules/integrated_electronics/subtypes/time.dm
index ffd8651f9128..645bf29ae8e5 100644
--- a/code/modules/integrated_electronics/subtypes/time.dm
+++ b/code/modules/integrated_electronics/subtypes/time.dm
@@ -85,7 +85,7 @@
/obj/item/integrated_circuit/time/ticker/Destroy()
if(is_running)
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
return ..()
/obj/item/integrated_circuit/time/ticker/on_data_written()
diff --git a/code/modules/loot/packs/weapons.dm b/code/modules/loot/packs/weapons.dm
index 2443fa9c5f6c..f6a9f723839c 100644
--- a/code/modules/loot/packs/weapons.dm
+++ b/code/modules/loot/packs/weapons.dm
@@ -3,8 +3,8 @@
/datum/prototype/struct/loot_pack/weapons/melee1
some = list(
- /obj/item/melee/energy/sword,
- /obj/item/shield/energy,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/shield/transforming/energy,
/obj/item/melee/baton,
/obj/item/melee/chainofcommand,
/obj/item/melee/nanite_knife,
diff --git a/code/modules/mapping/map_helpers/engine_loader.dm b/code/modules/mapping/map_helpers/engine_loader.dm
index 7d0d8e97ea4e..1dc4d35494f3 100644
--- a/code/modules/mapping/map_helpers/engine_loader.dm
+++ b/code/modules/mapping/map_helpers/engine_loader.dm
@@ -36,7 +36,7 @@
their_for_map = initial(map_path.id)
if(their_for_map != src.for_map)
continue
- var/name = initial(path.name)
+ var/name = lowertext(initial(path.name))
potential_filtered[path] = isnum(probabilities[name])? probabilities[name] : 1
var/picked_path = pickweightAllowZero(potential_filtered)
diff --git a/code/modules/mining/kinetic_crusher.dm b/code/modules/mining/kinetic_crusher.dm
index a6e7c3b66276..4bc04ca17d20 100644
--- a/code/modules/mining/kinetic_crusher.dm
+++ b/code/modules/mining/kinetic_crusher.dm
@@ -311,7 +311,7 @@
damage_type = BRUTE
damage_flag = ARMOR_BOMB
range = WORLD_ICON_SIZE * 6
- accuracy = INFINITY // NO.
+ accuracy_disabled = TRUE
// log_override = TRUE
var/obj/item/kinetic_crusher/hammer_synced
@@ -319,7 +319,10 @@
hammer_synced = null
return ..()
-/obj/projectile/destabilizer/on_hit(atom/target, blocked = FALSE)
+/obj/projectile/destabilizer/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isliving(target))
var/mob/living/L = target
if(hammer_synced.can_mark(L))
@@ -341,7 +344,6 @@
var/turf/simulated/mineral/M = target_turf
new /obj/effect/temp_visual/kinetic_blast(M)
M.GetDrilled(firer)
- ..()
/*
//trophies
diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm
index af02717eb6d7..a39668372302 100644
--- a/code/modules/mining/machine_processing.dm
+++ b/code/modules/mining/machine_processing.dm
@@ -199,9 +199,9 @@
speed_process = !speed_process // switching gears
if(speed_process) // high gear
STOP_MACHINE_PROCESSING(src)
- START_PROCESSING(SSfastprocess, src)
+ START_PROCESSING(SSprocess_5fps, src)
else // low gear
- STOP_PROCESSING(SSfastprocess, src)
+ STOP_PROCESSING(SSprocess_5fps, src)
START_MACHINE_PROCESSING(src)
/obj/machinery/mineral/processing_unit/process(delta_time)
diff --git a/code/modules/mining/mine_outcrops.dm b/code/modules/mining/mine_outcrops.dm
index d5c769d77473..03fb1c355fd1 100644
--- a/code/modules/mining/mine_outcrops.dm
+++ b/code/modules/mining/mine_outcrops.dm
@@ -108,11 +108,12 @@
return
. = ..()
-/obj/structure/outcrop/bullet_act(obj/projectile/P, def_zone)
- if(P.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks.
- GetDrilled()
- return
+/obj/structure/outcrop/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT)
+ return
+ if(proj.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks.
+ GetDrilled()
/obj/structure/outcrop/proc/GetDrilled()
new outcropdrop(get_turf(src), rand(mindrop,upperdrop))
diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm
index c8c81622e3ea..adb4c3169627 100644
--- a/code/modules/mining/mine_turfs.dm
+++ b/code/modules/mining/mine_turfs.dm
@@ -296,10 +296,10 @@ CREATE_STANDARD_TURFS(/turf/simulated/mineral/floor/ignore_cavegen)
new oretype(src)
resources[ore] = 0
-/turf/simulated/mineral/bullet_act(var/obj/projectile/Proj) // only emitters for now
+/turf/simulated/mineral/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
- if(Proj.excavation_amount)
- var/newDepth = excavation_level + Proj.excavation_amount // Used commonly below
+ if(proj.excavation_amount)
+ var/newDepth = excavation_level + proj.excavation_amount // Used commonly below
if(newDepth >= 200) // first, if the turf is completely drilled then don't bother checking for finds and just drill it
GetDrilled(0)
return
@@ -312,8 +312,8 @@ CREATE_STANDARD_TURFS(/turf/simulated/mineral/floor/ignore_cavegen)
if(prob(50))
artifact_debris()
- excavation_level += Proj.excavation_amount
- update_archeo_overlays(Proj.excavation_amount)
+ excavation_level += proj.excavation_amount
+ update_archeo_overlays(proj.excavation_amount)
/turf/simulated/mineral/Bumped(AM)
diff --git a/code/modules/mob/defense.dm b/code/modules/mob/defense.dm
deleted file mode 100644
index fae10ea95e3a..000000000000
--- a/code/modules/mob/defense.dm
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * calculates the resulting damage from an attack, taking into account our armor and soak
- *
- * @params
- * * damage - raw damage
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * target_zone - where it's impacting
- *
- * @return args as list.
- */
-/mob/proc/check_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/list/returned = check_armor(damage, tier, flag, mode, attack_type, weapon)
- damage = returned[1]
- mode = returned[4]
- return args.Copy()
-
-/**
- * runs armor against an incoming attack
- * this proc can have side effects
- *
- * @params
- * * damage - raw damage
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * target_zone - where it's impacting
- *
- * @return args as list.
- */
-/mob/proc/run_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/list/returned = run_armor(damage, tier, flag, mode, attack_type, weapon)
- damage = returned[1]
- mode = returned[4]
- return args.Copy()
-
-/**
- * check overall armor
- * does not support modifying damage modes.
- *
- * @params
- * * damage - raw damage
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * target_zone - where it's impacting
- *
- * @return args as list.
- */
-/mob/proc/check_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/list/returned = check_armor(damage, tier, flag, mode, attack_type, weapon)
- damage = returned[1]
- mode = returned[4]
- return args.Copy()
-
-/**
- * runs overall armor against an incoming attack
- * this proc can have side effects
- * does not support modifying damage modes.
- *
- * @params
- * * damage - raw damage
- * * tier - penetration / attack tier
- * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm]
- * * mode - damage_mode
- * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm]
- * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed
- * * target_zone - where it's impacting
- *
- * @return args as list.
- */
-/mob/proc/run_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/list/returned = run_armor(damage, tier, flag, mode, attack_type, weapon)
- damage = returned[1]
- mode = returned[4]
- return args.Copy()
diff --git a/code/modules/mob/grab.dm b/code/modules/mob/grab.dm
index aad94cc6f478..d42f8c6c55b3 100644
--- a/code/modules/mob/grab.dm
+++ b/code/modules/mob/grab.dm
@@ -1,21 +1,39 @@
/**
* returns everyone we're grabbing associated to state
*/
-/mob/proc/grabbing()
+/mob/proc/get_grabbing()
RETURN_TYPE(/list)
. = list()
for(var/obj/item/grab/G in get_held_items())
.[G.affecting] = G.state
+/**
+ * returns everyone we're grabbing that are at least the given grab state
+ */
+/mob/proc/get_grabbing_of_state(state)
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/obj/item/grab/G in get_held_items())
+ if(G.state < state)
+ continue
+ . += G.affecting
+
/**
* returns everyone we're grabbing, recursively; this can include ourselves!
+ *
+ * @return grabbed mobs associated to states
*/
-/mob/proc/grabbing_recursive(list/L = list())
+/mob/proc/get_grabbing_recursive(list/L = list(), safety = 15, list/processed = list())
RETURN_TYPE(/list)
+ if(processed[src])
+ return
+ processed[src] = TRUE
+ if(safety <= 0)
+ CRASH("infinite loop guard tripped")
. = L
for(var/obj/item/grab/G in get_held_items())
.[G.affecting] = max(.[G.affecting], G.state)
- grabbing_recursive(G.affecting)
+ G.affecting.get_grabbing_recursive(., --safety, processed)
/**
* check the grab state of us to someone
@@ -242,7 +260,7 @@
/obj/item/grab/throw_resolve_override(atom/movable/resolved, mob/user)
return TRUE
-/obj/item/grab/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags, mult)
+/obj/item/grab/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags)
switch(state)
if(GRAB_PASSIVE)
clickchain.visible_feedback(
diff --git a/code/modules/mob/inventory/items.dm b/code/modules/mob/inventory/items.dm
index 59b2a1d438c0..e0c62b3f61b9 100644
--- a/code/modules/mob/inventory/items.dm
+++ b/code/modules/mob/inventory/items.dm
@@ -34,7 +34,7 @@
/obj/item/proc/equipped(mob/user, slot, flags)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot, flags)
- SEND_SIGNAL(user, COMSIG_MOB_ITEM_EQUIPPED, user, slot, flags)
+ SEND_SIGNAL(user, COMSIG_MOB_ITEM_EQUIPPED, src, slot, flags)
worn_slot = slot
if(!(flags & INV_OP_IS_ACCESSORY))
// todo: shouldn't be in here
@@ -68,7 +68,7 @@
/obj/item/proc/unequipped(mob/user, slot, flags)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_UNEQUIPPED, user, slot, flags)
- SEND_SIGNAL(user, COMSIG_MOB_ITEM_UNEQUIPPED, user, slot, flags)
+ SEND_SIGNAL(user, COMSIG_MOB_ITEM_UNEQUIPPED, src, slot, flags)
worn_slot = null
if(!(flags & INV_OP_IS_ACCESSORY))
// todo: shouldn't be in here
@@ -145,11 +145,20 @@
*/
/obj/item/proc/pickup(mob/user, flags, atom/oldLoc)
SHOULD_CALL_PARENT(TRUE)
+
+ // we load the component here as it hooks equipped,
+ // so loading it here means it can still handle the equipped signal.
+ if(passive_parry)
+ LoadComponent(/datum/component/passive_parry, passive_parry)
+
SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user, flags, oldLoc)
SEND_SIGNAL(user, COMSIG_MOB_ITEM_PICKUP, src, flags, oldLoc)
+
reset_pixel_offsets()
hud_layerise()
+
item_flags |= ITEM_IN_INVENTORY
+
// todo: should this be here
transform = null
if(isturf(oldLoc) && !(flags & (INV_OP_SILENT | INV_OP_DIRECTLY_EQUIPPING)))
@@ -374,6 +383,8 @@
* checks if we're held in hand
*
* if so, returns mob we're in
+ *
+ * @return the mob holding us
*/
/obj/item/proc/is_held()
return (worn_slot == SLOT_ID_HANDS)? worn_mob() : null
diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm
index 182892376d35..48e5a56b50a8 100644
--- a/code/modules/mob/living/bot/secbot.dm
+++ b/code/modules/mob/living/bot/secbot.dm
@@ -226,9 +226,9 @@
if(health < curhealth && on == TRUE)
react_to_attack_polaris(user)
-/mob/living/bot/secbot/bullet_act(var/obj/projectile/P)
+/mob/living/bot/secbot/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
var/curhealth = health
- var/mob/shooter = P.firer
+ var/mob/shooter = proj.firer
. = ..()
//if we already have a target just ignore to avoid lots of checking
if(!target && health < curhealth && shooter && (shooter in view(world.view, src)))
diff --git a/code/modules/mob/living/carbon/carbon-defense.dm b/code/modules/mob/living/carbon/carbon-defense.dm
index 63a455454c09..8471cf172b62 100644
--- a/code/modules/mob/living/carbon/carbon-defense.dm
+++ b/code/modules/mob/living/carbon/carbon-defense.dm
@@ -1,5 +1,17 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 Citadel Station developers. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Projectile Handling *//
+
+/mob/living/carbon/process_bullet_miss(obj/projectile/proj, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!.)
+ return
+ // perform normal baymiss
+ . = get_zone_with_miss_chance(., src, -10, TRUE)
+ // check if we even have that organ; if not, they automatically miss
+ if(!get_organ(.))
+ return null
//* Misc Effects *//
diff --git a/code/modules/mob/living/carbon/human/defense.dm b/code/modules/mob/living/carbon/human/defense.dm
index c5b686488c2c..f1035982f304 100644
--- a/code/modules/mob/living/carbon/human/defense.dm
+++ b/code/modules/mob/living/carbon/human/defense.dm
@@ -1,37 +1,20 @@
-/mob/living/carbon/human/check_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/obj/item/organ/external/part = get_organ(target_zone)
- for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags))
- var/list/results = I.checking_mob_armor(arglist(args))
- damage = results[1]
- mode = results[4]
- return ..()
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
-/mob/living/carbon/human/run_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/obj/item/organ/external/part = get_organ(target_zone)
- for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags))
- var/list/results = I.running_mob_armor(arglist(args))
- damage = results[1]
- mode = results[4]
- return ..()
+/mob/living/carbon/human/run_armorcalls(list/shieldcall_args, fake_attack, filter_zone)
+ ..() // perform default /mob level
-/mob/living/carbon/human/check_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
- var/total = 0
- var/total_size = 0
- for(var/key in organs_by_name)
- var/rel_size = organ_rel_size[key]
- if(!rel_size)
- continue
- var/obj/item/organ/external/part = organs_by_name[key]
- var/resultant = damage
+ if(filter_zone)
+ // just one zone
+ var/obj/item/organ/external/part = get_organ(filter_zone)
for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags))
- var/list/results = I.checking_mob_armor(resultant, tier, flag, mode, attack_type, weapon, target_zone)
- resultant = results[1]
- total += resultant * rel_size
- total_size += rel_size
- damage = total / total_size
- return ..()
+ I.mob_armorcall(src, shieldcall_args, fake_attack)
+ if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE)
+ break
+ return
-/mob/living/carbon/human/run_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone)
+ var/damage = shieldcall_args[SHIELDCALL_ARG_DAMAGE]
+ // all zones, uh oh, this is about to get very ugly
var/total = 0
var/total_size = 0
for(var/key in organs_by_name)
@@ -41,9 +24,10 @@
var/obj/item/organ/external/part = organs_by_name[key]
var/resultant = damage
for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags))
- var/list/results = I.running_mob_armor(resultant, tier, flag, mode, attack_type, weapon, target_zone)
- resultant = results[1]
+ var/list/copied_args = args.Copy()
+ copied_args[SHIELDCALL_ARG_DAMAGE] = resultant
+ I.mob_armorcall(src, copied_args, fake_attack)
+ resultant = copied_args[SHIELDCALL_ARG_DAMAGE]
total += resultant * rel_size
total_size += rel_size
- damage = total / total_size
- return ..()
+ shieldcall_args[SHIELDCALL_ARG_DAMAGE] = total / total_size
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human-damage-legacy.dm
similarity index 98%
rename from code/modules/mob/living/carbon/human/human_damage.dm
rename to code/modules/mob/living/carbon/human/human-damage-legacy.dm
index 212de7a6f405..f5a2e375af4f 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human-damage-legacy.dm
@@ -350,9 +350,6 @@ This function restores all organs.
current_organ.rejuvenate_legacy(ignore_prosthetic_prefs)
/mob/living/carbon/human/apply_damage(var/damage = 0, var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/soaked = 0, var/sharp = 0, var/edge = 0, var/obj/used_weapon = null)
- if(GLOB.Debug2)
- log_world("## DEBUG: human/apply_damage() was called on [src], with [damage] damage, an armor value of [blocked], and a soak value of [soaked].")
-
var/obj/item/organ/external/organ = null
if(isorgan(def_zone))
organ = def_zone
diff --git a/code/modules/mob/living/carbon/human/human-damage.dm b/code/modules/mob/living/carbon/human/human-damage.dm
new file mode 100644
index 000000000000..1e411fffb295
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/human-damage.dm
@@ -0,0 +1,4 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+// todo: start translating code over
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human-defense-legacy.dm
similarity index 90%
rename from code/modules/mob/living/carbon/human/human_defense.dm
rename to code/modules/mob/living/carbon/human/human-defense-legacy.dm
index c5ac3393588d..663da91a0673 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human-defense-legacy.dm
@@ -1,44 +1,3 @@
-/*
-Contains most of the procs that are called when a mob is attacked by something
-
-bullet_act
-legacy_ex_act
-meteor_act
-
-*/
-
-/mob/living/carbon/human/bullet_act(var/obj/projectile/P, var/def_zone)
- def_zone = check_zone(def_zone)
- if(!has_organ(def_zone))
- return PROJECTILE_FORCE_MISS //if they don't have the organ in question then the projectile just passes by.
-
- var/obj/item/organ/external/organ = get_organ()
-
- //Shields
- var/shield_check = check_shields(P.damage, P, null, def_zone, "the [P.name]")
- if(shield_check) // If the block roll succeeded, this is true.
- if(shield_check < 0) // The shield did something weird and the bullet needs to keep doing things (e.g. it was reflected).
- return shield_check // Likely equal to PROJECTILE_FORCE_MISS or PROJECTILE_CONTINUE.
- else // Otherwise we blocked normally and stopped all the damage.
- return 0
-
- if(!P.nodamage)
- organ.add_autopsy_data("[P.name]", P.damage)
-
- //Shrapnel
- if(P.can_embed())
- var/armor = getarmor_organ(organ, "bullet")
- if(!prob(armor/2)) //Even if the armor doesn't stop the bullet from hurting you, it might stop it from embedding.
- var/hit_embed_chance = P.embed_chance + (P.damage - armor) //More damage equals more chance to embed
- if(prob(max(hit_embed_chance, 0)))
- var/obj/item/material/shard/shrapnel/SP = new()
- SP.name = (P.name != "shrapnel")? "[P.name] shrapnel" : "shrapnel"
- SP.desc = "[SP.desc] It looks like it was fired from [P.shot_from]."
- SP.loc = organ
- organ.embed(SP)
-
- return ..()
-
/mob/living/carbon/human/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone)
var/obj/item/organ/external/affected = get_organ(check_zone(def_zone))
var/siemens_coeff = get_siemens_coefficient_organ(affected)
@@ -207,14 +166,8 @@ meteor_act
return gear
return null
-/mob/living/carbon/human/proc/check_shields(var/damage = 0, var/atom/damage_source = null, var/mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- for(var/obj/item/shield in list(l_hand, r_hand, wear_suit))
- if(!shield) continue
- . = shield.handle_shield(src, damage, damage_source, attacker, def_zone, attack_text)
- if(.) return
- return 0
-
/mob/living/carbon/human/resolve_item_attack(obj/item/I, mob/living/user, var/target_zone)
+ SEND_SIGNAL(src, COMSIG_MOB_LEGACY_RESOLVE_ITEM_ATTACK, I, user, target_zone)
if(check_neckgrab_attack(I, user, target_zone))
return null
@@ -226,7 +179,9 @@ meteor_act
if(!hit_zone)
return null
- if(check_shields(I.damage_force, I, user, target_zone, "the [I.name]"))
+ var/shieldcall_results = atom_shieldcall_handle_item_melee(I, new /datum/event_args/actor/clickchain(user), FALSE, NONE)
+ // todo: clickchain should be checked for damage mult
+ if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK)
return
var/obj/item/organ/external/affecting = get_organ(hit_zone)
@@ -377,17 +332,29 @@ meteor_act
miss_chance = max(5 * (distance - 2), 0)
zone = get_zone_with_miss_chance(zone, src, miss_chance, ranged_attack=1)
- if(zone && TT.thrower != src)
- var/shield_check = check_shields(throw_damage, O, TT.thrower, zone, "[O]")
- if(shield_check == PROJECTILE_FORCE_MISS)
- zone = null
- else if(shield_check)
- return
+ var/force_pierce = FALSE
+ var/no_attack = FALSE
+ if(zone)
+ // perform shieldcall
+ // todo: reconcile all the way down to /atom, or at least a higher level than /human.
+ var/retval
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ retval |= shieldcall.handle_throw_impact(src, TT)
+ if(retval & SHIELDCALL_FLAGS_SHOULD_TERMINATE)
+ break
+ if(retval & SHIELDCALL_FLAGS_SHOULD_PROCESS)
+ if(retval & SHIELDCALL_FLAGS_PIERCE_ATTACK)
+ force_pierce = TRUE
+ if(retval & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ no_attack = TRUE
if(!zone)
visible_message("\The [O] misses [src] narrowly!")
return COMPONENT_THROW_HIT_NEVERMIND | COMPONENT_THROW_HIT_PIERCE
+ if(no_attack)
+ return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE
+
var/obj/item/organ/external/affecting = get_organ(zone)
var/hit_area = affecting.name
@@ -406,7 +373,6 @@ meteor_act
if(armor < 100)
apply_damage(throw_damage, dtype, zone, armor, soaked, is_sharp(O), has_edge(O), O)
-
//thrown weapon embedded object code.
if(dtype == BRUTE && istype(O,/obj/item))
var/obj/item/I = O
@@ -450,6 +416,8 @@ meteor_act
anchored = TRUE
pinned += O
+ return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE
+
// This does a prob check to catch the thing flying at you, with a minimum of 1%
/mob/living/carbon/human/proc/can_catch(var/obj/O)
if(!get_active_held_item()) // If active hand is empty
diff --git a/code/modules/mob/living/carbon/human/human-defense.dm b/code/modules/mob/living/carbon/human/human-defense.dm
index 09f8eb24c177..4de1b17baac5 100644
--- a/code/modules/mob/living/carbon/human/human-defense.dm
+++ b/code/modules/mob/living/carbon/human/human-defense.dm
@@ -1,6 +1,34 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station developers. *//
+//* Projectile Handling *//
+
+/mob/living/carbon/human/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT)
+ return
+
+ if(impact_flags & PROJECTILE_IMPACT_BLOCKED)
+ return
+
+ // todo: this shit shouldn't be here
+ var/obj/item/organ/external/organ = get_organ()
+
+ if(!proj.nodamage)
+ organ.add_autopsy_data("[proj.name]", proj.damage)
+
+ //Shrapnel
+ if(proj.can_embed())
+ var/armor = getarmor_organ(organ, "bullet")
+ if(!prob(armor/2)) //Even if the armor doesn't stop the bullet from hurting you, it might stop it from embedding.
+ var/hit_embed_chance = proj.embed_chance + (proj.damage - armor) //More damage equals more chance to embed
+ if(prob(max(hit_embed_chance, 0)))
+ var/obj/item/material/shard/shrapnel/SP = new()
+ SP.name = (proj.name != "shrapnel")? "[proj.name] shrapnel" : "shrapnel"
+ SP.desc = "[SP.desc] It looks like it was fired from [proj.shot_from]."
+ SP.loc = organ
+ organ.embed(SP)
+
//* Misc Effects *//
/mob/living/carbon/human/slip_act(slip_class, source, hard_strength, soft_strength, suppressed)
diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm
index 56cf6cf52b3e..0667987fb4fa 100644
--- a/code/modules/mob/living/carbon/human/human_attackhand.dm
+++ b/code/modules/mob/living/carbon/human/human_attackhand.dm
@@ -53,9 +53,11 @@
visible_message("[H] reaches for [src], but misses!")
return FALSE
- if(H != src && check_shields(0, null, H, H.zone_sel.selecting, H.name))
- H.do_attack_animation(src)
- return FALSE
+ if(user.a_intent != INTENT_HARM)
+ var/shieldcall_results = atom_shieldcall_handle_touch(new /datum/event_args/actor/clickchain(user))
+ if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ H.do_attack_animation(src)
+ return FALSE
if(istype(user,/mob/living/carbon))
var/mob/living/carbon/C = user
@@ -195,6 +197,11 @@
if(!attack)
return FALSE
+ var/shieldcall_results = atom_shieldcall_handle_unarmed_melee(attack, new /datum/event_args/actor/clickchain(user))
+ if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ H.do_attack_animation(src)
+ return FALSE
+
if(attack.unarmed_override(H, src, hit_zone))
return FALSE
diff --git a/code/modules/mob/living/carbon/human/traits/weaver_objs.dm b/code/modules/mob/living/carbon/human/traits/weaver_objs.dm
index b08f5cd4bb27..45211ebf7d6a 100644
--- a/code/modules/mob/living/carbon/human/traits/weaver_objs.dm
+++ b/code/modules/mob/living/carbon/human/traits/weaver_objs.dm
@@ -22,9 +22,9 @@ var/global/list/weavable_items = list()
visible_message("\The [src] has been [W.get_attack_verb(src, user)] with \the [W][(user ? " by [user]." : ".")]")
qdel(src)
-/obj/effect/weaversilk/bullet_act(var/obj/projectile/Proj)
- ..()
- if(Proj.get_structure_damage())
+/obj/effect/weaversilk/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(proj.get_structure_damage())
qdel(src)
/obj/effect/weaversilk/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
diff --git a/code/modules/mob/living/inventory.dm b/code/modules/mob/living/inventory.dm
index 8f179980acf6..c9db1ad494af 100644
--- a/code/modules/mob/living/inventory.dm
+++ b/code/modules/mob/living/inventory.dm
@@ -185,40 +185,6 @@
SLOT_ID_MASK
)
-/mob/living/ret_grab(obj/effect/list_container/mobl/L as obj, flag)
- if ((!( istype(l_hand, /obj/item/grab) ) && !( istype(r_hand, /obj/item/grab) )))
- if (!( L ))
- return null
- else
- return L.container
- else
- if (!( L ))
- L = new /obj/effect/list_container/mobl( null )
- L.container += src
- L.master = src
- if (istype(l_hand, /obj/item/grab))
- var/obj/item/grab/G = l_hand
- if (!( L.container.Find(G.affecting) ))
- L.container += G.affecting
- if (G.affecting)
- G.affecting.ret_grab(L, 1)
- if (istype(r_hand, /obj/item/grab))
- var/obj/item/grab/G = r_hand
- if (!( L.container.Find(G.affecting) ))
- L.container += G.affecting
- if (G.affecting)
- G.affecting.ret_grab(L, 1)
- if (!( flag ))
- if (L.master == src)
- var/list/temp = list( )
- temp += L.container
- //L = null
- qdel(L)
- return temp
- else
- return L.container
- return
-
/mob/living/abiotic(full_body)
if(full_body)
if(item_considered_abiotic(wear_mask))
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/living-damage.dm
similarity index 93%
rename from code/modules/mob/living/damage_procs.dm
rename to code/modules/mob/living/living-damage.dm
index 379abddbf02e..ac09b0d13bc5 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/living-damage.dm
@@ -206,3 +206,17 @@
return
radiation = max(0, radiation - amt)
+//* Damage Instance Handling *//
+
+/mob/living/inflict_damage_instance(SHIELDCALL_PROC_HEADER)
+ if(inflict_damage_type_special(args))
+ return
+
+ var/weapon_descriptor = RESOLVE_SHIELDCALL_WEAPON_DESCRIPTOR(args)
+ var/brute = damage_type == BRUTE? damage : 0
+ var/burn = damage_type == BURN? damage : 0
+
+ if(hit_zone)
+ take_targeted_damage(brute, burn, damage_mode, hit_zone, weapon_descriptor)
+ else
+ take_overall_damage(brute, burn, damage_mode, weapon_descriptor)
diff --git a/code/modules/mob/living/defense.dm b/code/modules/mob/living/living-defense-legacy.dm
similarity index 86%
rename from code/modules/mob/living/defense.dm
rename to code/modules/mob/living/living-defense-legacy.dm
index 1986b015bc07..6f88e783acd2 100644
--- a/code/modules/mob/living/defense.dm
+++ b/code/modules/mob/living/living-defense-legacy.dm
@@ -96,6 +96,7 @@
. = ..()
if(.)
return
+ SEND_SIGNAL(src, COMSIG_MOB_LEGACY_ATTACK_HAND_INTERCEPT, user, params)
var/mob/living/L = user
if(!istype(L))
return
@@ -110,65 +111,6 @@
else
afflict_radiation(strength * RAD_MOB_ACT_COEFFICIENT - RAD_MOB_ACT_PROTECTION_PER_WAVE_SOURCE, TRUE)
-/mob/living/bullet_act(var/obj/projectile/P, var/def_zone)
-
- //Being hit while using a deadman switch
- if(istype(get_active_held_item(),/obj/item/assembly/signaler))
- var/obj/item/assembly/signaler/signaler = get_active_held_item()
- if(signaler.deadman && prob(80))
- log_and_message_admins("has triggered a signaler deadman's switch")
- src.visible_message("[src] triggers their deadman's switch!")
- signaler.signal()
-
- if(ai_holder && P.firer)
- ai_holder.react_to_attack_polaris(P.firer)
-
- //Armor
- var/soaked = get_armor_soak(def_zone, P.damage_flag, P.armor_penetration)
- var/absorb = run_armor_check(def_zone, P.damage_flag, P.armor_penetration)
- var/proj_sharp = is_sharp(P)
- var/proj_edge = has_edge(P)
- var/final_damage = P.get_final_damage(src)
-
- if ((proj_sharp || proj_edge) && (soaked >= round(P.damage*0.8)))
- proj_sharp = 0
- proj_edge = 0
-
- if ((proj_sharp || proj_edge) && prob(legacy_mob_armor(def_zone, P.damage_flag)))
- proj_sharp = 0
- proj_edge = 0
-
- var/list/impact_sounds = islist(P.impact_sounds)? LAZYACCESS(P.impact_sounds, get_bullet_impact_effect_type(def_zone)) : P.impact_sounds
- if(length(impact_sounds))
- playsound(src, pick(impact_sounds), 75)
- else if(!isnull(impact_sounds))
- playsound(src, impact_sounds, 75)
-
- //Stun Beams
- if(P.taser_effect)
- stun_effect_act(0, P.agony, def_zone, P)
- to_chat(src, "You have been hit by [P]!")
- if(!P.nodamage)
- apply_damage(final_damage, P.damage_type, def_zone, absorb, soaked, 0, P, sharp=proj_sharp, edge=proj_edge)
- qdel(P)
- return
-
- if(!P.nodamage)
- apply_damage(final_damage, P.damage_type, def_zone, absorb, soaked, 0, P, sharp=proj_sharp, edge=proj_edge)
- P.on_hit(src, absorb, soaked, def_zone)
-
- if(absorb == 100)
- return 2
- else if (absorb >= 0)
- return 1
- else
- return 0
-
-// return absorb
-
-/mob/living/get_bullet_impact_effect_type(var/def_zone)
- return BULLET_IMPACT_MEAT
-
//Handles the effects of "stun" weapons
/mob/living/proc/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null)
flash_pain()
@@ -185,7 +127,7 @@
apply_effect(EYE_BLUR, agony_amount/10)
/mob/living/proc/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1)
- return 0 //only carbon liveforms have this proc
+ return 0 //only carbon liveforms have this proc
/mob/living/emp_act(severity)
var/list/L = src.get_equipped_items(TRUE, TRUE)
@@ -231,6 +173,13 @@
apply_damage(damage, damage_type, def_zone, absorb, soaked)
/mob/living/proc/resolve_item_attack(obj/item/I, mob/living/user, var/target_zone)
+ SEND_SIGNAL(src, COMSIG_MOB_LEGACY_RESOLVE_ITEM_ATTACK, I, user, target_zone)
+
+ var/shieldcall_results = atom_shieldcall_handle_item_melee(I, new /datum/event_args/actor/clickchain(user), FALSE, NONE)
+ // todo: clickchain should be checked for damage mult
+ if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ return
+
return target_zone
//Called when the mob is hit with an item in combat. Returns the blocked result
@@ -284,6 +233,32 @@
visible_message("\The [O] misses [src] narrowly!")
return COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND
+ var/force_pierce = FALSE
+ var/no_attack = FALSE
+
+ var/zone
+ if (istype(TT.thrower, /mob/living))
+ zone = check_zone(TT.target_zone)
+ else
+ zone = ran_zone(BP_TORSO,75) //Hits a random part of the body, geared towards the chest
+
+ if(zone)
+ // perform shieldcall
+ // todo: reconcile all the way down to /atom, or at least a higher level than /human.
+ var/retval
+ for(var/datum/shieldcall/shieldcall as anything in shieldcalls)
+ retval |= shieldcall.handle_throw_impact(src, TT)
+ if(retval & SHIELDCALL_FLAGS_SHOULD_TERMINATE)
+ break
+ if(retval & SHIELDCALL_FLAGS_SHOULD_PROCESS)
+ if(retval & SHIELDCALL_FLAGS_PIERCE_ATTACK)
+ force_pierce = TRUE
+ if(retval & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ no_attack = TRUE
+
+ if(no_attack)
+ return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE
+
src.visible_message("[src] has been hit by [O].")
var/armor = run_armor_check(null, "melee")
var/soaked = get_armor_soak(null, "melee")
@@ -325,11 +300,13 @@
var/turf/T = near_wall(dir,2)
if(T)
- src.loc = T
+ src.forceMove(T)
visible_message("[src] is pinned to the wall by [O]!","You are pinned to the wall by [O]!")
src.anchored = 1
src.pinned += O
+ return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE
+
/mob/living/proc/embed(var/obj/O, var/def_zone=null)
O.loc = src
src.embedded += O
@@ -487,6 +464,7 @@
/mob/living/proc/reagent_permeability()
return 1
+// todo: rework
// Returns a number to determine if something is harder or easier to hit than normal.
/mob/living/proc/get_evasion()
var/result = evasion // First we get the 'base' evasion. Generally this is zero.
@@ -495,14 +473,6 @@
result += M.evasion
return result
+// todo: rework
/mob/living/proc/get_accuracy_penalty()
- // Certain statuses make it harder to score a hit.
- var/accuracy_penalty = 0
- if(has_status_effect(/datum/status_effect/sight/blindness))
- accuracy_penalty += 75
- if(eye_blurry)
- accuracy_penalty += 30
- if(confused)
- accuracy_penalty += 45
-
- return accuracy_penalty
+ return 0
diff --git a/code/modules/mob/living/living-defense.dm b/code/modules/mob/living/living-defense.dm
index 3bdb111c5bc8..bce917a898b5 100644
--- a/code/modules/mob/living/living-defense.dm
+++ b/code/modules/mob/living/living-defense.dm
@@ -1,5 +1,115 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 Citadel Station developers. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Projectile Handling *//
+
+/mob/living/bullet_act(obj/projectile/proj, impact_flags, def_zone, efficiency)
+ //! LEGACY
+
+ // Using someone as a shield
+ // todo: need a counter to this..
+ for(var/mob/living/victim in get_grabbing_of_state(GRAB_NECK))
+ if(victim.stat == DEAD)
+ // small mobs are penalized; this is a holdover.
+ var/shield_chance = min(80, (30 * (mob_size / 10)))
+ if(prob(shield_chance))
+ visible_message("\The [src] uses [victim] as a shield!")
+ if(!(proj.impact_redirect(victim, args) | (PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH | PROJECTILE_IMPACT_DUPLICATE)))
+ return
+ else
+ visible_message("\The [src] tries to use [victim] as a shield, but fails!")
+ else
+ visible_message("\The [src] uses [victim] as a shield!")
+ if(!(proj.impact_redirect(victim, args) | (PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH | PROJECTILE_IMPACT_DUPLICATE)))
+ return
+ // Process baymiss & zonemiss
+ def_zone = process_bullet_miss(proj, impact_flags, def_zone, efficiency)
+ def_zone = proj.process_zone_miss(src, def_zone, proj.distance_travelled, TRUE)
+ if(!def_zone)
+ if(!proj.silenced)
+ visible_message(SPAN_WARNING("\The [proj] misses [src] narrowly!"))
+ playsound(src, pick(proj.miss_sounds), 60, TRUE)
+ add_attack_logs(
+ proj.firer,
+ src,
+ "shot with [src] ([type]) (missed)",
+ )
+ impact_flags |= PROJECTILE_IMPACT_PASSTHROUGH
+ return ..()
+
+ //! END
+
+ return ..()
+
+/mob/living/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ // todo: better logging
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT)
+ add_attack_logs(
+ proj.firer,
+ src,
+ "shot with [src] ([type]) (aborted)",
+ )
+ return
+ add_attack_logs(
+ proj.firer,
+ src,
+ "shot with [src] ([type])[(impact_flags & PROJECTILE_IMPACT_BLOCKED)? " (blocked)" : ""]",
+ )
+ // emit feedback
+ if(!(impact_flags & PROJECTILE_IMPACT_BLOCKED))
+ if(proj.silenced)
+ to_chat(src, SPAN_DANGER("You've been hit in the [parse_zone(bullet_act_args[BULLET_ACT_ARG_ZONE])] with \the [proj]!"))
+ else
+ visible_message(SPAN_DANGER("\The [src] is hit by [proj] in the [parse_zone(bullet_act_args[BULLET_ACT_ARG_ZONE])]"))
+
+ //! LEGACY
+
+ //Being hit while using a deadman switch
+ for(var/obj/item/assembly/signaler/signaler in get_held_items())
+ if(signaler.deadman && prob(80))
+ log_and_message_admins("has triggered a signaler deadman's switch")
+ visible_message("[src] triggers their deadman's switch!")
+ signaler.signal()
+
+ if(ai_holder && proj.firer)
+ ai_holder.react_to_attack_polaris(proj.firer)
+
+ //! END
+
+ if(!(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE)))
+ // todo: this should just be in base projectile on_impact
+ impact_flags |= proj.inflict_impact_damage(src, bullet_act_args[BULLET_ACT_ARG_EFFICIENCY], impact_flags, bullet_act_args[BULLET_ACT_ARG_ZONE])
+ return ..()
+
+/mob/living/get_bullet_impact_effect_type(var/def_zone)
+ return BULLET_IMPACT_MEAT
+
+/**
+ * @return zone to hit, or null to miss
+ */
+/mob/living/proc/process_bullet_miss(obj/projectile/proj, impact_flags, def_zone, efficiency)
+ var/hit_probability = process_baymiss(proj)
+ if(!prob(hit_probability))
+ return null
+ return def_zone
+
+/**
+ * * our_opinion is intentionally mutable; it is however only mutable from before ..(), so call ..() after modifying for pre-modification
+ * * our_opinion and impact_check are defaulted in the base function; this means that if you need to use it before, default it yourself.
+ *
+ * todo: 0 to 100 for accuracy might not be amazing; maybe allow negative values evasion-style?
+ * todo: don't default our_opinion and impact_check so early wtf; BYOND proc structure disagrees with the design here.
+ *
+ * @params
+ * * proj - the projectile
+ * * our_opinion - base probability of hitting
+ * * impact_check - are we checking if we should impact at all? used by pellets.
+ *
+ * @return 0 to 100 % probability of hitting
+ */
+/mob/living/proc/process_baymiss(obj/projectile/proj, our_opinion = 100, impact_check = TRUE)
+ our_opinion = clamp(our_opinion - get_evasion(), 5, INFINITY)
+ return proj.process_accuracy(src, our_opinion, null, impact_check)
//* Misc Effects *//
@@ -20,3 +130,5 @@
*/
/mob/living/proc/slip_act(slip_class, source, hard_strength, soft_strength, suppressed)
return 1
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station developers. *//
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 8b7ac8908cc5..cac79cb47bed 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -236,7 +236,7 @@ var/list/channel_to_radio_key = new
verb = speaking.speech_verb
w_not_heard = "[speaking.speech_verb] something [w_adverb]"
- var/list/message_args = list("message" = message, "whispering" = whispering, "cancelled" = FALSE)
+ var/list/message_args = list("message" = message, "whispering" = whispering, "cancelled" = FALSE, "message_mode" = message_mode)
SEND_SIGNAL(src, COMSIG_MOB_SAY, message_args)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 9c4ea9a0aecf..d141d84f0076 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -533,11 +533,13 @@
/mob/living/silicon/robot/restrained()
return 0
-/mob/living/silicon/robot/bullet_act(var/obj/projectile/Proj)
- ..(Proj)
- if(prob(75) && Proj.damage > 0)
+/mob/living/silicon/robot/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ // todo: why is this in bullet act and not where we take damage maybe?
+ if(prob(75) && proj.damage > 0)
spark_system.start()
- return 2
/mob/living/silicon/robot/attackby(obj/item/W as obj, mob/user as mob)
if (istype(W, /obj/item/handcuffs)) // fuck i don't even know why isrobot() in handcuff code isn't working so this will have to do
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm
index 1d21b94abe8a..bffbbf6c866f 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm
@@ -37,7 +37,7 @@
/obj/item/robot_module/robot/standard/handle_special_module_init(mob/living/silicon/robot/R)
. = ..()
- src.emag = new /obj/item/melee/energy/sword(src)
+ src.emag = new /obj/item/melee/transforming/energy/sword(src)
/obj/item/robot_module/robot/quad/basic
name = "Standard Quadruped module"
@@ -59,4 +59,4 @@
. = ..()
// These get a larger water synth.
synths_by_kind[MATSYN_WATER]:max_energy = 1000
- src.emag = new /obj/item/melee/energy/sword(src)
+ src.emag = new /obj/item/melee/transforming/energy/sword(src)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm b/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm
index 543a007c2d6d..5012c5031a35 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm
@@ -27,4 +27,4 @@
/obj/item/robot_module/drone/swarm/melee/get_modules()
. = ..()
- . |= /obj/item/melee/energy/sword/ionic_rapier/lance
+ . |= /obj/item/melee/transforming/energy/sword/ionic_rapier/lance
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm b/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm
index 02c593c5337c..fcb77a94c8f2 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm
@@ -35,7 +35,7 @@
. = ..()
. |= list(
/obj/item/pinpointer/shuttle/merc,
- /obj/item/melee/energy/sword
+ /obj/item/melee/transforming/energy/sword
)
/obj/item/robot_module/robot/syndicate/handle_special_module_init(mob/living/silicon/robot/R)
@@ -90,7 +90,7 @@
/obj/item/multitool/ai_detector,
/obj/item/pickaxe/plasmacutter,
/obj/item/rcd/electric/mounted/borg/lesser, // Can't eat rwalls to prevent AI core cheese.
- /obj/item/melee/energy/sword/ionic_rapier,
+ /obj/item/melee/transforming/energy/sword/ionic_rapier,
// FBP repair.
/obj/item/robotanalyzer,
diff --git a/code/modules/mob/living/silicon/silicon-damage.dm b/code/modules/mob/living/silicon/silicon-damage.dm
new file mode 100644
index 000000000000..d22faa575803
--- /dev/null
+++ b/code/modules/mob/living/silicon/silicon-damage.dm
@@ -0,0 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Damage Instance Handling *//
+
+/mob/living/silicon/inflict_damage_instance(damage, damage_type, damage_tier, damage_flag, damage_mode, attack_type, datum/weapon, shieldcall_flags, hit_zone, list/additional, datum/event_args/actor/clickchain/clickchain)
+ if(inflict_damage_type_special(args))
+ return
+ // we only care about those
+ switch(damage_type)
+ if(BRUTE)
+ adjustBruteLoss(damage)
+ if(BURN)
+ adjustBruteLoss(damage)
diff --git a/code/modules/mob/living/silicon/silicon-defense-legacy.dm b/code/modules/mob/living/silicon/silicon-defense-legacy.dm
new file mode 100644
index 000000000000..bf80122caf1f
--- /dev/null
+++ b/code/modules/mob/living/silicon/silicon-defense-legacy.dm
@@ -0,0 +1,57 @@
+/mob/living/silicon/emp_act(severity)
+ switch(severity)
+ if(1)
+ src.take_random_targeted_damage(brute = 0, burn = 20, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
+ Confuse(5)
+ if(2)
+ src.take_random_targeted_damage(brute = 0, burn = 15, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
+ Confuse(4)
+ if(3)
+ src.take_random_targeted_damage(brute = 0, burn = 10, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
+ Confuse(3)
+ if(4)
+ src.take_random_targeted_damage(brute = 0, burn = 5, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
+ Confuse(2)
+ flash_eyes(affect_silicon = 1)
+ to_chat(src, "*BZZZT*")
+ to_chat(src, "Warning: Electromagnetic pulse detected.")
+ ..()
+
+/mob/living/silicon/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null)
+ return //immune
+
+/mob/living/silicon/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1)
+ if(shock_damage > 0)
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(5, 1, loc)
+ s.start()
+
+ shock_damage *= siemens_coeff //take reduced damage
+ take_overall_damage(0, shock_damage)
+ visible_message("[src] was shocked by \the [source]!", \
+ "Energy pulse detected, system damaged!", \
+ "You hear an electrical crack.")
+ if(prob(20))
+ afflict_stun(20 * 2)
+ return
+
+/mob/living/silicon/legacy_ex_act(severity)
+ if(!has_status_effect(/datum/status_effect/sight/blindness))
+ flash_eyes()
+
+ switch(severity)
+ if(1.0)
+ if (stat != 2)
+ adjustBruteLoss(100)
+ adjustFireLoss(100)
+ if(!anchored)
+ gib()
+ if(2.0)
+ if (stat != 2)
+ adjustBruteLoss(60)
+ adjustFireLoss(60)
+ if(3.0)
+ if (stat != 2)
+ adjustBruteLoss(30)
+
+ update_health()
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 479e26ccf902..0f0a42b46378 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -112,62 +112,9 @@
else
src.bodytemp.icon_state = "temp-2"
-/mob/living/silicon/emp_act(severity)
- switch(severity)
- if(1)
- src.take_random_targeted_damage(brute = 0, burn = 20, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
- Confuse(5)
- if(2)
- src.take_random_targeted_damage(brute = 0, burn = 15, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
- Confuse(4)
- if(3)
- src.take_random_targeted_damage(brute = 0, burn = 10, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
- Confuse(3)
- if(4)
- src.take_random_targeted_damage(brute = 0, burn = 5, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge")
- Confuse(2)
- flash_eyes(affect_silicon = 1)
- to_chat(src, "*BZZZT*")
- to_chat(src, "Warning: Electromagnetic pulse detected.")
- ..()
-
-/mob/living/silicon/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null)
- return //immune
-
-/mob/living/silicon/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1)
- if(shock_damage > 0)
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
- s.set_up(5, 1, loc)
- s.start()
-
- shock_damage *= siemens_coeff //take reduced damage
- take_overall_damage(0, shock_damage)
- visible_message("[src] was shocked by \the [source]!", \
- "Energy pulse detected, system damaged!", \
- "You hear an electrical crack.")
- if(prob(20))
- afflict_stun(20 * 2)
- return
-
-/mob/living/silicon/proc/damage_mob(var/brute = 0, var/fire = 0, var/tox = 0)
- return
-
/mob/living/silicon/IsAdvancedToolUser()
return 1
-/mob/living/silicon/bullet_act(var/obj/projectile/Proj)
-
- if(!Proj.nodamage)
- switch(Proj.damage_type)
- if(BRUTE)
- adjustBruteLoss(Proj.get_final_damage(src))
- if(BURN)
- adjustFireLoss(Proj.get_final_damage(src))
-
- Proj.on_hit(src,2)
- update_health()
- return 2
-
/mob/living/silicon/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0, var/check_protection = 1)
return 0//The only effect that can hit them atm is flashes and they still directly edit so this works for now
@@ -300,27 +247,6 @@
/mob/living/silicon/binarycheck()
return 1
-/mob/living/silicon/legacy_ex_act(severity)
- if(!has_status_effect(/datum/status_effect/sight/blindness))
- flash_eyes()
-
- switch(severity)
- if(1.0)
- if (stat != 2)
- adjustBruteLoss(100)
- adjustFireLoss(100)
- if(!anchored)
- gib()
- if(2.0)
- if (stat != 2)
- adjustBruteLoss(60)
- adjustFireLoss(60)
- if(3.0)
- if (stat != 2)
- adjustBruteLoss(30)
-
- update_health()
-
/mob/living/silicon/proc/receive_alarm(var/datum/alarm_handler/alarm_handler, var/datum/alarm/alarm, was_raised)
if(!next_alarm_notice)
next_alarm_notice = world.time + SecondsToTicks(10)
diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm
index 5f2b43820182..32e833179976 100644
--- a/code/modules/mob/living/simple_animal/constructs/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs/constructs.dm
@@ -210,7 +210,7 @@
weakened = 0
..()
-/mob/living/simple_animal/construct/armoured/bullet_act(var/obj/projectile/P)
+/mob/living/simple_animal/construct/armoured/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
// if(istype(P, /obj/projectile/energy) || istype(P, /obj/projectile/beam)) //If it's going to be slow, it's probably going to need every reflect it can get.
var/reflectchance = 80 - round(P.damage/3)
if(prob(reflectchance))
@@ -364,7 +364,7 @@
/spell/targeted/construct_advanced/slam
)
-/mob/living/simple_animal/construct/behemoth/bullet_act(var/obj/projectile/P)
+/mob/living/simple_animal/construct/behemoth/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
var/reflectchance = 80 - round(P.damage/3)
if(prob(reflectchance))
visible_message("The [P.name] gets reflected by [src]'s shell!", \
diff --git a/code/modules/mob/living/simple_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm
index 9ac88e373eea..e226cbbfded4 100644
--- a/code/modules/mob/living/simple_mob/combat.dm
+++ b/code/modules/mob/living/simple_mob/combat.dm
@@ -62,10 +62,10 @@
playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1)
return FALSE // We missed.
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.check_shields(damage = damage_to_do, damage_source = src, attacker = src, def_zone = null, attack_text = "the attack"))
- return FALSE // We were blocked.
+ var/datum/event_args/actor/clickchain/simulated_clickchain = new(src, target = L)
+ var/list/shieldcall_result = L.atom_shieldcall(damage_to_do, BRUTE, MELEE_TIER_MEDIUM, ARMOR_MELEE, NONE, ATTACK_TYPE_MELEE, clickchain = simulated_clickchain)
+ if(shieldcall_result[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ return FALSE
if(apply_attack(A, damage_to_do))
apply_melee_effects(A)
@@ -138,7 +138,7 @@
// For some reason there isn't an argument for accuracy, so access the projectile directly instead.
// Also, placing dispersion here instead of in forced_spread will randomize the chosen angle between dispersion and -dispersion in fire() instead of having to do that here.
- P.accuracy += calculate_accuracy()
+ P.accuracy_overall_modify *= 1 + calculate_accuracy() / 100
P.dispersion += calculate_dispersion()
P.launch_projectile(target = A, target_zone = null, user = src, params = null, angle_override = null, forced_spread = 0)
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm
index 0944f22c199e..1aa2a52144f3 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm
@@ -51,7 +51,6 @@
var/roundstart = FALSE // If true, spawning won't try to pull a ghost.
var/used_dominate // world.time when the dominate power was last used.
-
/mob/living/simple_mob/animal/borer/roundstart
roundstart = TRUE
@@ -267,3 +266,9 @@
continue
else if(M.stat == DEAD && M.get_preference_toggle(/datum/game_preference_toggle/observer/ghost_ears))
to_chat(M, "[src.true_name] whispers to [host], \"[message]\"")
+
+/mob/living/simple_mob/animal/borer/proc/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from)
+ if(controlling)
+ target.release_control()
+ detatch()
+ leave_host()
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm
index 6161a011dda2..9589a72493c2 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm
@@ -87,10 +87,9 @@
if(L == src)
continue
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.check_shields(damage = 0, damage_source = src, attacker = src, def_zone = null, attack_text = "the leap"))
- continue // We were blocked.
+ var/list/shieldcall_result = L.atom_shieldcall(40, BRUTE, MELEE_TIER_MEDIUM, ARMOR_MELEE, NONE, ATTACK_TYPE_MELEE)
+ if(shieldcall_result[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ continue
victim = L
break
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm
index 237bf4f729fd..00cdeb796809 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm
@@ -116,7 +116,7 @@
..() // For the poison.
// Force unstealthing if attacked.
-/mob/living/simple_mob/animal/giant_spider/lurker/bullet_act(obj/projectile/P)
+/mob/living/simple_mob/animal/giant_spider/lurker/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
break_cloak()
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm b/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm
index 98d4713d3de1..baeb4b6a8c18 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm
@@ -537,7 +537,7 @@
..() // For the poison.
// Force unstealthing if attacked.
-/mob/living/simple_mob/animal/roach/zeitraum/bullet_act(obj/projectile/P)
+/mob/living/simple_mob/animal/roach/zeitraum/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
break_cloak()
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm
index 57d8d7963a95..f76c3a8289a1 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm
@@ -417,8 +417,10 @@
..() // For the poison.
// Force unstealthing if attacked.
-/mob/living/simple_mob/animal/space/mouse_army/stealth/bullet_act(obj/projectile/P)
+/mob/living/simple_mob/animal/space/mouse_army/stealth/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
break_cloak()
/mob/living/simple_mob/animal/space/mouse_army/stealth/hit_with_weapon(obj/item/O, mob/living/user, effective_force, hit_zone)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm
index 592bba73f355..af439b298ee2 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm
@@ -48,9 +48,9 @@
playsound(src, 'sound/h_sounds/headcrab.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Eddy/bullet_act()
+/mob/living/simple_mob/horror/Eddy/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Eddy/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm
index e17dcaf7b3c3..d49a957e3d42 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm
@@ -49,9 +49,9 @@
playsound(src, 'sound/h_sounds/imbeciles.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Master/bullet_act()
+/mob/living/simple_mob/horror/Master/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Master/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm
index bd5b19c6b2e3..c949dafb96e2 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm
@@ -50,9 +50,9 @@
playsound(src, 'sound/h_sounds/headcrab.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Rickey/bullet_act()
+/mob/living/simple_mob/horror/Rickey/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Rickey/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm
index 4a33ac456be5..c5f532332c85 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm
@@ -49,9 +49,9 @@
playsound(src, 'sound/h_sounds/lynx.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Helix/bullet_act()
+/mob/living/simple_mob/horror/Helix/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Helix/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm
index eb80a126aa91..507441749a72 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm
@@ -54,9 +54,9 @@
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Steve/bullet_act()
+/mob/living/simple_mob/horror/Steve/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Steve/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm
index 35fc24509ee5..8e7fe09304d5 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm
@@ -50,9 +50,9 @@
playsound(src, 'sound/h_sounds/sampler.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Willy/bullet_act()
+/mob/living/simple_mob/horror/Willy/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Willy/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm b/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm
index 5dce24cb5faa..e10d0e557053 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm
@@ -48,9 +48,9 @@
playsound(src, 'sound/h_sounds/mumble.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/bradley/bullet_act()
+/mob/living/simple_mob/horror/bradley/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/bradley/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm b/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm
index 0d4af8cfc580..b051923756db 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm
@@ -47,9 +47,9 @@
playsound(src, 'sound/h_sounds/lynx.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/Sally/bullet_act()
+/mob/living/simple_mob/horror/Sally/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/Sally/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm b/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm
index 8667b99e7321..2c051b4d6301 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm
@@ -48,9 +48,9 @@
playsound(src, 'sound/h_sounds/shitty_tim.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/BigTim/bullet_act()
+/mob/living/simple_mob/horror/BigTim/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/BigTim/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm b/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm
index f6c26f137cf4..60fe08909b2a 100644
--- a/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm
@@ -48,9 +48,9 @@
playsound(src, 'sound/h_sounds/shitty_tim.ogg', 50, 1)
..()
-/mob/living/simple_mob/horror/TinyTim/bullet_act()
+/mob/living/simple_mob/horror/TinyTim/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
- ..()
/mob/living/simple_mob/horror/TinyTim/attack_hand(mob/user, list/params)
playsound(src, 'sound/h_sounds/holla.ogg', 50, 1)
diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm
index fd903d2671f3..bf0caab9bcbd 100644
--- a/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm
@@ -508,17 +508,15 @@
to_chat(user, "This weapon is ineffective, it does no damage.")
visible_message("\The [user] gently taps [src] with \the [O].")
-/mob/living/simple_mob/humanoid/cultist/elite/bullet_act(var/obj/projectile/Proj)
- if(!Proj) return
+/mob/living/simple_mob/humanoid/cultist/elite/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(50))
- visible_message("[Proj] disappears into the mirror world as it hits the shield.")
- if(Proj.firer)
+ visible_message("[proj] disappears into the mirror world as it hits the shield.")
+ if(proj.firer)
if(istype(src.ai_holder, /datum/ai_holder/polaris))
var/datum/ai_holder/polaris/ai_holder = src.ai_holder
- ai_holder.react_to_attack_polaris(Proj.firer)
- return
- else
- ..()
+ ai_holder.react_to_attack_polaris(proj.firer)
+ return PROJECTILE_IMPACT_DELETE
+ return ..()
/mob/living/simple_mob/humanoid/cultist/elite/death()
new /obj/effect/decal/remains/human (src.loc)
diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm
index 59acf04dd4dc..344444ff8224 100644
--- a/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm
@@ -161,7 +161,7 @@
attack_edge = 1
attacktext = list("slashed")
- loot_list = list(/obj/item/melee/energy/sword = 100, /obj/item/shield/energy = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword = 100, /obj/item/shield/transforming/energy = 100)
// They have a shield, so they try to block
/mob/living/simple_mob/humanoid/merc/melee/sword/attackby(var/obj/item/O as obj, var/mob/user as mob)
@@ -177,16 +177,13 @@
to_chat(user, "This weapon is ineffective, it does no damage.")
visible_message("\The [user] gently taps [src] with \the [O].")
-/mob/living/simple_mob/humanoid/merc/melee/sword/bullet_act(var/obj/projectile/Proj)
- if(!Proj) return
+/mob/living/simple_mob/humanoid/merc/melee/sword/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(35))
- visible_message("[src] blocks [Proj] with its shield!")
- if(Proj.firer)
- ai_holder.react_to_attack_polaris(Proj.firer)
- return
- else
- ..()
-
+ visible_message("[src] blocks [proj] with its shield!")
+ if(proj.firer)
+ ai_holder.react_to_attack_polaris(proj.firer)
+ return PROJECTILE_IMPACT_BLOCKED
+ return ..()
////////////////////////////////
// Ranged
@@ -553,15 +550,13 @@
else
visible_message("\The [user] gently taps [src] with \the [O].")
-/mob/living/simple_mob/humanoid/merc/ranged/space/suppressor/bullet_act(var/obj/projectile/Proj)
- if(!Proj) return
+/mob/living/simple_mob/humanoid/merc/ranged/space/suppressor/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(50))
- visible_message("[src] blocks [Proj] with its shield!")
- if(Proj.firer)
- ai_holder.react_to_attack_polaris(Proj.firer)
- return
- else
- ..()
+ visible_message("[src] blocks [proj] with its shield!")
+ if(proj.firer)
+ ai_holder.react_to_attack_polaris(proj.firer)
+ return PROJECTILE_IMPACT_BLOCKED
+ return ..()
////////////////////////////////
// PoI Mercs
@@ -683,7 +678,7 @@
ai_holder_type = /datum/ai_holder/polaris/simple_mob/melee/evasive
corpse = /obj/spawner/corpse/vox/boarder_m
- loot_list = list(/obj/item/melee/energy/sword = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword = 100)
// They're good with the swords? I dunno. I like the idea they can deflect.
/mob/living/simple_mob/humanoid/merc/voxpirate/boarder/attackby(var/obj/item/O, var/mob/user)
@@ -699,15 +694,13 @@
to_chat(user, "This weapon is ineffective, it does no damage.")
visible_message("\The [user] gently taps [src] with \the [O].")
-/mob/living/simple_mob/humanoid/merc/voxpirate/boarder/bullet_act(var/obj/projectile/Proj)
- if(!Proj) return
+/mob/living/simple_mob/humanoid/merc/voxpirate/boarder/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(35))
- visible_message("[src] blocks [Proj] with its sword!")
- if(Proj.firer)
- ai_holder.react_to_attack_polaris(Proj.firer)
- return
- else
- ..()
+ visible_message("[src] blocks [proj] with its sword!")
+ if(proj.firer)
+ ai_holder.react_to_attack_polaris(proj.firer)
+ return PROJECTILE_IMPACT_BLOCKED
+ return ..()
////////////////////////////////
// Vox Ranged
diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm
index d7716b6f4471..640560545917 100644
--- a/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm
@@ -122,7 +122,7 @@
attack_sound = 'sound/weapons/blade1.ogg'
- loot_list = list(/obj/item/melee/energy/sword/pirate = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100)
corpse = /obj/spawner/corpse/pirate/melee_energy
@@ -134,7 +134,7 @@
icon_living = "piratemelee-las-armor"
movement_cooldown = 4
armor_legacy_mob = list(melee = 30, bullet = 20, laser = 20, energy = 5, bomb = 5, bio = 100, rad = 100)
- loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/accessory/armor/armorplate/stab = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/accessory/armor/armorplate/stab = 100)
corpse = /obj/spawner/corpse/pirate/melee_energy_armor
@@ -164,15 +164,13 @@
to_chat(user, "This weapon is ineffective, it does no damage.")
visible_message("\The [user] gently taps [src] with \the [O].")
-/mob/living/simple_mob/humanoid/merc/melee/sword/bullet_act(var/obj/projectile/Proj)
- if(!Proj) return
+/mob/living/simple_mob/humanoid/merc/melee/sword/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(25))
- visible_message("[src] blocks [Proj] with its shield!")
- if(Proj.firer)
- ai_holder.react_to_attack_polaris(Proj.firer)
- return
- else
- ..()
+ visible_message("[src] blocks [proj] with its shield!")
+ if(proj.firer)
+ ai_holder.react_to_attack_polaris(proj.firer)
+ return PROJECTILE_IMPACT_BLOCKED
+ return ..()
// Armored Variant
/mob/living/simple_mob/humanoid/pirate/shield/armored
@@ -352,7 +350,7 @@
armor_legacy_mob = list(melee = 30, bullet = 20, laser = 20, energy = 5, bomb = 5, bio = 100, rad = 100)
- loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/suit/armor/riot/alt = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/suit/armor/riot/alt = 100)
corpse = /obj/spawner/corpse/pirate/mate
@@ -526,13 +524,13 @@
icon_state = "old-piratemelee-las"
icon_living = "old-piratemelee-las"
icon_dead = "old-piratemelee_dead"
- loot_list = list(/obj/item/melee/energy/sword/pirate = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100)
//Armored Variant
/mob/living/simple_mob/humanoid/pirate/las/armored/old
icon_state = "old-piratemelee-las-armor"
icon_living = "old-piratemelee-las-armor"
- loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/suit/armor/material/makeshift = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/suit/armor/material/makeshift = 100)
//Shield Pirate
/mob/living/simple_mob/humanoid/pirate/shield/old
@@ -653,7 +651,7 @@
armor_legacy_mob = list(melee = 30, bullet = 20, laser = 20, energy = 5, bomb = 5, bio = 100, rad = 100)
- loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/suit/pirate = 100)
+ loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/suit/pirate = 100)
///////////////////////////////
diff --git a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm
index b724d80556b8..9e81d5932b36 100644
--- a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm
@@ -45,14 +45,10 @@
return
-/mob/living/simple_mob/illusion/bullet_act(obj/projectile/P)
- if(!P)
- return
-
+/mob/living/simple_mob/illusion/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(realistic)
return ..()
-
- return PROJECTILE_FORCE_MISS
+ return PROJECTILE_IMPACT_PHASE
/mob/living/simple_mob/illusion/attack_hand(mob/user, list/params)
var/mob/living/M = user
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm
index c61bba406f0f..1f2841dc4cd9 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm
@@ -182,11 +182,9 @@
if(L == src)
continue
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.check_shields(damage = 0, damage_source = src, attacker = src, def_zone = null, attack_text = "the leap"))
- // We were blocked.
- continue
+ var/list/shieldcall_result = L.atom_shieldcall(40, BRUTE, MELEE_TIER_MEDIUM, ARMOR_MELEE, NONE, ATTACK_TYPE_MELEE)
+ if(shieldcall_result[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ continue
victim = L
break
@@ -299,7 +297,7 @@
..() // For the poison.
// Force unstealthing if attacked.
-/mob/living/simple_mob/mechanical/cyber_horror/tajaran/bullet_act(obj/projectile/P)
+/mob/living/simple_mob/mechanical/cyber_horror/tajaran/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
break_cloak()
@@ -435,7 +433,10 @@
damage = 15
damage_type = BRUTE
-/obj/projectile/arc/blue_energy/priest/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/arc/blue_energy/priest/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ishuman(target))
var/mob/living/carbon/human/M = target
M.Confuse(rand(3,5))
@@ -500,8 +501,11 @@
icon_state = "plasma3"
rad_power = RAD_INTENSITY_PROJ_ARC_HORROR_PRIEST
-/obj/projectile/arc/radioactive/priest/on_impact(turf/T)
+/obj/projectile/arc/radioactive/priest/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
new /obj/effect/explosion(T)
explosion(T, 0, 1, 4)
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm
index 0f3ce3a23194..ba2be85fb94c 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm
@@ -169,15 +169,23 @@
/obj/projectile/arc/emp_blast
name = "emp blast"
icon_state = "bluespace"
-
-/obj/projectile/arc/emp_blast/on_impact(turf/T)
- empulse(T, 2, 4, 7, 10) // Normal EMP grenade.
- return ..()
-
-/obj/projectile/arc/emp_blast/weak/on_impact(turf/T)
- empulse(T, 1, 2, 3, 4) // Sec EMP grenade.
- return ..()
-
+ var/emp_dev = 2
+ var/emp_heavy = 4
+ var/emp_med = 7
+ var/emp_light = 10
+
+/obj/projectile/arc/emp_blast/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ empulse(target, emp_dev, emp_heavy, emp_med, emp_light) // Normal EMP grenade.
+ return . | PROJECTILE_IMPACT_DELETE
+
+/obj/projectile/arc/emp_blast/weak
+ emp_dev = 1
+ emp_heavy = 2
+ emp_med = 3
+ emp_light = 4
// Fires shots that irradiate the tile hit.
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/radiation
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm
index 7c5cef5f8a12..478bc8fd8720 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm
@@ -227,7 +227,11 @@
name = "rocket"
icon_state = "mortar"
-/obj/projectile/arc/explosive_rocket/on_impact(turf/T)
+/obj/projectile/arc/explosive_rocket/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
new /obj/effect/explosion(T) // Weak explosions don't produce this on their own, apparently.
explosion(T, 0, 0, 2, adminlog = FALSE)
@@ -244,10 +248,13 @@
name = "micro singularity"
icon_state = "bluespace"
-/obj/projectile/arc/microsingulo/on_impact(turf/T)
+/obj/projectile/arc/microsingulo/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
new /obj/effect/temporary_effect/pulse/microsingulo(T)
-
/obj/effect/temporary_effect/pulse/microsingulo
name = "micro singularity"
desc = "It's sucking everything in!"
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm
index 53e146c324fa..3ec87c9c34f2 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm
@@ -90,7 +90,7 @@
if(has_repair_droid)
add_overlay(image(icon = 'icons/mecha/mecha_equipment.dmi', icon_state = "repair_droid"))
-/mob/living/simple_mob/mechanical/mecha/bullet_act()
+/mob/living/simple_mob/mechanical/mecha/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
sparks.start()
@@ -111,11 +111,11 @@
return ..()
*/
-/mob/living/simple_mob/mechanical/mecha/bullet_act(obj/projectile/P)
+/mob/living/simple_mob/mechanical/mecha/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(deflect_chance))
- visible_message(SPAN_WARNING( "\The [P] is deflected by \the [src]'s armor!"))
+ visible_message(SPAN_WARNING( "\The [proj] is deflected by \the [src]'s armor!"))
deflect_sprite()
- return 0
+ return PROJECTILE_IMPACT_BLOCKED
return ..()
/mob/living/simple_mob/mechanical/mecha/proc/deflect_sprite()
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm
index bc6b49659e4c..9781a4618770 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm
@@ -67,14 +67,13 @@
damage = 5 // Getting hit with a launched syringe probably hurts, and makes it at least slightly relevant against synthetics.
var/piercing = FALSE // If true, ignores thick material.
-/obj/projectile/fake_syringe/on_hit(atom/target, blocked = 0, def_zone = null)
+/obj/projectile/fake_syringe/on_impact(atom/target, impact_flags, def_zone, efficiency)
if(isliving(target))
var/mob/living/L = target
if(!L.can_inject(null, null, def_zone, piercing))
- return FALSE
+ return impact_flags | PROJECTILE_IMPACT_BLOCKED
L.custom_pain(SPAN_WARNING("You feel a tiny prick!"), 1, TRUE)
- return ..() // This will add the modifier and return the correct value.
-
+ return ..()
// Fake syringe, which inflicts a long lasting modifier that slowly kills them.
/obj/projectile/fake_syringe/poison
diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm
index 172854fd9d55..56ddbc516b3e 100644
--- a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm
@@ -63,46 +63,45 @@
. = ..()
AddComponent(/datum/component/horror_aura/strong)
-/mob/living/simple_mob/construct/juggernaut/bullet_act(var/obj/projectile/P)
- var/reflectchance = 80 - round(P.damage/3)
- if(prob(reflectchance))
+/mob/living/simple_mob/construct/juggernaut/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ var/reflectchance = 80 - round(proj.damage/3)
+ if(prob(reflectchance) && !istype(src, /mob/living/simple_mob/construct/juggernaut/behemoth))
var/damage_mod = rand(2,4)
- var/projectile_dam_type = P.damage_type
- var/incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3)))
- var/armorcheck = run_armor_check(null, P.damage_flag)
- var/soakedcheck = get_armor_soak(null, P.damage_flag)
- if(!(istype(P, /obj/projectile/energy) || istype(P, /obj/projectile/beam)))
- visible_message("The [P.name] bounces off of [src]'s shell!", \
- "The [P.name] bounces off of [src]'s shell!")
+ var/projectile_dam_type = proj.damage_type
+ var/incoming_damage = (round(proj.damage / damage_mod) - (round((proj.damage / damage_mod) * 0.3)))
+ var/armorcheck = run_armor_check(null, proj.damage_flag)
+ var/soakedcheck = get_armor_soak(null, proj.damage_flag)
+ if(!(istype(proj, /obj/projectile/energy) || istype(proj, /obj/projectile/beam)))
+ visible_message("The [proj.name] bounces off of [src]'s shell!", \
+ "The [proj.name] bounces off of [src]'s shell!")
new /obj/item/material/shard/shrapnel(src.loc)
- if(!(P.damage_type == BRUTE || P.damage_type == BURN))
+ if(!(proj.damage_type == BRUTE || proj.damage_type == BURN))
projectile_dam_type = BRUTE
incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to brute for physical projectiles, though severely decreased.
- apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P)
- return -1 //Doesn't reflect non-beams or non-energy projectiles. They just smack and drop with little to no effect.
+ apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(proj), has_edge(proj), proj)
+ return ..()
else
- visible_message("The [P.name] gets reflected by [src]'s shell!", \
- "The [P.name] gets reflected by [src]'s shell!")
+ visible_message("The [proj.name] gets reflected by [src]'s shell!", \
+ "The [proj.name] gets reflected by [src]'s shell!")
damage_mod = rand(3,5)
- incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3)))
- if(!(P.damage_type == BRUTE || P.damage_type == BURN))
+ incoming_damage = (round(proj.damage / damage_mod) - (round((proj.damage / damage_mod) * 0.3)))
+ if(!(proj.damage_type == BRUTE || proj.damage_type == BURN))
projectile_dam_type = BURN
incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to burn for energy-type projectiles, though severely decreased.
- apply_damage(incoming_damage, P.damage_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P)
+ apply_damage(incoming_damage, proj.damage_type, null, armorcheck, soakedcheck, is_sharp(proj), has_edge(proj), proj)
// Find a turf near or on the original location to bounce to
- if(P.starting)
- var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
- var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
+ if(proj.starting)
+ var/new_x = proj.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
+ var/new_y = proj.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
var/turf/curloc = get_turf(src)
// redirect the projectile
- P.redirect(new_x, new_y, curloc, src)
- P.reflected = 1
-
- return -1 // complete projectile permutation
+ proj.legacy_redirect(new_x, new_y, curloc, src)
+ proj.reflected = 1
- return (..(P))
+ return PROJECTILE_IMPACT_REFLECT
+ return ..()
/*
* The Behemoth. Admin-allowance only, still try to keep it in some guideline of 'Balanced', even if it means Security has to be fully geared to be so.
@@ -138,22 +137,21 @@
/spell/targeted/construct_advanced/slam
)
-/mob/living/simple_mob/construct/juggernaut/behemoth/bullet_act(var/obj/projectile/P)
- var/reflectchance = 80 - round(P.damage/3)
+/mob/living/simple_mob/construct/juggernaut/behemoth/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ var/reflectchance = 80 - round(proj.damage/3)
if(prob(reflectchance))
- visible_message("The [P.name] gets reflected by [src]'s shell!", \
- "The [P.name] gets reflected by [src]'s shell!")
+ visible_message("The [proj.name] gets reflected by [src]'s shell!", \
+ "The [proj.name] gets reflected by [src]'s shell!")
// Find a turf near or on the original location to bounce to
- if(P.starting)
- var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
- var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
+ if(proj.starting)
+ var/new_x = proj.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
+ var/new_y = proj.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
var/turf/curloc = get_turf(src)
// redirect the projectile
- P.redirect(new_x, new_y, curloc, src)
- P.reflected = 1
+ proj.legacy_redirect(new_x, new_y, curloc, src)
+ proj.reflected = 1
- return -1 // complete projectile permutation
-
- return (..(P))
+ return PROJECTILE_IMPACT_REFLECT
+ return ..()
diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm
index 349fd1bfe428..6165ec87ae73 100644
--- a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm
@@ -73,9 +73,11 @@
icon_scale_y = 2
sharp = TRUE
-/obj/projectile/icicle/on_impact(atom/A)
- playsound(get_turf(A), "shatter", 70, 1)
- return ..()
+/obj/projectile/icicle/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ playsound(get_turf(target), "shatter", 70, 1)
/obj/projectile/icicle/get_structure_damage()
return damage / 2 // They're really deadly against mobs, but less effective against solid things.
diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm
index 9bee1fc035f0..8923e1610597 100644
--- a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm
@@ -199,12 +199,11 @@
log_and_message_admins("[src] ignited due to exposure to fire.")
ignite()
-/mob/living/simple_mob/slime/xenobio/dark_purple/bullet_act(var/obj/projectile/P, var/def_zone)
- if(P.damage_type && P.damage_type == BURN && P.damage) // Most bullets won't trigger the explosion, as a mercy towards Security.
- log_and_message_admins("[src] ignited due to bring hit by a burning projectile[P.firer ? " by [key_name(P.firer)]" : ""].")
+/mob/living/simple_mob/slime/xenobio/dark_purple/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(proj.damage_type && proj.damage_type == BURN && proj.damage) // Most bullets won't trigger the explosion, as a mercy towards Security.
+ log_and_message_admins("[src] ignited due to bring hit by a burning projectile[proj.firer ? " by [key_name(proj.firer)]" : ""].")
ignite()
- else
- return ..()
/mob/living/simple_mob/slime/xenobio/dark_purple/attackby(var/obj/item/W, var/mob/user)
if(istype(W) && W.damage_force && W.damtype == BURN)
@@ -285,21 +284,20 @@
/mob/living/simple_mob/slime/xenobio/amber
)
-/mob/living/simple_mob/slime/xenobio/silver/bullet_act(var/obj/projectile/P, var/def_zone)
- if(istype(P,/obj/projectile/beam) || istype(P, /obj/projectile/energy))
- visible_message(SPAN_DANGER("\The [src] reflects \the [P]!"))
+/mob/living/simple_mob/slime/xenobio/silver/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(istype(proj,/obj/projectile/beam) || istype(proj, /obj/projectile/energy))
+ visible_message(SPAN_DANGER("\The [src] reflects \the [proj]!"))
// Find a turf near or on the original location to bounce to
- var/new_x = P.starting.x + pick(0, 0, 0, -1, 1, -2, 2)
- var/new_y = P.starting.y + pick(0, 0, 0, -1, 1, -2, 2)
+ var/new_x = proj.starting.x + pick(0, 0, 0, -1, 1, -2, 2)
+ var/new_y = proj.starting.y + pick(0, 0, 0, -1, 1, -2, 2)
var/turf/curloc = get_turf(src)
// redirect the projectile
- P.redirect(new_x, new_y, curloc, src)
- P.reflected = TRUE
- return PROJECTILE_CONTINUE // complete projectile permutation
- else
- return ..()
+ proj.legacy_redirect(new_x, new_y, curloc, src)
+ proj.reflected = TRUE
+ impact_flags |= PROJECTILE_IMPACT_REFLECT
+ return ..()
// Tier 3
@@ -654,12 +652,11 @@
log_and_message_admins("[src] exploded due to exposure to fire.")
explode()
-/mob/living/simple_mob/slime/xenobio/oil/bullet_act(obj/projectile/P, def_zone)
- if(P.damage_type && P.damage_type == BURN && P.damage) // Most bullets won't trigger the explosion, as a mercy towards Security.
- log_and_message_admins("[src] exploded due to bring hit by a burning projectile[P.firer ? " by [key_name(P.firer)]" : ""].")
+/mob/living/simple_mob/slime/xenobio/oil/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(proj.damage_type && proj.damage_type == BURN && proj.damage) // Most bullets won't trigger the explosion, as a mercy towards Security.
+ log_and_message_admins("[src] exploded due to bring hit by a burning projectile[proj.firer ? " by [key_name(proj.firer)]" : ""].")
explode()
- else
- return ..()
/mob/living/simple_mob/slime/xenobio/oil/attackby(obj/item/W, mob/living/user)
if(istype(W) && W.damage_force && W.damtype == BURN)
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm
index e8bef7354c06..0313e5be44b2 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm
@@ -136,16 +136,13 @@
to_chat(user, "This weapon is ineffective, it does no damage.")
visible_message("\The [user] gently taps [src] with \the [O].")
-/mob/living/simple_mob/vore/aggressive/corrupthound/sword/bullet_act(var/obj/projectile/Proj)
- if(!Proj) return
+/mob/living/simple_mob/vore/aggressive/corrupthound/sword/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(prob(35))
- visible_message("[src] deflects [Proj] with its sword tail!")
- if(Proj.firer)
- ai_holder.react_to_attack_polaris(Proj.firer)
- return
- else
- ..()
-
+ visible_message("[src] deflects [proj] with its sword tail!")
+ if(proj.firer)
+ ai_holder.react_to_attack_polaris(proj.firer)
+ return PROJECTILE_IMPACT_BLOCKED
+ return ..()
/mob/living/simple_mob/vore/aggressive/corrupthound/isSynthetic()
return TRUE
diff --git a/code/modules/mob/mob-damage.dm b/code/modules/mob/mob-damage.dm
new file mode 100644
index 000000000000..f797f95b8702
--- /dev/null
+++ b/code/modules/mob/mob-damage.dm
@@ -0,0 +1,7 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Damage Instance Handling *//
+
+/mob/inflict_damage_instance(SHIELDCALL_PROC_HEADER)
+ return
diff --git a/code/modules/mob/mob-defense.dm b/code/modules/mob/mob-defense.dm
new file mode 100644
index 000000000000..632159f429c6
--- /dev/null
+++ b/code/modules/mob/mob-defense.dm
@@ -0,0 +1,121 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Armor Handling *//
+
+/**
+ * An override of /atom/proc/run_armorcalls(), with zone filter capability.
+ *
+ * This is what you should override, instead of the check/run procs.
+ *
+ * * At current moment, due to expensiveness reasons, not providing filter_zone will result in only SHIELDCALL_ARG_DAMAGE being modified.
+ *
+ * @params
+ * * shieldcall_args - passed in shieldcall args list to modify
+ * * fake_attack - are we just checking armor?
+ * * filter_zone - confine to a certain hit zone. this is **required** for full processing, otherwise we just check overall damage.
+ */
+/mob/run_armorcalls(list/shieldcall_args, fake_attack, filter_zone)
+ ..() // perform default /atom level
+
+/**
+ * Generic, low-level armor check for inbound attacks
+ *
+ * * This will filter armor by zone.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/check_mob_armor(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, TRUE, hit_zone) // by default, use atom/var/armor on ourselves
+
+/**
+ * Generic, low-level armor processing for inbound attacks
+ *
+ * * This will filter armor by zone.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/run_mob_armor(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, FALSE, hit_zone) // by default, use atom/var/armor on ourselves
+
+/**
+ * Checks the average armor for a full-body attack.
+ *
+ * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/check_mob_overall_armor(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, TRUE) // by default, use atom/var/armor on ourselves
+
+/**
+ * Checks the average armor for a full-body attack.
+ *
+ * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/run_mob_overall_armor(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, FALSE) // by default, use atom/var/armor on ourselves
+
+//* Defense Handling *//
+
+/**
+ * Generic, low-level defense check for inbound attacks
+ *
+ * * This will filter defense by zone.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/check_mob_defense(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, TRUE, hit_zone) // by default, use atom/var/armor on ourselves
+ run_shieldcalls(args, TRUE)
+
+/**
+ * Generic, low-level defense processing for inbound attacks
+ *
+ * * This will filter defense by zone.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/run_mob_defense(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, FALSE, hit_zone) // by default, use atom/var/armor on ourselves
+ run_shieldcalls(args, FALSE)
+
+/**
+ * Checks the average defense for a full-body attack.
+ *
+ * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/check_mob_overall_defense(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, TRUE) // by default, use atom/var/armor on ourselves
+ run_shieldcalls(args, TRUE)
+
+/**
+ * Checks the average defense for a full-body attack.
+ *
+ * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick.
+ * * This operates like [atom_shieldcall()].
+ *
+ * @return modified argument list, with SHIELDCALL_ARG_* defines as indices.
+ */
+/mob/proc/run_mob_overall_defense(SHIELDCALL_PROC_HEADER)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ run_armorcalls(args, FALSE) // by default, use atom/var/armor on ourselves
+ run_shieldcalls(args, FALSE)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 74257bf1b8e0..6c4eccc05ae3 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -427,9 +427,6 @@
. += M
. -= src
-/mob/proc/ret_grab(obj/effect/list_container/mobl/L as obj, flag)
- return
-
/**
* Get the notes of this mob
*
diff --git a/code/modules/mob/movement.dm b/code/modules/mob/movement.dm
index a42342538f4a..c02dd0d4d7ec 100644
--- a/code/modules/mob/movement.dm
+++ b/code/modules/mob/movement.dm
@@ -47,9 +47,6 @@
. = ..()
if(.)
return
- if(istype(mover, /obj/projectile))
- var/obj/projectile/P = mover
- return !P.can_hit_target(src, P.permutated, src == P.original, TRUE)
// thrown things still hit us even when nondense
if(can_cross_under(mover))
return TRUE
@@ -61,8 +58,11 @@
return TRUE
return ..()
+/**
+ * Can something cross under us without being blocked by us?
+ */
/mob/proc/can_cross_under(atom/movable/mover)
- return !mover.density && !mover.throwing
+ return !mover.density && !mover.throwing && !istype(mover, /obj/projectile)
/**
* Toggle the move intent of the mob
@@ -284,7 +284,7 @@
//Something with pulling things
if(locate(/obj/item/grab, mob))
add_delay_grab = 7
- var/list/grabbed = mob.ret_grab()
+ var/list/grabbed = mob.get_grabbing_recursive() + src // im fucking screaming; this is because old code always considers self as grabbed.
if(grabbed)
if(grabbed.len == 2)
grabbed -= mob
diff --git a/code/modules/modular_computers/computers/modular_computer/damage.dm b/code/modules/modular_computers/computers/modular_computer/damage.dm
index 64d34486891a..32da9c40a242 100644
--- a/code/modules/modular_computers/computers/modular_computer/damage.dm
+++ b/code/modules/modular_computers/computers/modular_computer/damage.dm
@@ -51,12 +51,12 @@
* "Burn" damage is equally strong against internal components and exterior casing
* "Brute" damage mostly damages the casing.
*/
-/obj/item/modular_computer/bullet_act(obj/projectile/Proj)
+/obj/item/modular_computer/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
- switch(Proj.damage_type)
+ switch(proj.damage_type)
if(BRUTE)
- take_damage_legacy(Proj.damage, Proj.damage / 2)
+ take_damage_legacy(proj.damage, proj.damage / 2)
if(HALLOSS)
- take_damage_legacy(Proj.damage, Proj.damage / 3, 0)
+ take_damage_legacy(proj.damage, proj.damage / 3, 0)
if(BURN)
- take_damage_legacy(Proj.damage, Proj.damage / 1.5)
+ take_damage_legacy(proj.damage, proj.damage / 1.5)
diff --git a/code/modules/multiz/atoms.dm b/code/modules/multiz/atoms.dm
index 9aa67f7f61cf..5abbda59d9a8 100644
--- a/code/modules/multiz/atoms.dm
+++ b/code/modules/multiz/atoms.dm
@@ -23,4 +23,4 @@
* new_loc - new turf
*/
/atom/proc/z_pass_out(atom/movable/AM, dir, turf/new_loc)
- return !AM || Uncross(AM)
+ return !AM || Uncross(AM, new_loc)
diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm
index 650e4f85d0dc..907f3d5a4106 100644
--- a/code/modules/multiz/movement.dm
+++ b/code/modules/multiz/movement.dm
@@ -314,7 +314,7 @@
return 1
var/atom/A = find_fall_target(oldloc, landing)
- if(special_fall_handle(A) || !A || !A.check_impact(src))
+ if(special_fall_handle(A) || !A || !A.check_z_impact(src))
return
var/mob/drop_mob = locate(/mob, landing)
if(drop_mob && !(drop_mob == src) && ismob(drop_mob) && isliving(drop_mob)) //Shitload of checks. This is because the game finds various ways to screw me over.
@@ -371,16 +371,14 @@
return TRUE
return prevent_z_fall(falling_atom, 0, NONE) & (FALL_TERMINATED | FALL_BLOCKED)
-
/**
* If you are hit: how is it handled.
* Return TRUE if the generic fall_impact should be called.
* Return FALSE if you handled it yourself or if there's no effect from hitting you.
*/
-/atom/proc/check_impact(atom/movable/falling_atom)
+/atom/proc/check_z_impact(atom/movable/falling_atom)
return TRUE
-
/**
* Called by CheckFall when we actually hit something. Various Vars will be described below.
* hit_atom is the thing we fall on.
diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm
index a4261ffa6b4a..e35652e4e2bd 100644
--- a/code/modules/multiz/turf.dm
+++ b/code/modules/multiz/turf.dm
@@ -113,9 +113,6 @@
return TRUE // impact!
return ..()
-/turf/check_impact(atom/movable/falling_atom)
- return TRUE
-
//* lookups
/turf/proc/above()
diff --git a/code/modules/organs/external/external.dm b/code/modules/organs/external/external.dm
index a7861e3289a5..ded3e53b1096 100644
--- a/code/modules/organs/external/external.dm
+++ b/code/modules/organs/external/external.dm
@@ -1269,7 +1269,7 @@ Note that amputating the affected organ does in fact remove the infection from t
/obj/item/organ/external/proc/embed(var/obj/item/W, var/silent = 0)
if(!owner || loc != owner)
return
- if(owner.species.species_flags & IS_SLIME)
+ if(owner.species.reagent_tag == IS_SLIME)
create_wound( CUT, 15 ) //fixes proms being bugged into paincrit;instead whatever would embed now just takes a chunk out
src.visible_message("[owner] has been seriously wounded by [W]!")
W.add_blood(owner)
diff --git a/code/modules/organs/internal/augment/armmounted.dm b/code/modules/organs/internal/augment/armmounted.dm
index b6e32224a72a..c4b1c99dd71c 100644
--- a/code/modules/organs/internal/augment/armmounted.dm
+++ b/code/modules/organs/internal/augment/armmounted.dm
@@ -70,7 +70,7 @@
/obj/item/organ/internal/augment/armmounted/hand/sword
name = "energy blade implant"
- integrated_object_type = /obj/item/melee/energy/sword
+ integrated_object_type = /obj/item/melee/transforming/energy/sword
/*
* Shoulder augment.
diff --git a/code/modules/overmap/legacy/ships/computers/sensors.dm b/code/modules/overmap/legacy/ships/computers/sensors.dm
index ad0e255d7373..f142942fcc8b 100644
--- a/code/modules/overmap/legacy/ships/computers/sensors.dm
+++ b/code/modules/overmap/legacy/ships/computers/sensors.dm
@@ -219,9 +219,9 @@
else if(health < max_health * 0.75)
. += "It shows signs of damage!"
-/obj/machinery/shipsensors/bullet_act(var/obj/projectile/Proj)
- take_damage_legacy(Proj.get_structure_damage())
- ..()
+/obj/machinery/shipsensors/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ take_damage_legacy(proj.get_structure_damage())
/obj/machinery/shipsensors/proc/toggle()
if(!use_power && (health == 0 || !in_vacuum()))
diff --git a/code/modules/power/antimatter/control.dm b/code/modules/power/antimatter/control.dm
index 75ad385a6001..f65362e5f7ef 100644
--- a/code/modules/power/antimatter/control.dm
+++ b/code/modules/power/antimatter/control.dm
@@ -8,6 +8,7 @@
use_power = USE_POWER_IDLE
idle_power_usage = 100
active_power_usage = 1000
+ integrity_flags = INTEGRITY_INDESTRUCTIBLE
var/list/obj/machinery/am_shielding/linked_shielding
var/list/obj/machinery/am_shielding/linked_cores
@@ -116,12 +117,10 @@
check_stability()
return
-
-/obj/machinery/power/am_control_unit/bullet_act(var/obj/projectile/Proj)
- if(Proj.damage_flag != "bullet")
- stability -= Proj.damage
- return 0
-
+/obj/machinery/power/am_control_unit/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(proj.damage_flag != ARMOR_BULLET)
+ stability -= proj.damage
+ return ..()
/obj/machinery/power/am_control_unit/power_change()
..()
diff --git a/code/modules/power/antimatter/shielding.dm b/code/modules/power/antimatter/shielding.dm
index 8a8784bc5ab9..d7997462c6da 100644
--- a/code/modules/power/antimatter/shielding.dm
+++ b/code/modules/power/antimatter/shielding.dm
@@ -98,12 +98,11 @@
check_stability()
return
+/obj/machinery/am_shielding/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
-/obj/machinery/am_shielding/bullet_act(var/obj/projectile/Proj)
- if(Proj.damage_flag != "bullet")
- stability -= Proj.damage/2
- return 0
-
+ if(proj.damage_flag != "bullet")
+ stability -= proj.damage/2
/obj/machinery/am_shielding/update_icon()
cut_overlays()
diff --git a/code/modules/power/fusion/core/_core.dm b/code/modules/power/fusion/core/_core.dm
index eff1ef5682ba..2ff85f772b77 100644
--- a/code/modules/power/fusion/core/_core.dm
+++ b/code/modules/power/fusion/core/_core.dm
@@ -87,9 +87,10 @@ var/list/fusion_cores = list()
owned_field.AddParticles(name, quantity)
. = 1
-/obj/machinery/power/fusion_core/bullet_act(var/obj/projectile/Proj)
+/obj/machinery/power/fusion_core/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(owned_field)
- . = owned_field.bullet_act(Proj)
+ return proj.impact_redirect(owned_field, args)
+ return ..()
/obj/machinery/power/fusion_core/proc/set_strength(var/value)
value = clamp(value, MIN_FIELD_STR, MAX_FIELD_STR)
diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/power/fusion/core/core_field.dm
index 35daf97aa660..ec9c2ab34109 100644
--- a/code/modules/power/fusion/core/core_field.dm
+++ b/code/modules/power/fusion/core/core_field.dm
@@ -26,7 +26,6 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY)
var/obj/machinery/power/fusion_core/owned_core
var/list/dormant_reactant_quantities = list()
- var/list/particle_catchers = list()
var/list/ignore_types
@@ -60,67 +59,24 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY)
if(!owned_core)
qdel(src)
id_tag = owned_core.id_tag
- //create the gimmicky things to handle field collisions
- var/obj/effect/fusion_particle_catcher/catcher
-
- catcher = new (locate(src.x,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(1)
- particle_catchers.Add(catcher)
-
- catcher = new (locate(src.x-1,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(3)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x+1,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(3)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x,src.y+1,src.z))
- catcher.parent = src
- catcher.SetSize(3)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x,src.y-1,src.z))
- catcher.parent = src
- catcher.SetSize(3)
- particle_catchers.Add(catcher)
-
- catcher = new (locate(src.x-2,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(5)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x+2,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(5)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x,src.y+2,src.z))
- catcher.parent = src
- catcher.SetSize(5)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x,src.y-2,src.z))
- catcher.parent = src
- catcher.SetSize(5)
- particle_catchers.Add(catcher)
-
- catcher = new (locate(src.x-3,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(7)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x+3,src.y,src.z))
- catcher.parent = src
- catcher.SetSize(7)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x,src.y+3,src.z))
- catcher.parent = src
- catcher.SetSize(7)
- particle_catchers.Add(catcher)
- catcher = new (locate(src.x,src.y-3,src.z))
- catcher.parent = src
- catcher.SetSize(7)
- particle_catchers.Add(catcher)
START_PROCESSING(SSobj, src)
+/obj/effect/fusion_em_field/bullet_act(obj/projectile/proj, impact_flags, def_zone, efficiency)
+ if(!(proj.projectile_type & PROJECTILE_TYPE_BEAM))
+ return ..()
+ AddEnergy(proj.damage)
+ update_icon()
+ impact_flags |= PROJECTILE_IMPACT_DELETE
+ return ..()
+
+/obj/effect/fusion_em_field/CanAllowThrough(atom/movable/mover, turf/target)
+ if(istype(mover, /obj/projectile))
+ var/obj/projectile/proj = mover
+ if(proj.projectile_type & PROJECTILE_TYPE_BEAM)
+ return FALSE
+ return TRUE
+
/obj/effect/fusion_em_field/process(delta_time)
//make sure the field generator is still intact
if(!owned_core || QDELETED(owned_core))
@@ -340,8 +296,20 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY)
tick_instability += rand(30,50)
AM.emp_act(empsev)
-/obj/effect/fusion_em_field/proc/change_size(var/newsize = 1)
- var/changed = 0
+/**
+ * Immediately change the field's size.
+ *
+ * * This is the radius of the field.
+ * * This will change `locs` of this field, and its bounds!
+ */
+/obj/effect/fusion_em_field/proc/change_size(new_size = 1)
+ ASSERT(!((new_size - 1) % 2))
+ ASSERT(new_size <= 13)
+
+ if(new_size == size)
+ return FALSE
+
+ //! LEGACY
var/static/list/size_to_icon = list(
"3" = 'icons/effects/96x96.dmi',
"5" = 'icons/effects/160x160.dmi',
@@ -351,19 +319,27 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY)
"13" = 'icons/effects/416x416.dmi'
)
- if( ((newsize-1)%2==0) && (newsize<=13) )
- icon = 'icons/obj/machines/power/fusion.dmi'
- if(newsize>1)
- icon = size_to_icon["[newsize]"]
- icon_state = "emfield_s[newsize]"
- pixel_x = ((newsize-1) * -16) * PIXEL_MULTIPLIER
- pixel_y = ((newsize-1) * -16) * PIXEL_MULTIPLIER
- size = newsize
- changed = newsize
+ icon = 'icons/obj/machines/power/fusion.dmi'
- for(var/obj/effect/fusion_particle_catcher/catcher in particle_catchers)
- catcher.UpdateSize()
- return changed
+ if(new_size>1)
+ icon = size_to_icon["[new_size]"]
+ icon_state = "emfield_s[new_size]"
+ //! END
+
+ size = new_size
+
+ // this will shift locs!
+ var/new_additional_radius = (new_size - 1) / 2
+ bound_x = -new_additional_radius * WORLD_ICON_SIZE
+ bound_y = -new_additional_radius * WORLD_ICON_SIZE
+ bound_width = new_additional_radius * 2 * WORLD_ICON_SIZE + WORLD_ICON_SIZE
+ bound_height = new_additional_radius * 2 * WORLD_ICON_SIZE + WORLD_ICON_SIZE
+
+ // shift visuals
+ pixel_x = -new_additional_radius * WORLD_ICON_SIZE
+ pixel_y = -new_additional_radius * WORLD_ICON_SIZE
+
+ return TRUE
//the !!fun!! part
/obj/effect/fusion_em_field/proc/React()
@@ -480,18 +456,12 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY)
/obj/effect/fusion_em_field/Destroy()
set_light(0)
RadiateAll()
- for(var/obj/effect/fusion_particle_catcher/catcher in particle_catchers)
- qdel(catcher)
if(owned_core)
owned_core.owned_field = null
owned_core = null
STOP_PROCESSING(SSobj, src)
. = ..()
-/obj/effect/fusion_em_field/bullet_act(var/obj/projectile/Proj)
- AddEnergy(Proj.damage)
- update_icon()
- return 0
//All procs below this point are called in _core.dm, starting at line 41.
//Stability monitoring. Gives radio annoucements if field stability is below 80%
/obj/effect/fusion_em_field/proc/stability_monitor()
diff --git a/code/modules/power/fusion/fusion_particle_catcher.dm b/code/modules/power/fusion/fusion_particle_catcher.dm
deleted file mode 100644
index ea8ae497ff57..000000000000
--- a/code/modules/power/fusion/fusion_particle_catcher.dm
+++ /dev/null
@@ -1,43 +0,0 @@
-/obj/effect/fusion_particle_catcher
- icon = 'icons/effects/effects.dmi'
- density = 1
- anchored = 1
- invisibility = 101
- var/obj/effect/fusion_em_field/parent
- var/mysize = 0
-
- light_color = COLOR_BLUE
-
-/obj/effect/fusion_particle_catcher/Destroy()
- . =..()
- parent.particle_catchers -= src
- parent = null
-
-/obj/effect/fusion_particle_catcher/proc/SetSize(var/newsize)
- name = "collector [newsize]"
- mysize = newsize
- UpdateSize()
-
-/obj/effect/fusion_particle_catcher/proc/AddParticles(var/name, var/quantity = 1)
- if(parent && parent.size >= mysize)
- parent.AddParticles(name, quantity)
- return 1
- return 0
-
-/obj/effect/fusion_particle_catcher/proc/UpdateSize()
- if(parent.size >= mysize)
- density = 1
- name = "collector [mysize] ON"
- else
- density = 0
- name = "collector [mysize] OFF"
-
-/obj/effect/fusion_particle_catcher/bullet_act(var/obj/projectile/Proj)
- parent.AddEnergy(Proj.damage)
- update_icon()
- return 0
-
-/obj/effect/fusion_particle_catcher/CanAllowThrough(atom/movable/mover, turf/target)
- if(istype(mover, /obj/effect/accelerated_particle) || istype(mover, /obj/projectile/beam))
- return !density
- return TRUE
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index c74746180773..fc548866d6b2 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -607,10 +607,10 @@
tesla_zap(src, 5, power_gen * 50)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), get_turf(src), 2, 3, 4, 8), 100) // Not a normal explosion.
-/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/Proj)
+/obj/machinery/power/rtg/abductor/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
. = ..()
- if(!going_kaboom && istype(Proj) && !Proj.nodamage && ((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)))
- log_and_message_admins("[ADMIN_LOOKUPFLW(Proj.firer)] triggered an Abductor Core explosion at [x],[y],[z] via projectile.")
+ if(!going_kaboom && istype(proj) && !proj.nodamage && ((proj.damage_type == BURN) || (proj.damage_type == BRUTE)))
+ log_and_message_admins("[ADMIN_LOOKUPFLW(proj.firer)] triggered an Abductor Core explosion at [x],[y],[z] via projectile.")
asplod()
/obj/machinery/power/rtg/abductor/attack_hand(mob/user, list/params)
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm
index 2233e458f083..6a80497c66b0 100644
--- a/code/modules/power/singularity/field_generator.dm
+++ b/code/modules/power/singularity/field_generator.dm
@@ -21,7 +21,7 @@ field_generator power level display
anchored = 0
density = 1
use_power = USE_POWER_OFF
-
+ armor = /datum/armor/object/heavy
worth_intrinsic = 350
var/const/num_power_levels = 6 // Total number of power level icon has
var/Varedit_start = 0
@@ -152,23 +152,20 @@ field_generator power level display
..()
return
-
/obj/machinery/field_generator/emp_act()
return 0
-/obj/machinery/field_generator/bullet_act(var/obj/projectile/Proj)
- if(istype(Proj, /obj/projectile/beam))
- power += Proj.damage * EMITTER_DAMAGE_POWER_TRANSFER
+/obj/machinery/field_generator/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(istype(proj, /obj/projectile/beam))
+ power += proj.damage * EMITTER_DAMAGE_POWER_TRANSFER
update_icon()
- return 0
-
+ return PROJECTILE_IMPACT_DELETE
+ return ..()
/obj/machinery/field_generator/Destroy()
src.cleanup()
. = ..()
-
-
/obj/machinery/field_generator/proc/turn_off()
active = 0
warming_up = 0
diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm
index 3c6aadbaf710..c19fd665569b 100644
--- a/code/modules/power/singularity/particle_accelerator/particle.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle.dm
@@ -5,8 +5,11 @@
desc = "Small things moving very fast."
icon = 'icons/obj/machines/particle_accelerator2.dmi'
icon_state = "particle1"//Need a new icon for this
- anchored = 1
- density = 1
+ anchored = TRUE
+ density = TRUE
+ generic_canpass = FALSE
+ plane = MOB_PLANE
+ layer = ABOVE_MOB_LAYER
var/movement_range = 10
var/energy = 10 //energy in eV
var/mega_energy = 0 //energy in MeV
@@ -18,6 +21,10 @@
var/turf/source
var/movetotarget = 1
+/obj/effect/accelerated_particle/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
+ SHOULD_CALL_PARENT(FALSE)
+ return TRUE
+
/obj/effect/accelerated_particle/weak
icon_state = "particle0"
movement_range = 8
@@ -39,18 +46,44 @@
energy = -20
/obj/effect/accelerated_particle/Initialize(mapload, dir = SOUTH)
+ setDir(dir)
. = ..()
- src.loc = loc
- src.setDir(dir)
- INVOKE_ASYNC(src, PROC_REF(move), 1)
+ START_PROCESSING(SSprocess_5fps, src)
+
+/obj/effect/accelerated_particle/Destroy()
+ STOP_PROCESSING(SSprocess_5fps, src)
+ return ..()
+
+/obj/effect/accelerated_particle/process(delta_time)
+ var/old_z = z
+ var/turf/where_to = get_step(src, dir)
+ if(!where_to)
+ qdel(src)
+ return
+ if(!Move(where_to))
+ if(!QDELETED(src))
+ qdel(src)
+ return
+ else
+ return PROCESS_KILL
+ // being deleted changes Z so that's also an implicit qdeleted() check.
+ if(z != old_z)
+ if(!QDELETED(src))
+ qdel(src)
+ return
+ else
+ return PROCESS_KILL
/obj/effect/accelerated_particle/Moved()
. = ..()
if(!isturf(loc))
return
- for(var/atom/movable/AM as anything in loc.contents)
+ for(var/atom/movable/AM as anything in loc)
do_the_funny(AM)
+ if(QDELETED(src))
+ return
+// todo: particle_accelerator_act() or something
/obj/effect/accelerated_particle/proc/do_the_funny(atom/A)
if (A)
if(ismob(A))
@@ -58,24 +91,14 @@
if((istype(A,/obj/machinery/the_singularitygen))||(istype(A,/obj/singularity/))||(istype(A, /obj/machinery/particle_smasher)))
A:energy += energy
//R-UST port
- else if(istype(A,/obj/machinery/power/fusion_core))
- var/obj/machinery/power/fusion_core/collided_core = A
- if(particle_type && particle_type != "neutron")
- if(collided_core.AddParticles(particle_type, 1 + additional_particles))
- collided_core.owned_field.plasma_temperature += mega_energy
- collided_core.owned_field.energy += energy
- loc = null
- else if(istype(A, /obj/effect/fusion_particle_catcher))
- var/obj/effect/fusion_particle_catcher/PC = A
+ if(istype(A, /obj/effect/fusion_em_field))
+ var/obj/effect/fusion_em_field/field = A
if(particle_type && particle_type != "neutron")
- if(PC.parent.owned_core.AddParticles(particle_type, 1 + additional_particles))
- PC.parent.plasma_temperature += mega_energy
- PC.parent.energy += energy
- loc = null
-
-
-/obj/effect/accelerated_particle/legacy_ex_act(severity)
- qdel(src)
+ if(field.owned_core.AddParticles(particle_type, 1 + additional_particles))
+ field.plasma_temperature += mega_energy
+ field.energy += energy
+ qdel(src)
+ return
/obj/effect/accelerated_particle/singularity_act()
return
@@ -84,26 +107,3 @@
if(!istype(M))
return
M.afflict_radiation(energy * 5, TRUE)
-
-/obj/effect/accelerated_particle/proc/move(var/lag)
- var/turf/new_target
- if(target)
- if(movetotarget)
- new_target = get_step_towards(src, target)
- if(get_dist(src,new_target) < 1)
- movetotarget = 0
- else
- new_target = get_step_away(src, source)
- else
- new_target = get_step(src, dir)
- if(new_target)
- forceMove(new_target)
- else
- qdel(src)
- return
- movement_range--
- if(movement_range <= 0)
- qdel(src)
- else
- sleep(lag)
- move(lag)
diff --git a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
index a98c670f46a3..fb7500fcf428 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
@@ -10,6 +10,7 @@
anchored = 0
density = 1
use_power = USE_POWER_OFF
+ armor = /datum/armor/object/heavy
var/image/material_layer // Holds the image used for the filled overlay.
var/image/material_glow // Holds the image used for the glow overlay.
@@ -127,11 +128,12 @@
else
set_light(0, 0, "#FFFFFF")
-/obj/machinery/particle_smasher/bullet_act(var/obj/projectile/Proj)
- if(istype(Proj, /obj/projectile/beam))
- if(Proj.damage >= 50)
+/obj/machinery/particle_smasher/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(istype(proj, /obj/projectile/beam))
+ if(proj.damage >= 50)
TryCraft()
- return 0
+ return PROJECTILE_IMPACT_DELETE
+ return ..()
/obj/machinery/particle_smasher/process(delta_time)
if(!src.anchored) // Rapidly loses focus.
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 0b12ef9396ea..71ed027c49ac 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -64,8 +64,8 @@ GLOBAL_LIST_BOILERPLATE(all_singularities, /obj/singularity)
if(3)
energy -= round(((energy+1)/4),1)
-/obj/singularity/bullet_act(obj/projectile/P)
- return 0 //Will there be an impact? Who knows. Will we see it? No.
+/obj/singularity/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ return PROJECTILE_IMPACT_DELETE
/obj/singularity/Bump(atom/A)
consume(A)
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 599a8ed31d96..63c73826cfaf 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -361,25 +361,26 @@
return 1
-
-/obj/machinery/power/supermatter/bullet_act(var/obj/projectile/Proj)
+/obj/machinery/power/supermatter/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
var/turf/L = loc
- if(!istype(L)) // We don't run process() when we are in space
- return 0 // This stops people from being able to really power up the supermatter
- // Then bring it inside to explode instantly upon landing on a valid turf.
+ // We don't run process() when we are in space
+ // This stops people from being able to really power up the supermatter
+ // Then bring it inside to explode instantly upon landing on a valid turf.
+ if(!istype(L))
+ return PROJECTILE_IMPACT_DELETE
var/added_energy
var/added_damage
- var/proj_damage = Proj.get_structure_damage()
- if(istype(Proj, /obj/projectile/beam))
+ var/proj_damage = proj.get_structure_damage()
+ if(istype(proj, /obj/projectile/beam))
added_energy = proj_damage * config_bullet_energy * CHARGING_FACTOR / POWER_FACTOR
power += added_energy
else
added_damage = proj_damage * config_bullet_energy
damage += added_damage
if(added_energy || added_damage)
- investigate_log("Hit by \"[Proj.name]\". +[added_energy] Energy, +[added_damage] Damage.", INVESTIGATE_SUPERMATTER)
- return 0
+ investigate_log("Hit by \"[proj.name]\". +[added_energy] Energy, +[added_damage] Damage.", INVESTIGATE_SUPERMATTER)
+ return PROJECTILE_IMPACT_DELETE
/obj/machinery/power/supermatter/attack_robot(mob/user as mob)
if(Adjacent(user))
@@ -465,20 +466,25 @@
Consume(W)
-/obj/machinery/power/supermatter/Bumped(atom/AM as mob|obj)
- if(istype(AM, /obj/effect))
- return
- if(istype(AM, /mob/living))
- var/mob/living/M = AM
- var/datum/gender/T = GLOB.gender_datums[M.get_visible_gender()]
- AM.visible_message("\The [AM] slams into \the [src] inducing a resonance... [T.his] body starts to glow and catch flame before flashing into ash.",\
- "You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\
- "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat.")
- else if(!grav_pulling) //To prevent spam, detonating supermatter does not indicate non-mobs being destroyed
- AM.visible_message("\The [AM] smacks into \the [src] and rapidly flashes to ash.",\
- "You hear a loud crack as you are washed with a wave of heat.")
-
- Consume(AM)
+/obj/machinery/power/supermatter/Bumped(atom/movable/bumped_atom)
+ . = ..()
+ var/their_loc = bumped_atom.loc
+ spawn(0)
+ if(their_loc != bumped_atom.loc)
+ return
+ if(istype(bumped_atom, /obj/effect))
+ return
+ if(istype(bumped_atom, /mob/living))
+ var/mob/living/M = bumped_atom
+ var/datum/gender/T = GLOB.gender_datums[M.get_visible_gender()]
+ bumped_atom.visible_message("\The [bumped_atom] slams into \the [src] inducing a resonance... [T.his] body starts to glow and catch flame before flashing into ash.",\
+ "You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\
+ "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat.")
+ else if(!grav_pulling) //To prevent spam, detonating supermatter does not indicate non-mobs being destroyed
+ bumped_atom.visible_message("\The [bumped_atom] smacks into \the [src] and rapidly flashes to ash.",\
+ "You hear a loud crack as you are washed with a wave of heat.")
+
+ Consume(bumped_atom)
/obj/machinery/power/supermatter/proc/Consume(var/mob/living/user)
// todo: rework the fucking supermatter so we don't need this
diff --git a/code/modules/projectiles/README.md b/code/modules/projectiles/README.md
new file mode 100644
index 000000000000..845e6a2075b1
--- /dev/null
+++ b/code/modules/projectiles/README.md
@@ -0,0 +1,8 @@
+# Projectiles
+
+The projectiles module contains the following and the systems that support them:
+
+* Guns
+* Ammunition
+* Magazines
+* Projectiles
diff --git a/code/modules/projectiles/ammunition/ammo_caliber.dm b/code/modules/projectiles/ammunition/ammo_caliber.dm
index 4bc9635dff49..a16391347c3c 100644
--- a/code/modules/projectiles/ammunition/ammo_caliber.dm
+++ b/code/modules/projectiles/ammunition/ammo_caliber.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 silicons *//
+//* Copyright (c) 2024 Citadel Station Developers *//
GLOBAL_LIST_INIT(calibers, init_calibers())
diff --git a/code/modules/projectiles/ammunition/calibers/special/dart.dm b/code/modules/projectiles/ammunition/calibers/special/dart.dm
index 118dac8ddb33..a4772c10cd30 100644
--- a/code/modules/projectiles/ammunition/calibers/special/dart.dm
+++ b/code/modules/projectiles/ammunition/calibers/special/dart.dm
@@ -63,8 +63,13 @@
. = ..()
create_reagents(reagent_amount)
-/obj/projectile/bullet/chemdart/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null)
- if(blocked < 2 && isliving(target))
+/obj/projectile/bullet/chemdart/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ if((. & PROJECTILE_IMPACT_BLOCKED) || (efficiency < 0.95))
+ return
+ if(isliving(target))
var/mob/living/L = target
if(L.can_inject(target_zone=def_zone))
reagents.trans_to_mob(L, reagent_amount, CHEM_INJECT)
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 76512e496447..8fb6cf408a30 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -55,6 +55,15 @@
zoomdevicename = "scope"
inhand_default_type = INHAND_DEFAULT_ICON_GUNS
+ //* Accuracy, Dispersion, Instability *//
+
+ /// entirely disable baymiss on fired projectiles
+ ///
+ /// * this is a default value; set to null by default to have the projectile's say.
+ var/accuracy_disabled = null
+
+ // 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
@@ -321,9 +330,10 @@
if(!istype(A))
return ..()
if(user.a_intent == INTENT_HARM) //point blank shooting
- if (A == user && user.zone_sel.selecting == O_MOUTH && !mouthshoot)
- handle_suicide(user)
- return
+ // todo: disabled for now
+ // if (A == user && user.zone_sel.selecting == O_MOUTH && !mouthshoot)
+ // handle_suicide(user)
+ // return
var/mob/living/L = user
if(user && user.client && istype(L) && L.aiming && L.aiming.active && L.aiming.aiming_at != A && A != user)
PreFire(A,user) //They're using the new gun system, locate what they're aiming at.
@@ -501,7 +511,7 @@
var/acc = burst_accuracy[min(i, burst_accuracy.len)]
var/disp = dispersion[min(i, dispersion.len)]
- P.accuracy = accuracy + acc
+ P.accuracy_overall_modify *= 1 + acc / 100
P.dispersion = disp
P.shot_from = src.name
@@ -662,22 +672,24 @@
disp_mod += one_handed_penalty*0.5 //dispersion per point of two-handedness
//Accuracy modifiers
- P.accuracy = accuracy + acc_mod
- P.dispersion = disp_mod
+ if(!isnull(accuracy_disabled))
+ P.accuracy_disabled = accuracy_disabled
- P.accuracy -= user.get_accuracy_penalty()
+ 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 += 30
+ 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 += M.accuracy
+ P.accuracy_overall_modify += 1 + (M.accuracy / 100)
if(!isnull(M.accuracy_dispersion))
P.dispersion = max(P.dispersion + M.accuracy_dispersion, 0)
@@ -717,44 +729,45 @@
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 != HALLOSS && !in_chamber.nodamage)
- log_and_message_admins("[key_name(user)] commited suicide using \a [src]")
- user.apply_damage(in_chamber.damage*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 == 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/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 != HALLOSS && !in_chamber.nodamage)
+// log_and_message_admins("[key_name(user)] commited suicide using \a [src]")
+// user.apply_damage(in_chamber.damage*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 == 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
diff --git a/code/modules/projectiles/gun_item_renderer.dm b/code/modules/projectiles/gun_item_renderer.dm
index 3ee4b5652857..d7b57cc54155 100644
--- a/code/modules/projectiles/gun_item_renderer.dm
+++ b/code/modules/projectiles/gun_item_renderer.dm
@@ -1,6 +1,6 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 Citadel Station developers. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* gun render system
diff --git a/code/modules/projectiles/gun_mob_renderer.dm b/code/modules/projectiles/gun_mob_renderer.dm
index fb98d935d003..5bbce3dc96b3 100644
--- a/code/modules/projectiles/gun_mob_renderer.dm
+++ b/code/modules/projectiles/gun_mob_renderer.dm
@@ -1,6 +1,6 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 Citadel Station developers. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/**
* gun render system
diff --git a/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm b/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm
index 389ba521281a..caeac99fe5e7 100644
--- a/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm
+++ b/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm
@@ -20,7 +20,12 @@
tracer_type = /obj/effect/projectile/tracer/medigun
impact_type = /obj/effect/projectile/impact/medigun
-/obj/projectile/beam/medical_cell/on_hit(var/mob/living/carbon/human/target) //what does it do when it hits someone?
+/obj/projectile/beam/medical_cell/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(ishuman(target))
+ on_hit_legacy(target)
+
+/obj/projectile/beam/medical_cell/proc/on_hit_legacy(var/mob/living/carbon/human/target) //what does it do when it hits someone?
return
/obj/item/ammo_casing/microbattery/medical/brute
@@ -29,7 +34,7 @@
type_name = "BRUTE"
projectile_type = /obj/projectile/beam/medical_cell/brute
-/obj/projectile/beam/medical_cell/brute/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/brute/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustBruteLoss(-10)
else
@@ -41,7 +46,7 @@
type_name = "BURN"
projectile_type = /obj/projectile/beam/medical_cell/burn
-/obj/projectile/beam/medical_cell/burn/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/burn/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustFireLoss(-10)
else
@@ -53,7 +58,7 @@
type_name = "STABILIZE"
projectile_type = /obj/projectile/beam/medical_cell/stabilize
-/obj/projectile/beam/medical_cell/stabilize/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/stabilize/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustOxyLoss(-30)
for(var/name in list(BP_HEAD, BP_L_HAND, BP_R_HAND, BP_L_ARM, BP_R_ARM, BP_L_FOOT, BP_R_FOOT, BP_L_LEG, BP_R_LEG, BP_GROIN, BP_TORSO))
@@ -81,7 +86,7 @@
type_name = "TOXIN"
projectile_type = /obj/projectile/beam/medical_cell/toxin
-/obj/projectile/beam/medical_cell/toxin/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/toxin/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustToxLoss(-10)
else
@@ -93,7 +98,7 @@
type_name = "OMNI"
projectile_type = /obj/projectile/beam/medical_cell/omni
-/obj/projectile/beam/medical_cell/omni/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/omni/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustBruteLoss(-2.5)
target.adjustFireLoss(-2.5)
@@ -108,7 +113,7 @@
type_name = "ANTIRAD"
projectile_type = /obj/projectile/beam/medical_cell/antirad
-/obj/projectile/beam/medical_cell/antirad/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/antirad/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustToxLoss(-2.5)
target.cure_radiation(RAD_MOB_CURE_STRENGTH_MEDIGUN)
@@ -121,7 +126,7 @@
type_name = "BRUTE-II"
projectile_type = /obj/projectile/beam/medical_cell/brute2
-/obj/projectile/beam/medical_cell/brute2/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/brute2/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustBruteLoss(-20)
else
@@ -133,7 +138,7 @@
type_name = "BURN-II"
projectile_type = /obj/projectile/beam/medical_cell/burn2
-/obj/projectile/beam/medical_cell/burn2/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/burn2/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustFireLoss(-20)
else
@@ -145,7 +150,7 @@
type_name = "STABILIZE-II"
projectile_type = /obj/projectile/beam/medical_cell/stabilize2
-/obj/projectile/beam/medical_cell/stabilize2/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/stabilize2/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustOxyLoss(-200)
for(var/name in list(BP_HEAD, BP_L_HAND, BP_R_HAND, BP_L_ARM, BP_R_ARM, BP_L_FOOT, BP_R_FOOT, BP_L_LEG, BP_R_LEG, BP_GROIN, BP_TORSO))
@@ -168,7 +173,7 @@
type_name = "OMNI-II"
projectile_type = /obj/projectile/beam/medical_cell/omni2
-/obj/projectile/beam/medical_cell/omni2/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/omni2/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustBruteLoss(-5)
target.adjustFireLoss(-5)
@@ -183,7 +188,7 @@
type_name = "TOXIN-II"
projectile_type = /obj/projectile/beam/medical_cell/toxin2
-/obj/projectile/beam/medical_cell/toxin2/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/toxin2/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustToxLoss(-20)
else
@@ -195,7 +200,7 @@
type_name = "HASTE"
projectile_type = /obj/projectile/beam/medical_cell/haste
-/obj/projectile/beam/medical_cell/haste/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/haste/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.add_modifier(/datum/modifier/medigunhaste, 20 SECONDS)
else
@@ -215,7 +220,7 @@
type_name = "RESIST"
projectile_type = /obj/projectile/beam/medical_cell/resist
-/obj/projectile/beam/medical_cell/resist/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/resist/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.add_modifier(/datum/modifier/resistance, 20 SECONDS)
else
@@ -235,7 +240,7 @@
type_name = "CORPSE MEND"
projectile_type = /obj/projectile/beam/medical_cell/corpse_mend
-/obj/projectile/beam/medical_cell/corpse_mend/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/corpse_mend/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
if(target.stat == DEAD)
target.adjustBruteLoss(-50)
@@ -251,7 +256,7 @@
type_name = "BRUTE-III"
projectile_type = /obj/projectile/beam/medical_cell/brute3
-/obj/projectile/beam/medical_cell/brute3/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/brute3/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustBruteLoss(-40)
else
@@ -263,7 +268,7 @@
type_name = "BURN-III"
projectile_type = /obj/projectile/beam/medical_cell/burn3
-/obj/projectile/beam/medical_cell/burn3/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/burn3/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustFireLoss(-40)
else
@@ -275,7 +280,7 @@
type_name = "TOXIN-III"
projectile_type = /obj/projectile/beam/medical_cell/toxin3
-/obj/projectile/beam/medical_cell/toxin3/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/toxin3/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustToxLoss(-40)
else
@@ -287,7 +292,7 @@
type_name = "OMNI-III"
projectile_type = /obj/projectile/beam/medical_cell/omni3
-/obj/projectile/beam/medical_cell/omni3/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/omni3/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.adjustBruteLoss(-10)
target.adjustFireLoss(-10)
@@ -303,7 +308,7 @@
type_name = "SHRINK"
projectile_type = /obj/projectile/beam/medical_cell/shrink
-/obj/projectile/beam/medical_cell/shrink/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/shrink/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.resize(0.5)
target.show_message("The beam fires into your body, changing your size!")
@@ -317,7 +322,7 @@
type_name = "GROW"
projectile_type = /obj/projectile/beam/medical_cell/grow
-/obj/projectile/beam/medical_cell/grow/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/grow/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.resize(2.0)
target.show_message("The beam fires into your body, changing your size!")
@@ -331,7 +336,7 @@
type_name = "NORMALSIZE"
projectile_type = /obj/projectile/beam/medical_cell/normalsize
-/obj/projectile/beam/medical_cell/normalsize/on_hit(var/mob/living/carbon/human/target)
+/obj/projectile/beam/medical_cell/normalsize/on_hit_legacy(var/mob/living/carbon/human/target)
if(istype(target, /mob/living/carbon/human))
target.resize(1)
target.show_message("The beam fires into your body, changing your size!")
diff --git a/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm b/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm
index c7ee2819ed9c..e43caa1981ce 100644
--- a/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm
+++ b/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm
@@ -40,9 +40,6 @@
damage = 2
agony = 20
pellets = 6 //number of pellets
- range_step = 2 //projectile will lose a fragment each time it travels this distance. Can be a non-integer.
- base_spread = 90 //lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone
- spread_step = 10
embed_chance = 0
sharp = 0
damage_flag = ARMOR_MELEE
@@ -67,14 +64,17 @@
sharp = 0
damage_flag = ARMOR_MELEE
-/obj/projectile/bullet/stripper/on_hit(var/atom/stripped)
- if(ishuman(stripped))
- var/mob/living/carbon/human/H = stripped
+/obj/projectile/bullet/stripper/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
if(!H.permit_stripped)
return
H.drop_slots_to_ground(list(SLOT_ID_SUIT, SLOT_ID_UNIFORM, SLOT_ID_BACK, SLOT_ID_SHOES, SLOT_ID_GLOVES))
//Hats can stay! Most other things fall off with removing these.
- ..()
/obj/item/ammo_casing/microbattery/combat/final
name = "\'Hydra\' microbattery - FINAL OPTION"
@@ -94,7 +94,11 @@
tracer_type = /obj/effect/projectile/tracer/laser_omni
impact_type = /obj/effect/projectile/impact/laser_omni
-/obj/projectile/beam/final_option/on_hit(var/atom/impacted)
+/obj/projectile/beam/final_option/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(isliving(impacted))
var/mob/living/L = impacted
if(L.mind)
@@ -104,5 +108,3 @@
nif = H.nif
SStranscore.m_backup(L.mind,nif,one_time = TRUE)
L.gib()
-
- ..()
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index b8101a537ae8..ef599f88a7ca 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -223,7 +223,7 @@
kinetic_gun = null
return ..()
-/obj/projectile/kinetic/Bump(atom/target)
+/obj/projectile/kinetic/pre_impact(atom/target, impact_flags, def_zone)
if(kinetic_gun)
var/list/mods = kinetic_gun.get_modkits()
for(var/obj/item/ka_modkit/M in mods)
@@ -234,20 +234,15 @@
pressure_decrease_active = TRUE
return ..()
-/obj/projectile/kinetic/projectile_attack_mob(mob/living/target_mob, distance, miss_modifier)
- if(!pressure_decrease_active && !lavaland_environment_check(get_turf(src)))
- name = "weakened [name]"
- damage = damage * pressure_decrease
- pressure_decrease_active = TRUE
- return ..()
-
/obj/projectile/kinetic/legacy_on_range()
strike_thing()
..()
-/obj/projectile/kinetic/on_hit(atom/target)
- strike_thing(target)
+/obj/projectile/kinetic/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ strike_thing(target)
/obj/projectile/kinetic/proc/strike_thing(atom/target)
if(!pressure_decrease_active && !lavaland_environment_check(get_turf(src)))
diff --git a/code/modules/projectiles/guns/energy/particle.dm b/code/modules/projectiles/guns/energy/particle.dm
index 910bedde7903..52184f2956c7 100644
--- a/code/modules/projectiles/guns/energy/particle.dm
+++ b/code/modules/projectiles/guns/energy/particle.dm
@@ -187,9 +187,9 @@
light_power = 1
light_color = "#CCFFFF"
-/turf/simulated/mineral/bullet_act(var/obj/projectile/Proj)
- if(istype(Proj, /obj/projectile/bullet/particle))
- if(prob(Proj.damage))
+/turf/simulated/mineral/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(istype(proj, /obj/projectile/bullet/particle))
+ if(prob(proj.damage))
GetDrilled()
- ..()
diff --git a/code/modules/projectiles/guns/energy/sizegun_vr.dm b/code/modules/projectiles/guns/energy/sizegun_vr.dm
index a96008be2e08..73b690bbd4cc 100644
--- a/code/modules/projectiles/guns/energy/sizegun_vr.dm
+++ b/code/modules/projectiles/guns/energy/sizegun_vr.dm
@@ -73,7 +73,11 @@
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
-/obj/projectile/beam/sizelaser/on_hit(var/atom/target)
+/obj/projectile/beam/sizelaser/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
var/mob/living/M = target
if(!M.permit_sizegun)
M.visible_message("[src] has no effect on [M].")
@@ -87,18 +91,13 @@
var/mob/living/H = M
H.resize(set_size, TRUE)
H.updateicon()
- else
- return 1
-
/obj/projectile/beam/sizelaser/shrink
set_size = 0.5 //50% of current size
-
/obj/projectile/beam/sizelaser/grow
set_size = 2.0 //200% of current size
-
/obj/item/gun/energy/stripper//Because it can be fun
name = "stripper gun"
desc = "A gun designed to remove unnessary layers from people. For external use only!"
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 4286b0cd921d..b117377439ea 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -428,7 +428,7 @@
icon_state = "ermitter_gun"
item_state = "pulse"
projectile_type = /obj/projectile/beam/emitter
- fire_delay = 10
+ fire_delay = 2 SECONDS
charge_cost = 900
cell_type = /obj/item/cell
accept_cell_type = /obj/item/cell
diff --git a/code/modules/projectiles/guns/launcher.dm b/code/modules/projectiles/guns/launcher.dm
index f95cee55f07d..a752f382bd73 100644
--- a/code/modules/projectiles/guns/launcher.dm
+++ b/code/modules/projectiles/guns/launcher.dm
@@ -14,11 +14,6 @@
/obj/item/gun/launcher/can_hit(var/mob/living/target as mob, var/mob/living/user as mob)
return 1
-//Override this to avoid a runtime with suicide handling.
-/obj/item/gun/launcher/handle_suicide(mob/living/user)
- to_chat(user, "Shooting yourself with \a [src] is pretty tricky. You can't seem to manage it.")
- return
-
/obj/item/gun/launcher/proc/update_release_force(obj/projectile)
return 0
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm b/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm
index e96a67f93eb3..322951fb7dd5 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm
@@ -31,10 +31,10 @@
update_icon()
update_held_icon()
-
-/obj/item/gun/ballistic/automatic/fluff/crestrose/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(default_parry_check(user, attacker, damage_source) && prob(50))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
+// todo: uhh we can fix it later maybe idfk
+// /obj/item/gun/ballistic/automatic/fluff/crestrose/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
+// if(default_parry_check(user, attacker, damage_source) && prob(50))
+// user.visible_message("\The [user] parries [attack_text] with \the [src]!")
+// playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
+// return 1
+// return 0
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm b/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm
index 1c4283e0b106..85a97302a6fe 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm
@@ -36,13 +36,14 @@
vacuum_traversal = 0
range = WORLD_ICON_SIZE * 6 //Scary name, but just deletes the projectile after this range
-/obj/projectile/pummel/on_hit(var/atom/movable/target, var/blocked = 0)
+/obj/projectile/pummel/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isliving(target))
var/mob/living/L = target
var/throwdir = get_dir(firer,L)
- if(prob(40) && !blocked)
+ if(prob(40) && (efficiency >= 0.9))
L.afflict_stun(20 * 1)
L.Confuse(1)
L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10)
-
- return 1
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm b/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm
index efa27a577727..72b43ca83f12 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm
@@ -34,7 +34,11 @@
vacuum_traversal = 0
range = WORLD_ICON_SIZE * 5 //Scary name, but just deletes the projectile after this range
-/obj/projectile/sickshot/on_hit(var/atom/movable/target, var/blocked = 0)
+/obj/projectile/sickshot/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(isliving(target))
var/mob/living/L = target
if(prob(20))
@@ -44,5 +48,3 @@
var/mob/living/carbon/human/H = target
H.vomit()
H.Confuse(2)
-
- return 1
diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm
index 90a5b2d2af9f..2cb005d1b736 100644
--- a/code/modules/projectiles/guns/magic/staff.dm
+++ b/code/modules/projectiles/guns/magic/staff.dm
@@ -27,9 +27,6 @@
icon_state = "staffofhealing"
item_state = "staffofhealing"
-/obj/item/gun/magic/staff/healing/handle_suicide() //Stops people trying to commit suicide to heal themselves
- return
-
/*
/obj/item/gun/magic/staff/chaos
name = "staff of chaos"
@@ -98,12 +95,12 @@
return ..()
*/
-/obj/item/gun/magic/staff/locker
- name = "staff of the locker"
- desc = "An artefact that expells encapsulating bolts, for incapacitating thy enemy."
- fire_sound = 'sound/magic/staff_change.ogg'
- ammo_type = /obj/item/ammo_casing/magic/locker
- icon_state = "locker"
- item_state = "locker"
- max_charges = 6
- recharge_rate = 4
+// /obj/item/gun/magic/staff/locker
+// name = "staff of the locker"
+// desc = "An artefact that expells encapsulating bolts, for incapacitating thy enemy."
+// fire_sound = 'sound/magic/staff_change.ogg'
+// ammo_type = /obj/item/ammo_casing/magic/locker
+// icon_state = "locker"
+// item_state = "locker"
+// max_charges = 6
+// recharge_rate = 4
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm
index e892f40b9857..3e9afa3f3fe4 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/projectile/automatic.dm
@@ -735,10 +735,6 @@
. = ..()
icon_state = (ammo_magazine)? "toy_smg" : "toy_smg-empty"
-/obj/item/gun/ballistic/automatic/advanced_smg/foam/handle_suicide(mob/living/user)
- user.show_message("You feel rather silly, trying to commit suicide with a toy.")
- mouthshoot = 0
-
/obj/item/gun/ballistic/automatic/advanced_smg/foam/blue
icon_state = "toy_smg_blue"
@@ -767,10 +763,6 @@
else
icon_state = "toy_c20r"
-/obj/item/gun/ballistic/automatic/c20r/foam/handle_suicide(mob/living/user)
- user.show_message("You feel rather silly, trying to commit suicide with a toy.")
- mouthshoot = 0
-
//Foam LMG
/obj/item/gun/ballistic/automatic/lmg/foam
name = "toy light machine gun"
@@ -795,8 +787,3 @@
/obj/item/gun/ballistic/automatic/lmg/foam/update_icon()
. = ..()
update_held_icon()
-
-/obj/item/gun/ballistic/automatic/lmg/foam/handle_suicide(mob/living/user)
- user.show_message("You feel rather silly, trying to commit suicide with a toy.")
- mouthshoot = 0
- return
diff --git a/code/modules/projectiles/guns/projectile/boltaction.dm b/code/modules/projectiles/guns/projectile/boltaction.dm
index afe3233f7326..1106d7ea4a3b 100644
--- a/code/modules/projectiles/guns/projectile/boltaction.dm
+++ b/code/modules/projectiles/guns/projectile/boltaction.dm
@@ -50,7 +50,7 @@
// Stole hacky terrible code from doublebarrel shotgun. -Spades
/obj/item/gun/ballistic/shotgun/pump/rifle/ceremonial/attackby(var/obj/item/A as obj, mob/user as mob)
- if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL)
+ 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)
to_chat(user, "You begin to shorten the barrel and stock of \the [src].")
if(loaded.len)
afterattack(user, user)
@@ -90,7 +90,7 @@
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/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL)
+ 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)
to_chat(user, "You begin to shorten the barrel and stock of \the [src].")
if(loaded.len)
afterattack(user, user)
@@ -134,7 +134,7 @@
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/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL)
+ 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)
to_chat(user, "You begin to shorten the barrel and stock of \the [src].")
if(loaded.len)
afterattack(user, user)
diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/projectile/pistol.dm
index 08476e331c76..c188758bb3ca 100644
--- a/code/modules/projectiles/guns/projectile/pistol.dm
+++ b/code/modules/projectiles/guns/projectile/pistol.dm
@@ -537,11 +537,6 @@
allowed_magazines = list(/obj/item/ammo_magazine/foam/pistol)
fire_sound = 'sound/items/syringeproj.ogg'
-/obj/item/gun/ballistic/pistol/foam/handle_suicide(mob/living/user)
- user.show_message("You feel rather silly, trying to commit suicide with a toy.")
- mouthshoot = 0
- return
-
/obj/item/gun/ballistic/pistol/foam/blue
icon_state = "toy_pistol_blue"
diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/projectile/shotgun.dm
index 3aa3ab634014..96bd6b3fbd2f 100644
--- a/code/modules/projectiles/guns/projectile/shotgun.dm
+++ b/code/modules/projectiles/guns/projectile/shotgun.dm
@@ -187,7 +187,7 @@
//this is largely hacky and bad :( -Pete
/obj/item/gun/ballistic/shotgun/doublebarrel/attackby(var/obj/item/A as obj, mob/user as mob)
- if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/energy) || istype(A, /obj/item/pickaxe/plasmacutter))
+ 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
@@ -350,11 +350,6 @@
one_handed_penalty = 5
fire_sound = 'sound/items/syringeproj.ogg'
-/obj/item/gun/ballistic/shotgun/pump/foam/handle_suicide(mob/living/user)
- user.show_message("You feel rather silly, trying to commit suicide with a toy.")
- mouthshoot = 0
- return
-
/obj/item/gun/ballistic/shotgun/pump/foam/pump(mob/M as mob)
playsound(M, action_sound, 60, 1)
diff --git a/code/modules/projectiles/guns/vox.dm b/code/modules/projectiles/guns/vox.dm
index 53f3c262bc7a..ca21ee38d086 100644
--- a/code/modules/projectiles/guns/vox.dm
+++ b/code/modules/projectiles/guns/vox.dm
@@ -151,8 +151,11 @@
/obj/projectile/sonic/strong
damage = 45
-/obj/projectile/sonic/strong/on_hit(var/atom/movable/target, var/blocked = 0)
+/obj/projectile/sonic/strong/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ismob(target))
+ var/mob/M = target
var/throwdir = get_dir(firer,target)
- target.throw_at_old(get_edge_target_turf(target, throwdir), rand(1,6), 10)
- return 1
+ M.throw_at_old(get_edge_target_turf(target, throwdir), rand(1,6), 10)
diff --git a/code/modules/projectiles/projectile-tracing.dm b/code/modules/projectiles/projectile-tracing.dm
deleted file mode 100644
index dc6144a7735b..000000000000
--- a/code/modules/projectiles/projectile-tracing.dm
+++ /dev/null
@@ -1,51 +0,0 @@
-
-//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 Citadel Station developers. *//
-
-/obj/projectile/trace
- atom_flags = ATOM_ABSTRACT | ATOM_NONWORLD
- invisibility = INVISIBILITY_ABSTRACT
- hitscan = TRUE
- has_tracer = FALSE
- damage = 0
- nodamage = TRUE
- /// did we manage to hit the given target?
- var/could_hit_target = FALSE
- /// delete on hitting target?
- var/del_on_success = FALSE
- /// do we check opacity?
- var/check_opacity = FALSE
-
-/obj/projectile/trace/Bump(atom/A)
- if(A == original)
- could_hit_target = TRUE
- if(del_on_success)
- qdel(src)
- return
- return ..()
-
-/obj/projectile/trace/projectile_attack_mob()
- return
-
-/obj/projectile/trace/Moved()
- . = ..()
- if(check_opacity && isturf(loc))
- // *sigh* //
- var/turf/T = loc
- if(T.has_opaque_atom)
- qdel(src)
-
-/obj/projectile/trace/proc/prepare_trace(atom/target, pass_flags = ATOM_PASS_GLASS | ATOM_PASS_GRILLE | ATOM_PASS_TABLE, check_opacity)
- src.pass_flags = pass_flags
- src.original = target
- src.check_opacity = check_opacity
- src.range = max(src.range, (get_dist(src, target) + 1) * WORLD_ICON_SIZE)
-
-/**
- * Simple trace to see if we could hit a given target
- */
-/obj/projectile/trace/proc/simple_trace(atom/target, angle)
- prepare_trace(target)
- fire(angle)
- del_on_success = TRUE
- return could_hit_target
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
deleted file mode 100644
index ac7164dcc9df..000000000000
--- a/code/modules/projectiles/projectile.dm
+++ /dev/null
@@ -1,1320 +0,0 @@
-/**
- * ## Physics Specifications
- *
- * We track physics as absolute pixel on a tile, not byond's pixel x/y
- * thus the first pixel at bottom left of tile is 1, 1
- * and the last pixel at top right is 32, 32 (for a world icon size of 32 pixels)
- *
- * We cross over to the next tile at above 32, for up/right,
- * and to the last tile at below 1, for bottom/left.
- *
- * The code might handle it based on how it's implemented,
- * but as long as the error is 1 pixel or below, it's not a big deal.
- *
- * The reason we're so accurate (1 pixel/below is pretty insanely strict) is
- * so players have the projectile act like what the screen says it should be like;
- * hence why projectiles can realistically path across corners based on their 'hitbox center'.
- */
-/obj/projectile
- name = "projectile"
- icon = 'icons/obj/projectiles.dmi'
- icon_state = "bullet"
- density = FALSE
- anchored = TRUE
- integrity_flags = INTEGRITY_INDESTRUCTIBLE | INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF
- pass_flags = ATOM_PASS_TABLE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- depth_level = INFINITY // nothing should be passing over us from depth
-
- //* Physics - Configuration *//
-
- /// speed, in pixels per second
- var/speed = 25 * WORLD_ICON_SIZE
- /// are we a hitscan projectile?
- var/hitscan = FALSE
- /// angle, in degrees **clockwise of north**
- var/angle
- /// max distance in pixels
- ///
- /// * please set this to a multiple of [WORLD_ICON_SIZE] so we scale with tile size.
- var/range = WORLD_ICON_SIZE * 50
- // todo: lifespan
-
- //* Physics - Homing *//
- /// Are we homing in on something?
- var/homing = FALSE
- /// current homing target
- var/atom/homing_target
- /// angle per second
- ///
- /// * this is smoother the less time between SSprojectiles fires
- var/homing_turn_speed = 100
- /// rand(min, max) for inaccuracy offsets
- var/homing_inaccuracy_min = 0
- /// rand(min, max) for inaccuracy offsets
- var/homing_inaccuracy_max = 0
- /// pixels; added to the real location of target so we're not exactly on-point
- var/homing_offset_x = 0
- /// pixels; added to the real location of target so we're not exactly on-point
- var/homing_offset_y = 0
-
- //* Physics - Tracers *//
-
- /// tracer /datum/point's
- var/list/tracer_vertices
- /// first point is a muzzle effect
- var/tracer_muzzle_flash
- /// last point is an impact
- var/tracer_impact_effect
- /// default tracer duration
- var/tracer_duration = 5
-
- //* Physics - State *//
-
- /// paused? if so, we completely get passed over during processing
- var/paused = FALSE
- /// currently hitscanning
- var/hitscanning = FALSE
- /// a flag to prevent movement hooks from resetting our physics on a forced movement
- var/trajectory_ignore_forcemove = FALSE
- /// cached value: move this much x for this much distance
- /// basically, dx / distance
- var/calculated_dx
- /// cached value: move this much y for this much distance
- /// basically, dy / distance
- var/calculated_dy
- /// cached sign of dx; 1, -1, or 0
- var/calculated_sdx
- /// cached sign of dy; 1, -1, or 0
- var/calculated_sdy
- /// our current pixel location on turf
- /// byond pixel_x rounds, and we don't want that
- ///
- /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf
- var/current_px
- /// our current pixel location on turf
- /// byond pixel_y rounds, and we don't want that
- ///
- /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf
- var/current_py
- /// the pixel location we're moving to, or the [current_px] after this iteration step
- ///
- /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step.
- var/next_px
- /// the pixel location we're moving to, or the [current_px] after this iteration step
- ///
- /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step.
- var/next_py
- /// used to track if we got kicked forwards after calling Move()
- var/trajectory_kick_forwards = 0
- /// to avoid going too fast when kicked forwards by a mirror, if we overshoot the pixels we're
- /// supposed to move this gets set to penalize the next move with a weird algorithm
- /// that i won't bother explaining
- var/trajectory_penalty_applied = 0
- /// currently travelled distance in pixels
- var/distance_travelled
- /// if we get forcemoved, this gets reset to 0 as a trip
- /// this way, we know exactly how far we moved
- var/distance_travelled_this_iteration
- /// where the physics loop and/or some other thing moving us is trying to move to
- /// used to determine where to draw hitscan tracers
- // todo: this being here is kinda a symptom that things are handled weirdly but whatever
- // optimally physics loop should handle tracking for stuff like animations, not require on hit processing to check turfs
- var/turf/trajectory_moving_to
-
- //Fired processing vars
- var/fired = FALSE //Have we been fired yet
- var/ignore_source_check = FALSE
-
- var/original_angle = 0 //Angle at firing
- var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
- var/spread = 0 //amount (in degrees) of projectile spread
- animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy
- var/ricochets = 0
- var/ricochets_max = 2
- var/ricochet_chance = 30
-
- //Hitscan
- /// do we have a tracer? if not we completely ignore hitscan logic
- var/has_tracer = TRUE
- var/tracer_type
- var/muzzle_type
- var/impact_type
-
- var/miss_sounds
- var/ricochet_sounds
- var/list/impact_sounds //for different categories, IMPACT_MEAT etc
-
- //Fancy hitscan lighting effects!
- var/hitscan_light_intensity = 1.5
- var/hitscan_light_range = 0.75
- var/hitscan_light_color_override
- var/muzzle_flash_intensity = 3
- var/muzzle_flash_range = 1.5
- var/muzzle_flash_color_override
- var/impact_light_intensity = 3
- var/impact_light_range = 2
- var/impact_light_color_override
-
- //Targetting
- var/yo = null
- var/xo = null
- var/atom/original = null // the original target clicked
- var/turf/starting = null // the projectile's starting turf
- var/list/permutated = list() // we've passed through these atoms, don't try to hit them again
- var/p_x = 16
- var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
-
- var/def_zone = "" //Aiming at
- var/mob/firer = null//Who shot it
- var/silenced = 0 //Attack message
- var/shot_from = "" // name of the object which shot us
-
- var/accuracy = 0
- var/dispersion = 0.0
-
- // Sub-munitions. Basically, multi-projectile shotgun, rather than pellets.
- var/use_submunitions = FALSE
- var/only_submunitions = FALSE // Will the projectile delete itself after firing the submunitions?
- var/list/submunitions = list() // Assoc list of the paths of any submunitions, and how many they are. [projectilepath] = [projectilecount].
- var/submunition_spread_max = 30 // Divided by 10 to get the percentile dispersion.
- var/submunition_spread_min = 5 // Above.
- /// randomize spread? if so, evenly space between 0 and max on each side.
- var/submunition_constant_spread = FALSE
- var/force_max_submunition_spread = FALSE // Do we just force the maximum?
- var/spread_submunition_damage = FALSE // Do we assign damage to our sub projectiles based on our main projectile damage?
-
- //? Damage - default handling
- /// damage amount
- var/damage = 10
- /// damage tier - goes hand in hand with [damage_armor]
- var/damage_tier = BULLET_TIER_DEFAULT
- /// todo: legacy - BRUTE, BURN, TOX, OXY, CLONE, HALLOSS, ELECTROCUTE, BIOACID are the only things that should be in here
- var/damage_type = BRUTE
- /// armor flag for damage - goes hand in hand with [damage_tier]
- var/damage_flag = ARMOR_BULLET
- /// damage mode - see [code/__DEFINES/combat/damage.dm]
- var/damage_mode = NONE
-
- var/SA_bonus_damage = 0 // Some bullets inflict extra damage on simple animals.
- var/SA_vulnerability = null // What kind of simple animal the above bonus damage should be applied to. Set to null to apply to all SAs.
- var/nodamage = 0 //Determines if the projectile will skip any damage inflictions
- var/taser_effect = 0 //If set then the projectile will apply it's agony damage using stun_effect_act() to mobs it hits, and other damage will be ignored
- var/projectile_type = /obj/projectile
- var/penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate()
- //Effects
- var/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech
- var/flammability = 0 //Amount of fire stacks to add for the above.
- var/combustion = TRUE //Does this set off flammable objects on fire/hit?
- var/stun = 0
- var/weaken = 0
- var/paralyze = 0
- var/irradiate = 0
- var/stutter = 0
- var/eyeblur = 0
- var/drowsy = 0
- var/agony = 0
- var/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections.
- var/modifier_type_to_apply = null // If set, will apply a modifier to mobs that are hit by this projectile.
- var/modifier_duration = null // How long the above modifier should last for. Leave null to be permanent.
- var/excavation_amount = 0 // How much, if anything, it drills from a mineral turf.
- /// If this projectile is holy. Silver bullets, etc. Currently no effects.
- var/holy = FALSE
-
- // Antimagic
- /// Should we check for antimagic effects?
- var/antimagic_check = FALSE
- /// Antimagic charges to use, if any
- var/antimagic_charges_used = 0
- /// Multiplier for damage if antimagic is on the target
- var/antimagic_damage_factor = 0
-
- var/embed_chance = 0 //Base chance for a projectile to embed
-
- var/fire_sound = 'sound/weapons/Gunshot_old.ogg' // Can be overriden in gun.dm's fire_sound var. It can also be null but I don't know why you'd ever want to do that. -Ace
-
- // todo: currently unimplemneted
- var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum.
-
- var/no_attack_log = FALSE
-
-/obj/projectile/Destroy()
- // stop processing
- STOP_PROCESSING(SSprojectiles, src)
- // cleanup
- cleanup_hitscan_tracers()
- return ..()
-
-/obj/projectile/proc/legacy_on_range() //if we want there to be effects when they reach the end of their range
- finalize_hitscan_tracers(impact_effect = FALSE, kick_forwards = 8)
- qdel(src)
-
-/obj/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it.
- if(AM.is_incorporeal())
- return
- ..()
- if(isliving(AM) && !check_pass_flags(ATOM_PASS_MOB))
- var/mob/living/L = AM
- if(can_hit_target(L, permutated, (AM == original)))
- Bump(AM)
-
-/obj/projectile/forceMove(atom/target)
- var/is_a_jump = isturf(target) != isturf(loc) || target.z != z || !trajectory_ignore_forcemove
- if(is_a_jump)
- record_hitscan_end()
- render_hitscan_tracers()
- . = ..()
- if(!.)
- stack_trace("projectile forcemove failed; please do not try to forcemove projectiles to invalid locations!")
- distance_travelled_this_iteration = 0
- if(!trajectory_ignore_forcemove)
- reset_physics_to_turf()
- if(is_a_jump)
- record_hitscan_start()
-
-/obj/projectile/proc/fire(set_angle_to, atom/direct_target)
- if(only_submunitions) // refactor projectiles whwen holy shit this is awful lmao
- // todo: this should make a muzzle flash
- qdel(src)
- return
- //If no angle needs to resolve it from xo/yo!
- if(direct_target)
- direct_target.bullet_act(src, def_zone)
- // todo: this should make a muzzle flash
- qdel(src)
- return
- if(isnum(set_angle_to))
- set_angle(set_angle_to)
-
- // setup physics
- setup_physics()
-
- var/turf/starting = get_turf(src)
- if(isnull(angle)) //Try to resolve through offsets if there's no angle set.
- if(isnull(xo) || isnull(yo))
- stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!")
- qdel(src)
- return
- var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z)
- set_angle(get_visual_angle(src, target))
- if(dispersion)
- set_angle(angle + rand(-dispersion, dispersion))
- original_angle = angle
- forceMove(starting)
- permutated = list()
- fired = TRUE
- // kickstart
- if(hitscan)
- physics_hitscan()
- else
- START_PROCESSING(SSprojectiles, src)
- physics_iteration(WORLD_ICON_SIZE, SSprojectiles.wait)
-
-/obj/projectile/Move(atom/newloc, dir = NONE)
- . = ..()
- if(.)
- if(fired && can_hit_target(original, permutated, TRUE))
- Bump(original)
-
-//Spread is FORCED!
-/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0)
- var/turf/curloc = get_turf(source)
- var/turf/targloc = get_turf(target)
-
- if(istype(source, /atom/movable))
- var/atom/movable/MT = source
- if(MT.locs && MT.locs.len) // Multi tile!
- for(var/turf/T in MT.locs)
- if(get_dist(T, target) < get_turf(curloc))
- curloc = get_turf(T)
-
- trajectory_ignore_forcemove = TRUE
- forceMove(get_turf(source))
- trajectory_ignore_forcemove = FALSE
- starting = curloc
- original = target
- if(targloc || !params)
- yo = targloc.y - curloc.y
- xo = targloc.x - curloc.x
- set_angle(get_visual_angle(src, targloc) + spread)
-
- if(isliving(source) && params)
- var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params)
- p_x = calculated[2]
- p_y = calculated[3]
-
- set_angle(calculated[1] + spread)
- else if(targloc)
- yo = targloc.y - curloc.y
- xo = targloc.x - curloc.x
- set_angle(get_visual_angle(src, targloc) + spread)
- else
- stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!")
- qdel(src)
-
-/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params)
- var/list/mouse_control = params2list(params)
- var/p_x = 0
- var/p_y = 0
- var/angle = 0
- if(mouse_control["icon-x"])
- p_x = text2num(mouse_control["icon-x"])
- if(mouse_control["icon-y"])
- p_y = text2num(mouse_control["icon-y"])
- if(mouse_control["screen-loc"])
- //Split screen-loc up into X+Pixel_X and Y+Pixel_Y
- var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
-
- //Split X+Pixel_X up into list(X, Pixel_X)
- var/list/screen_loc_X = splittext(screen_loc_params[1],":")
-
- //Split Y+Pixel_Y up into list(Y, Pixel_Y)
- var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
- var/x = text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32
- var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32
-
- //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average.
- var/screenviewX = user.client.current_viewport_width * world.icon_size
- var/screenviewY = user.client.current_viewport_height * world.icon_size
-
- var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x
- var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y
- angle = arctan(y - oy, x - ox)
- return list(angle, p_x, p_y)
-
-/obj/projectile/proc/redirect(x, y, starting, source)
- old_style_target(locate(x, y, z), starting? get_turf(starting) : get_turf(source))
-
-/obj/projectile/proc/old_style_target(atom/target, atom/source)
- if(!source)
- source = get_turf(src)
- starting = get_turf(source)
- original = target
- set_angle(get_visual_angle(source, target))
-
-/obj/projectile/proc/vol_by_damage()
- if(damage)
- return clamp((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then clamp the value between 30 and 100
- else
- return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume.
-
-//Returns true if the target atom is on our current turf and above the right layer
-//If direct target is true it's the originally clicked target.
-/obj/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE)
- if(QDELETED(target))
- return FALSE
- if(!ignore_source_check && firer)
- var/mob/M = firer
- if((target == firer) || ((target == firer.loc) && istype(firer.loc, /obj/vehicle/sealed/mecha)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target)))
- return FALSE
- if(!ignore_loc && (loc != target.loc))
- return FALSE
- if(target in passthrough)
- return FALSE
- if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
- return TRUE
- if(!isliving(target))
- if(direct_target)
- return TRUE
- if(target.layer < PROJECTILE_HIT_THRESHOLD_LAYER)
- return FALSE
- else
- var/mob/living/L = target
- if(!direct_target)
- if(!L.density)
- return FALSE
- return TRUE
-
-/obj/projectile/Bump(atom/A)
- if(A in permutated)
- trajectory_ignore_forcemove = TRUE
- forceMove(get_turf(A))
- trajectory_ignore_forcemove = FALSE
- return FALSE
- if(firer && !reflected)
- if(A == firer || (A == firer.loc && istype(A, /obj/vehicle/sealed/mecha))) //cannot shoot yourself or your mech
- trajectory_ignore_forcemove = TRUE
- forceMove(get_turf(A))
- trajectory_ignore_forcemove = FALSE
- return FALSE
-
- var/distance = get_dist(starting, get_turf(src))
- var/turf/target_turf = get_turf(A)
- var/passthrough = FALSE
-
- if(ismob(A))
- var/mob/M = A
- if(istype(A, /mob/living))
- //if they have a neck grab on someone, that person gets hit instead
- var/obj/item/grab/G = locate() in M
- if(G && G.state >= GRAB_NECK)
- if(G.affecting.stat == DEAD)
- var/shield_chance = min(80, (30 * (M.mob_size / 10))) //Small mobs have a harder time keeping a dead body as a shield than a human-sized one. Unathi would have an easier job, if they are made to be SIZE_LARGE in the future. -Mech
- if(prob(shield_chance))
- visible_message("\The [M] uses [G.affecting] as a shield!")
- if(Bump(G.affecting))
- return
- else
- visible_message("\The [M] tries to use [G.affecting] as a shield, but fails!")
- else
- visible_message("\The [M] uses [G.affecting] as a shield!")
- if(Bump(G.affecting))
- return //If Bump() returns 0 (keep going) then we continue on to attack M.
-
- passthrough = !projectile_attack_mob(M, distance)
- else
- passthrough = 1 //so ghosts don't stop bullets
- else
- passthrough = (A.bullet_act(src, def_zone) == PROJECTILE_CONTINUE) //backwards compatibility
- if(isturf(A))
- for(var/obj/O in A)
- O.bullet_act(src)
- for(var/mob/living/M in A)
- projectile_attack_mob(M, distance)
-
- //penetrating projectiles can pass through things that otherwise would not let them
- if(!passthrough && penetrating > 0)
- if(check_penetrate(A))
- passthrough = TRUE
- penetrating--
-
- if(passthrough)
- trajectory_ignore_forcemove = TRUE
- forceMove(target_turf)
- permutated.Add(A)
- trajectory_ignore_forcemove = FALSE
- return FALSE
-
- if(A)
- on_impact(A)
-
- if(hitscanning)
- if(trajectory_moving_to)
- // create tracers
- var/datum/point/visual_impact_point = get_intersection_point(trajectory_moving_to)
- if(visual_impact_point)
- // kick it forwards a bit
- visual_impact_point.shift_in_projectile_angle(angle, 2)
- // draw
- finalize_hitscan_tracers(visual_impact_point, impact_effect = TRUE)
- else
- finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32)
- else
- finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32)
-
- qdel(src)
- return TRUE
-
-//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not
-/obj/projectile/proc/on_hit(atom/target, blocked = 0, def_zone)
- if(blocked >= 100)
- return 0//Full block
- if(!isliving(target))
- return 0
-// if(isanimal(target)) return 0
- var/mob/living/L = target
- L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY!
- if(modifier_type_to_apply)
- L.add_modifier(modifier_type_to_apply, modifier_duration)
- return 1
-
-//called when the projectile stops flying because it Bump'd with something
-/obj/projectile/proc/on_impact(atom/A)
- if(damage && damage_type == BURN)
- var/turf/T = get_turf(A)
- if(T)
- T.hotspot_expose(700, 5)
-
-//Checks if the projectile is eligible for embedding. Not that it necessarily will.
-/obj/projectile/proc/can_embed()
- //embed must be enabled and damage type must be brute
- if(embed_chance == 0 || damage_type != BRUTE)
- return 0
- return 1
-
-/obj/projectile/proc/get_structure_damage()
- if(damage_type == BRUTE || damage_type == BURN)
- return damage
- return 0
-
-//return 1 if the projectile should be allowed to pass through after all, 0 if not.
-/obj/projectile/proc/check_penetrate(atom/A)
- return 1
-
-/obj/projectile/proc/check_fire(atom/target as mob, mob/living/user as mob) //Checks if you can hit them or not.
- check_trajectory(target, user, pass_flags, atom_flags)
-
-/obj/projectile/CanAllowThrough()
- . = ..()
- return TRUE
-
-//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying.
-/obj/projectile/proc/projectile_attack_mob(mob/living/target_mob, distance, miss_modifier = 0)
- if(!istype(target_mob))
- return
-
- //roll to-hit
- miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), 0)
- var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss
-
- var/result = PROJECTILE_FORCE_MISS
- if(hit_zone)
- def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part
- result = target_mob.bullet_act(src, def_zone)
-
- if(result == PROJECTILE_FORCE_MISS)
- if(!silenced)
- visible_message("\The [src] misses [target_mob] narrowly!")
- playsound(target_mob.loc, pick(miss_sounds), 60, 1)
- return FALSE
-
- //hit messages
- if(silenced)
- to_chat(target_mob, "You've been hit in the [parse_zone(def_zone)] by \the [src]!")
- else
- visible_message("\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter
-
- //admin logs
- if(!no_attack_log)
- if(istype(firer, /mob) && istype(target_mob))
- add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile")
-
- //sometimes bullet_act() will want the projectile to continue flying
- if (result == PROJECTILE_CONTINUE)
- return FALSE
-
- return TRUE
-
-/**
- * i hate everything
- *
- * todo: refactor guns
- * projectiles
- * and everything else
- *
- * i am losing my fucking mind
- * this shouldn't have to fucking exist because the ammo casing and/or gun should be doing it
- * and submunitions SHOULDNT BE HANDLED HERE!!
- */
-/obj/projectile/proc/launch_projectile_common(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
- original = target
- def_zone = check_zone(target_zone)
- firer = user
-
- if(use_submunitions && submunitions.len)
- var/temp_min_spread = 0
- if(force_max_submunition_spread)
- temp_min_spread = submunition_spread_max
- else
- temp_min_spread = submunition_spread_min
-
- var/damage_override = null
-
- if(spread_submunition_damage)
- damage_override = damage
- if(nodamage)
- damage_override = 0
-
- var/projectile_count = 0
-
- for(var/proj in submunitions)
- projectile_count += submunitions[proj]
-
- damage_override = round(damage_override / max(1, projectile_count))
-
- for(var/path in submunitions)
- var/amt = submunitions[path]
- for(var/count in 1 to amt)
- var/obj/projectile/SM = new path(get_turf(loc))
- SM.shot_from = shot_from
- SM.silenced = silenced
- if(!isnull(damage_override))
- SM.damage = damage_override
- if(submunition_constant_spread)
- SM.dispersion = 0
- var/calculated = angle + round((count / amt - 0.5) * submunition_spread_max, 1)
- SM.launch_projectile(target, target_zone, user, params, calculated)
- else
- SM.dispersion = rand(temp_min_spread, submunition_spread_max) / 10
- SM.launch_projectile(target, target_zone, user, params, angle_override)
-
-/obj/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
- var/direct_target
- if(get_turf(target) == get_turf(src))
- direct_target = target
-
- preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread)
- launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread)
- return fire(angle_override, direct_target)
-
-//called to launch a projectile from a gun
-/obj/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/gun/launcher)
-
- shot_from = launcher.name
- silenced = launcher.silenced
-
- return launch_projectile(target, target_zone, user, params, angle_override, forced_spread)
-
-/obj/projectile/proc/launch_projectile_from_turf(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
- var/direct_target
- if(get_turf(target) == get_turf(src))
- direct_target = target
-
- preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread)
- launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread)
- return fire(angle_override, direct_target)
-
-/**
- * Standard proc to determine damage when impacting something. This does not affect the special damage variables/effect variables, only damage and damtype.
- * May or may not be called before/after armor calculations.
- *
- * @params
- * - target The atom hit
- *
- * @return Damage to apply to target.
- */
-/obj/projectile/proc/run_damage_vulnerability(atom/target)
- var/final_damage = damage
- if(isliving(target))
- var/mob/living/L = target
- if(issimple(target))
- var/mob/living/simple_mob/SM = L
- if(SM.mob_class & SA_vulnerability)
- final_damage += SA_bonus_damage
- if(L.anti_magic_check(TRUE, TRUE, antimagic_charges_used, FALSE))
- final_damage *= antimagic_damage_factor
- return final_damage
-
-/**
- * Probably isn't needed but saves me the time and I can regex this later:
- * Gets the final `damage` that should be used on something
- */
-/obj/projectile/proc/get_final_damage(atom/target)
- return run_damage_vulnerability(target)
-
-//* Hitscan Visuals *//
-
-/**
- * returns a /datum/point based on where we currently are
- */
-/obj/projectile/proc/get_tracer_point()
- RETURN_TYPE(/datum/point)
- var/datum/point/point = new
- if(trajectory_moving_to)
- // we're in move. use next px/py to respect 1. kick forwards 2. deflections
- point.x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px
- point.y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py
- else
- point.x = (x - 1) * WORLD_ICON_SIZE + current_px
- point.y = (y - 1) * WORLD_ICON_SIZE + current_py
- point.z = z
- return point
-
-/**
- * * returns a /datum/point based on where we'll be when we loosely intersect a tile
- * * returns null if we'll never intersect it
- * * returns our current point if we're already loosely intersecting it
- * * loosely intersecting means that we are level with the tile in either x or y.
- */
-/obj/projectile/proc/get_intersection_point(turf/colliding)
- RETURN_TYPE(/datum/point)
- ASSERT(!isnull(angle))
-
- // calculate hwere we are
- var/our_x = (x - 1) * WORLD_ICON_SIZE + current_px
- var/our_y = (y - 1) * WORLD_ICON_SIZE + current_py
-
- // calculate how far we have to go to touch their closest x / y axis
- var/d_to_reach_x
- var/d_to_reach_y
-
- if(colliding.x != x)
- switch(calculated_sdx)
- if(0)
- return
- if(1)
- if(colliding.x < x)
- return
- d_to_reach_x = (((colliding.x - 1) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx
- if(-1)
- if(colliding.x > x)
- return
- d_to_reach_x = (((colliding.x - 0) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx
- else
- d_to_reach_x = 0
-
- if(colliding.y != y)
- switch(calculated_sdy)
- if(0)
- return
- if(1)
- if(colliding.y < y)
- return
- d_to_reach_y = (((colliding.y - 1) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy
- if(-1)
- if(colliding.y > y)
- return
- d_to_reach_y = (((colliding.y - 0) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy
- else
- d_to_reach_y = 0
-
- var/needed_distance = max(d_to_reach_x, d_to_reach_y)
-
- // calculate if we'll actually be touching the tile once we go that far
- var/future_x = our_x + needed_distance * calculated_dx
- var/future_y = our_y + needed_distance * calculated_dy
- // let's be slightly lenient and do 1 instead of 0.5
- if(future_x < (colliding.x - 1) * WORLD_ICON_SIZE && future_x > (colliding.x) * WORLD_ICON_SIZE + 1 && \
- future_y < (colliding.y - 1) * WORLD_ICON_SIZE && future_y > (colliding.y) * WORLD_ICON_SIZE + 1)
- return // not gonna happen
-
- // make the point based on how far we need to go
- var/datum/point/point = new
- point.x = future_x
- point.y = future_y
- point.z = z
- return point
-
-/**
- * records the start of a hitscan
- *
- * this can edit the point passed in!
- */
-/obj/projectile/proc/record_hitscan_start(datum/point/point, muzzle_marker, kick_forwards)
- if(!hitscanning)
- return
- if(isnull(point))
- point = get_tracer_point()
- tracer_vertices = list(point)
- tracer_muzzle_flash = muzzle_marker
-
- // kick forwards
- point.shift_in_projectile_angle(angle, kick_forwards)
-
-/**
- * ends the hitscan tracer
- *
- * this can edit the point passed in!
- */
-/obj/projectile/proc/record_hitscan_end(datum/point/point, impact_marker, kick_forwards)
- if(!hitscanning)
- return
- if(isnull(point))
- point = get_tracer_point()
- tracer_vertices += point
- tracer_impact_effect = impact_marker
-
- // kick forwards
- point.shift_in_projectile_angle(angle, kick_forwards)
-
-/**
- * records a deflection (change in angle, aka generate new tracer)
- */
-/obj/projectile/proc/record_hitscan_deflection(datum/point/point)
- if(!hitscanning)
- return
- if(isnull(point))
- point = get_tracer_point()
- // there's no way you need more than 25
- // if this is hit, fix your shit, don't bump this up; there's absolutely no reason for example,
- // to simulate reflectors working !!25!! times.
- if(length(tracer_vertices) >= 25)
- CRASH("tried to add more than 25 vertices to a hitscan tracer")
- tracer_vertices += point
-
-/obj/projectile/proc/render_hitscan_tracers(duration = tracer_duration)
- // don't stay too long
- ASSERT(duration >= 0 && duration <= 10 SECONDS)
- // check everything
- if(!has_tracer || !duration || !length(tracer_vertices))
- return
- var/list/atom/movable/beam_components = list()
-
- // muzzle
- if(muzzle_type && tracer_muzzle_flash)
- var/datum/point/starting = tracer_vertices[1]
- var/atom/movable/muzzle_effect = starting.instantiate_movable_with_unmanaged_offsets(muzzle_type)
- if(muzzle_effect)
- // turn it
- var/matrix/muzzle_transform = matrix()
- muzzle_transform.Turn(original_angle)
- muzzle_effect.transform = muzzle_transform
- muzzle_effect.color = color
- muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color)
- // add to list
- beam_components += muzzle_effect
- // impact
- if(impact_type && tracer_impact_effect)
- var/datum/point/starting = tracer_vertices[length(tracer_vertices)]
- var/atom/movable/impact_effect = starting.instantiate_movable_with_unmanaged_offsets(impact_type)
- if(impact_effect)
- // turn it
- var/matrix/impact_transform = matrix()
- impact_transform.Turn(angle)
- impact_effect.transform = impact_transform
- impact_effect.color = color
- impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color)
- // add to list
- beam_components += impact_effect
- // path tracers
- if(tracer_type)
- var/tempref = "\ref[src]"
- for(var/i in 1 to length(tracer_vertices) - 1)
- var/j = i + 1
- var/datum/point/first_point = tracer_vertices[i]
- var/datum/point/second_point = tracer_vertices[j]
- generate_tracer_between_points(first_point, second_point, beam_components, tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref)
-
- QDEL_LIST_IN(beam_components, duration)
-
-
-/obj/projectile/proc/cleanup_hitscan_tracers()
- tracer_vertices=null
-
-/obj/projectile/proc/finalize_hitscan_tracers(datum/point/end_point, impact_effect, kick_forwards)
- // if end wasn't recorded yet and we're still on a turf, record end
- if(isnull(tracer_impact_effect) && loc)
- record_hitscan_end(end_point, impact_marker = impact_effect, kick_forwards = kick_forwards)
- // render & cleanup
- render_hitscan_tracers()
- cleanup_hitscan_tracers()
-
-//* Physics - Configuration *//
-
-/**
- * sets our angle
- */
-/obj/projectile/proc/set_angle(new_angle)
- angle = new_angle
-
- // update sprite
- if(!nondirectional_sprite)
- var/matrix/M = new
- M.Turn(angle)
- transform = M
-
- // update trajectory
- calculated_dx = sin(new_angle)
- calculated_dy = cos(new_angle)
- calculated_sdx = calculated_dx == 0? 0 : (calculated_dx > 0? 1 : -1)
- calculated_sdy = calculated_dy == 0? 0 : (calculated_dy > 0? 1 : -1)
-
- // record our tracer's change
- if(hitscanning)
- record_hitscan_deflection()
-
-/**
- * sets our speed in pixels per decisecond
- */
-/obj/projectile/proc/set_speed(new_speed)
- speed = clamp(new_speed, 1, WORLD_ICON_SIZE * 100)
-
-/**
- * sets our angle and speed
- */
-/obj/projectile/proc/set_velocity(new_angle, new_speed)
- // this is so this can be micro-optimized later but for once i'm not going to do it early for no reason
- set_speed(new_speed)
- set_angle(new_angle)
-
-/**
- * todo: this is somewhat mildly terrible
- */
-/obj/projectile/proc/set_homing_target(atom/A)
- if(!A || (!isturf(A) && !isturf(A.loc)))
- return FALSE
- homing = TRUE
- homing_target = A
- homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max)
- homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max)
- if(prob(50))
- homing_offset_x = -homing_offset_x
- if(prob(50))
- homing_offset_y = -homing_offset_y
-
-/**
- * initializes physics vars
- */
-/obj/projectile/proc/setup_physics()
- distance_travelled = 0
-
-/**
- * called after an unhandled forcemove is detected, or other event
- * that should reset our on-turf state
- */
-/obj/projectile/proc/reset_physics_to_turf()
- // we use this because we can center larger than 32x32 projectiles
- // without disrupting physics this way
- //
- // we add by (WORLD_ICON_SIZE / 2) because
- // pixel_x / pixel_y starts at center,
- //
- current_px = pixel_x - base_pixel_x + (WORLD_ICON_SIZE / 2)
- current_py = pixel_y - base_pixel_y + (WORLD_ICON_SIZE / 2)
- // interrupt active move logic
- trajectory_moving_to = null
-
-//* Physics - Processing *//
-
-/obj/projectile/process(delta_time)
- if(paused)
- return
- physics_iteration(min(10 * WORLD_ICON_SIZE, delta_time * speed * SSprojectiles.global_projectile_speed_multiplier), delta_time)
-
-/**
- * immediately processes hitscan
- */
-/obj/projectile/proc/physics_hitscan(safety = 250, resuming)
- // setup
- if(!resuming)
- hitscanning = TRUE
- record_hitscan_start(muzzle_marker = TRUE, kick_forwards = 16)
-
- // just move as many times as we can
- while(!QDELETED(src) && loc)
- // check safety
- safety--
- if(safety <= 0)
- // if you're here, you shouldn't be. do not bump safety up, fix whatever
- // you're doing because no one should be making projectiles go more than 250
- // tiles in a single life.
- stack_trace("projectile hit iteration limit for hitscan")
- break
-
- // move forwards by 1 tile length
- distance_travelled += physics_step(WORLD_ICON_SIZE)
- // if we're being yanked, yield
- if(movable_flags & MOVABLE_IN_MOVED_YANK)
- spawn(0)
- physics_hitscan(safety, TRUE)
- return
-
- // see if we're done
- if(distance_travelled >= range)
- legacy_on_range()
- break
-
- hitscanning = FALSE
-
-/**
- * ticks forwards a number of pixels
- *
- * todo: potential lazy animate support for performance, as we honestly don't need to animate at full fps if the server's above 20fps
- *
- * * delta_tiem is in deciseconds, not seconds.
- */
-/obj/projectile/proc/physics_iteration(pixels, delta_time, additional_animation_length)
- // setup iteration
- var/safety = 15
- var/pixels_remaining = pixels
- distance_travelled_this_iteration = 0
-
- // apply penalty
- var/penalizing = clamp(trajectory_penalty_applied, 0, pixels_remaining)
- pixels_remaining -= penalizing
- trajectory_penalty_applied -= penalizing
-
- // clamp to max distance
- pixels_remaining = min(pixels_remaining, range - distance_travelled)
-
- // move as many times as we need to
- //
- // * break if we're loc = null (by deletion or otherwise)
- // * break if we get paused
- while(pixels_remaining > 0)
- // check safety
- safety--
- if(safety <= 0)
- CRASH("ran out of safety! what happened?")
-
- // move
- var/pixels_moved = physics_step(pixels_remaining)
- distance_travelled += pixels_moved
- distance_travelled_this_iteration += pixels_moved
- pixels_remaining -= pixels_moved
- // we're being yanked, yield
- if(movable_flags & MOVABLE_IN_MOVED_YANK)
- spawn(0)
- physics_iteration(pixels_remaining, delta_time, distance_travelled_this_iteration)
- return
- if(!loc || paused)
- break
-
- // penalize next one if we were kicked forwards forcefully too far
- trajectory_penalty_applied = max(0, -pixels_remaining)
-
- // if we don't have a loc anymore just bail
- if(!loc)
- return
-
- // if we're at max range
- if(distance_travelled >= range)
- // todo: egh
- legacy_on_range()
- if(QDELETED(src))
- return
-
- // process homing
- physics_tick_homing(delta_time)
-
- // perform animations
- // we assume at this point any deflections that should have happened, has happened,
- // so we just do a naive animate based on our current loc and pixel x/y
- //
- // todo: animation needs to take into account angle changes,
- // but that's expensive as shit so uh lol
- //
- // the reason we use distance_travelled_this_iteration is so if something disappears
- // by forceMove or whatnot,
- // we won't have it bounce from its previous location to the new one as it's not going
- // to be accurate anymore
- //
- // so instead, as of right now, we backtrack via how much we know we moved.
- var/final_px = base_pixel_x + current_px - (WORLD_ICON_SIZE / 2)
- var/final_py = base_pixel_y + current_py - (WORLD_ICON_SIZE / 2)
- var/anim_dist = distance_travelled_this_iteration + additional_animation_length
- pixel_x = final_px - (anim_dist * sin(angle))
- pixel_y = final_py - (anim_dist * cos(angle))
-
- animate(
- src,
- delta_time,
- flags = ANIMATION_END_NOW,
- pixel_x = final_px,
- pixel_y = final_py,
- )
-
-/**
- * based on but exactly http://www.cs.yorku.ca/~amana/research/grid.pdf
- *
- * move into the next tile, or the specified number of pixels,
- * whichever is less pixels moved
- *
- * this will modify our current_px/current_py as necessary
- *
- * @return pixels moved
- */
-/obj/projectile/proc/physics_step(limit)
- // distance to move in our angle to get to next turf for horizontal and vertical
- var/d_next_horizontal = \
- (calculated_sdx? ((calculated_sdx > 0? (WORLD_ICON_SIZE + 0.5) - current_px : -current_px + 0.5) / calculated_dx) : INFINITY)
- var/d_next_vertical = \
- (calculated_sdy? ((calculated_sdy > 0? (WORLD_ICON_SIZE + 0.5) - current_py : -current_py + 0.5) / calculated_dy) : INFINITY)
- var/turf/move_to_target
-
- /**
- * explanation on why current and next are done:
- *
- * projectiles track their pixel x/y on turf, not absolute pixel x/y from edge of map
- * this is done to make it simpler to reason about, but is not necessarily the most simple
- * or efficient way to do things.
- *
- * part of the problems with this approach is that Move() is not infallible. the projectile can be blocked.
- * if we immediately set current pixel x/y, if the projectile is intercepted by a Bump, we now dont' know the 'real'
- * position of the projectile because it's out of sync with where it should be
- *
- * now, things that require math operations on it don't know the actual location of the projectile until this proc
- * rolls it back
- *
- * so instead, we never touch current px/py until the move is known to be successful, then we set it
- * to the stored next px/py
- *
- * this way, things accessing can mutate our state freely without worrying about needing to handle rollbacks
- *
- * this entire system however adds overhead
- * if we want to not have overhead, we'll need to rewrite hit processing and have it so moves are fully illegal to fail
- * but doing that is literally not possible because anything can reject a move for any reason whatsoever
- * and we cannot control that, so, instead, we make projectiles track in absolute pixel x/y coordinates from edge of map
- *
- * that way, we don't even need to care about where the .loc is, we just know where the projectile is supposed to be by
- * knowing where it isn't, and by taking the change in its pixels the projectile controller can tell the projectile
- * where to go-
- *
- * (all shitposting aside, this is for future work; it works right now and we have an API to do set angle, kick forwards, etc)
- * (so i'm not going to touch this more because it's 4 AM and honestly this entire raycaster is already far less overhead)
- * (than the old system of a 16-loop of brute forced 2 pixel increments)
- */
-
- if(d_next_horizontal == d_next_vertical)
- // we're diagonal
- if(d_next_horizontal <= limit)
- move_to_target = locate(x + calculated_sdx, y + calculated_sdy, z)
- . = d_next_horizontal
- if(!move_to_target)
- // we hit the world edge and weren't transit; time to get deleted.
- if(hitscanning)
- finalize_hitscan_tracers(impact_effect = FALSE)
- qdel(src)
- return
- next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
- next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
- else if(d_next_horizontal < d_next_vertical)
- // closer is to move left/right
- if(d_next_horizontal <= limit)
- move_to_target = locate(x + calculated_sdx, y, z)
- . = d_next_horizontal
- if(!move_to_target)
- // we hit the world edge and weren't transit; time to get deleted.
- if(hitscanning)
- finalize_hitscan_tracers(impact_effect = FALSE)
- qdel(src)
- return
- next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
- next_py = current_py + d_next_horizontal * calculated_dy
- else if(d_next_vertical < d_next_horizontal)
- // closer is to move up/down
- if(d_next_vertical <= limit)
- move_to_target = locate(x, y + calculated_sdy, z)
- . = d_next_vertical
- if(!move_to_target)
- // we hit the world edge and weren't transit; time to get deleted.
- if(hitscanning)
- finalize_hitscan_tracers(impact_effect = FALSE)
- qdel(src)
- return
- next_px = current_px + d_next_vertical * calculated_dx
- next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
-
- // if we need to move
- if(move_to_target)
- var/atom/old_loc = loc
- trajectory_moving_to = move_to_target
- if(!Move(move_to_target) && ((loc != move_to_target) || !trajectory_moving_to))
- // if we don't successfully move, don't change anything, we didn't move.
- . = 0
- if(loc == old_loc)
- stack_trace("projectile failed to move, but is still on turf instead of deleted or relocated.")
- qdel(src) // bye
- else
- // only do these if we successfully move, or somehow end up in that turf anyways
- if(trajectory_kick_forwards)
- . += trajectory_kick_forwards
- trajectory_kick_forwards = 0
- current_px = next_px
- current_py = next_py
- #ifdef CF_PROJECTILE_RAYCAST_VISUALS
- new /atom/movable/render/projectile_raycast(move_to_target, current_px, current_py, "#77ff77")
- #endif
- trajectory_moving_to = null
- else
- // not moving to another tile, so, just move on current tile
- if(trajectory_kick_forwards)
- trajectory_kick_forwards = 0
- stack_trace("how did something kick us forwards when we didn't even move?")
- . = limit
- current_px += limit * calculated_dx
- current_py += limit * calculated_dy
- next_px = current_px
- next_py = current_py
- #ifdef CF_PROJECTILE_RAYCAST_VISUALS
- new /atom/movable/render/projectile_raycast(loc, current_px, current_py, "#ff3333")
- #endif
-
-#ifdef CF_PROJECTILE_RAYCAST_VISUALS
-GLOBAL_VAR_INIT(projectile_raycast_debug_visual_delay, 2 SECONDS)
-
-/atom/movable/render/projectile_raycast
- plane = OBJ_PLANE
- icon = 'icons/system/color_32x32.dmi'
- icon_state = "white-pixel"
-
-/**
- * px, py are absolute pixel coordinates on the tile, not pixel_x / pixel_y of this renderer!
- */
-/atom/movable/render/projectile_raycast/Initialize(mapload, px, py, color)
- src.pixel_x = px - 1
- src.pixel_y = py - 1
- src.color = color
- . = ..()
- QDEL_IN(src, GLOB.projectile_raycast_debug_visual_delay)
-#endif
-
-/**
- * immediately, without processing, kicks us forward a number of pixels
- *
- * since we immediately cross over into a turf when entering,
- * things like mirrors/reflectors will immediately set angle
- *
- * it looks ugly and is pretty bad to just reflect off the edge of a turf so said things can
- * call this proc to kick us forwards by a bit
- */
-/obj/projectile/proc/physics_kick_forwards(pixels)
- trajectory_kick_forwards += pixels
- next_px += pixels * calculated_dx
- next_py += pixels * calculated_dy
-
-/**
- * only works during non-hitscan
- *
- * this is called once per tick
- * homing is smoother the higher fps the server / SSprojectiles runs at
- *
- * todo: this is somewhat mildly terrible
- * todo: this has absolutely no arc/animation support; this is bad
- */
-/obj/projectile/proc/physics_tick_homing(delta_time)
- if(!homing)
- return FALSE
- // checks if they're 1. on a turf, 2. on our z
- // todo: should we add support for tracking something even if it leaves a turf?
- if(homing_target?.z != z)
- // bye bye!
- return FALSE
- // todo: this assumes single-tile objects. at some point, we should upgrade this to be unnecessarily expensive and always center-mass.
- var/dx = (homing_target.x - src.x) * WORLD_ICON_SIZE + (0 - current_px) + homing_offset_x
- var/dy = (homing_target.y - src.y) * WORLD_ICON_SIZE + (0 - current_py) + homing_offset_y
- // say it with me, arctan()
- // is CCW of east if (dx, dy)
- // and CW of north if (dy, dx)
- // where dx and dy is distance in x/y pixels from us to them.
-
- var/nudge_towards = closer_angle_difference(arctan(dy, dx))
- var/max_turn_speed = homing_turn_speed * delta_time
-
- set_angle(angle + clamp(nudge_towards, -max_turn_speed, max_turn_speed))
-
-//* Physics - Querying *//
-
-/**
- * predict what turf we'll be in after going forwards a certain amount of pixels
- *
- * doesn't actually sim; so this will go through walls/obstacles!
- *
- * * if we go out of bounds, we will return null; this doesn't level-wrap
- */
-/obj/projectile/proc/physics_predicted_turf_after_iteration(pixels)
- // -1 at the end if 0, because:
- //
- // -32 is go back 1 tile and be at the 1st pixel (as 0 is going back)
- // 0 is go back 1 tile and be at the 32nd pixel.
- var/incremented_px = (current_px + pixels * calculated_dx) || - 1
- var/incremented_py = (current_py + pixels * calculated_dy) || - 1
-
- var/incremented_tx = floor(incremented_px / 32)
- var/incremented_ty = floor(incremented_py / 32)
-
- return locate(x + incremented_tx, y + incremented_ty, z)
-
-/**
- * predict what turfs we'll hit, excluding the current turf, after going forwards
- * a certain amount of pixels
- *
- * doesn't actually sim; so this will go through walls/obstacles!
- */
-/obj/projectile/proc/physics_predicted_turfs_during_iteration(pixels)
- return pixel_physics_raycast(loc, current_px, current_py, angle, pixels)
-
-//* Targeting *//
-
-/**
- * Checks if something is a valid target when directly clicked.
- */
-/obj/projectile/proc/is_valid_target(atom/target)
- if(isobj(target))
- var/obj/O = target
- return O.obj_flags & OBJ_RANGE_TARGETABLE
- else if(isliving(target))
- return TRUE
- else if(isturf(target))
- return target.density
- return FALSE
diff --git a/code/modules/projectiles/projectile/README.md b/code/modules/projectiles/projectile/README.md
new file mode 100644
index 000000000000..187e857cfe43
--- /dev/null
+++ b/code/modules/projectiles/projectile/README.md
@@ -0,0 +1,6 @@
+# /obj/projectile
+
+The base type of physics-simulated raycasting flying projectiles.
+
+Used for when you need precision, as throwing is very imprecise.
+
diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm
index f0a0a86cbe89..8277bd2bfa5f 100644
--- a/code/modules/projectiles/projectile/arc.dm
+++ b/code/modules/projectiles/projectile/arc.dm
@@ -10,6 +10,7 @@
name = "arcing shot"
icon_state = "fireball" // WIP
movement_type = MOVEMENT_UNSTOPPABLE
+ impact_ground_on_expiry = TRUE
plane = ABOVE_PLANE // Since projectiles are 'in the air', they might visually overlap mobs while in flight, so the projectile needs to be above their plane.
speed = 10 * WORLD_ICON_SIZE
var/fired_dir = null // Which direction was the projectile fired towards. Needed to invert the projectile turning based on if facing left or right.
@@ -17,8 +18,6 @@
var/visual_y_offset = -16 // Adjusts how high the projectile and its shadow start, visually. This is so the projectile and shadow align with the center of the tile.
var/obj/effect/projectile_shadow/shadow = null // Visual indicator for the projectile's 'true' position. Needed due to being bound to two dimensions in reality.
-/obj/projectile/arc/Bump()
- return
/obj/projectile/arc/Initialize(mapload)
shadow = new(get_turf(src))
@@ -28,7 +27,6 @@
QDEL_NULL(shadow)
return ..()
-
/obj/projectile/arc/proc/calculate_initial_pixel_distance(atom/user, atom/target)
var/datum/point/A = new(user)
var/datum/point/B = new(target)
@@ -44,12 +42,6 @@
var/datum/point/starting_point = new(starting)
return pixel_length_between_points(current_point, starting_point)
-/obj/projectile/arc/legacy_on_range()
- if(loc)
- on_impact(loc)
- return ..()
-
-
/obj/projectile/arc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
fired_dir = get_dir(user, target) // Used to determine if the projectile should turn in the air.
distance_to_fly = calculate_initial_pixel_distance(user, target) // Calculates how many pixels to travel before hitting the ground.
@@ -93,7 +85,6 @@
shadow.pixel_x = pixel_x
shadow.pixel_y = pixel_y + visual_y_offset
-
/obj/effect/projectile_shadow
name = "shadow"
desc = "You better avoid the thing coming down!"
@@ -102,64 +93,13 @@
anchored = TRUE
animate_movement = 0 // Just like the projectile it's following.
-//////////////
-// Subtypes
-//////////////
-
-// This is a test projectile in the sense that its testing the code to make sure it works,
-// as opposed to a 'can I hit this thing' projectile.
-/obj/projectile/arc/test/on_impact(turf/T)
- new /obj/effect/explosion(T)
- T.color = "#FF0000"
-
-// Generic, Hivebot related
-/obj/projectile/arc/blue_energy
- name = "energy missile"
- icon_state = "force_missile"
- damage = 15
- damage_type = BURN
-
-/obj/projectile/arc/blue_energy/on_impact(turf/T)
- for(var/mob/living/L in T)
- projectile_attack_mob(L) // Everything on the turf it lands gets hit.
-
-// Fragmentation arc shot
-/obj/projectile/arc/fragmentation
- name = "fragmentation shot"
- icon_state = "shell"
- var/list/fragment_types = list(
- /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment, \
- /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment/strong
- )
- var/fragment_amount = 63 // Same as a grenade.
- var/spread_range = 7
-
-/obj/projectile/arc/fragmentation/on_impact(turf/T)
- fragmentate(T, fragment_amount, spread_range, fragment_types)
-
-/obj/projectile/arc/fragmentation/mortar
- icon_state = "mortar"
- fragment_amount = 10
- spread_range = 3
-
-// EMP arc shot
-/obj/projectile/arc/emp_blast
- name = "emp blast"
- icon_state = "bluespace"
-
-/obj/projectile/arc/emp_blast/on_impact(turf/T)
- empulse(T, 2, 4, 7, 10) // Normal EMP grenade.
-
-/obj/projectile/arc/emp_blast/weak/on_impact(turf/T)
- empulse(T, 1, 2, 3, 4) // Sec EMP grenade.
-
-// Radiation arc shot
-/obj/projectile/arc/radioactive
- name = "radiation blast"
- icon_state = "green_pellet"
- icon_scale_x = 2
- icon_scale_y = 2
- var/rad_power = RAD_INTENSITY_PROJ_ARC
-
-/obj/projectile/arc/radioactive/on_impact(turf/T)
- radiation_pulse(T, rad_power)
+//* We do not hit normally. *//
+
+/obj/projectile/arc/scan_crossed_atom(atom/movable/target)
+ return
+
+/obj/projectile/arc/scan_moved_turf(turf/tile)
+ return
+
+/obj/projectile/arc/impact_loop(turf/was_moving_onto, atom/bumped)
+ return
diff --git a/code/modules/projectiles/projectile/helpers.dm b/code/modules/projectiles/projectile/helpers.dm
new file mode 100644
index 000000000000..3469a09b6a67
--- /dev/null
+++ b/code/modules/projectiles/projectile/helpers.dm
@@ -0,0 +1,68 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Creates a shrapnel explosion
+ *
+ * todo: should this be in a single file with procs like explosion(), emp_pulse(), and other blast-like effects?
+ * todo: /pellet shouldn't be /bullet/pellet
+ * todo: there has to be a better way to set vars on every cloud than passing them in as args
+ *
+ * @params
+ * * total_fragments - total pellets. this is divided by turfs within the given radius and rounded up.
+ * * radius - radius to target. fragments are spread into every turf in the range, making one pellet cloud projectile
+ * * fragment_types - either a type, or a list of types to pick from. weighted lists are not allowed for speed reasons. all types must be /obj/projectile/bullet/pellet.
+ * * source - (optional) actual source; used to detect if something is on the ground or not
+ * * shot_from_name - (legacy - pending reconsideration) what we were shot from
+ * * firer - (legacy - pending reconsideration) what to set firer to
+ */
+/turf/proc/shrapnel_explosion(total_fragments, radius, fragment_types = /obj/projectile/bullet/pellet/fragment, atom/movable/source, shot_from_name, firer)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ shrapnel_explosion_impl(total_fragments, radius, fragment_types, source, shot_from_name, firer)
+
+/turf/proc/shrapnel_explosion_impl(total_fragments, radius, fragment_types, atom/movable/source, shot_from_name, firer)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(radius > 8)
+ // remember that for radius 8 we're making 32 projectiles already
+ CRASH("attempted radius [radius]; this is too large.")
+ // todo: verify getcircle() behavior is what we want
+ // todo: the answer is probably no, as this doesn't work near map edges.
+ var/list/target_turfs = getcircle(src, radius)
+ // round up
+ var/fragments_per_projectile = ceil(total_fragments / length(target_turfs))
+ // make shrapnel clouds
+ . = list()
+ for(var/turf/T in target_turfs)
+ var/fragment_type = islist(fragment_types) ? pick(fragment_types) : fragment_types
+ var/obj/projectile/bullet/pellet/pellet_cloud = new fragment_type(src)
+ pellet_cloud.pellets = fragments_per_projectile
+ pellet_cloud.shot_from = shot_from_name
+ pellet_cloud.firer = firer
+ pellet_cloud.fire(arctan(T.y - y, T.x - x), null, TRUE)
+ . += pellet_cloud
+
+/**
+ * make a shrapnel explosion
+ */
+/obj/proc/shrapnel_explosion(total_fragments, radius, fragment_types)
+ var/turf/turf = get_turf(src)
+ if(!turf)
+ return
+ var/list/obj/projectile/bullet/pellet/pellet_clouds = turf.shrapnel_explosion(total_fragments, radius, fragment_types, src, name, src)
+ // hit things in our turf
+ for(var/mob/living/victim in turf)
+ for(var/obj/projectile/bullet/pellet/pellet_cloud as anything in pellet_clouds)
+ if(QDELETED(pellet_cloud))
+ continue
+ // they're laying on us, o h n o
+ if(victim.lying && (loc == turf))
+ if(prob(90))
+ pellet_cloud.impact(victim)
+ // they're not laying down but they're holding the source and standing
+ else if(!victim.lying && victim.is_holding(src))
+ if(prob(25))
+ pellet_cloud.impact(victim)
+ // they are either holding us while laying down or just on the turf without holding us
+ else
+ if(prob(15))
+ pellet_cloud.impact(victim)
diff --git a/code/modules/projectiles/projectile/pellets.dm b/code/modules/projectiles/projectile/pellets.dm
deleted file mode 100644
index 61e9b45cda0c..000000000000
--- a/code/modules/projectiles/projectile/pellets.dm
+++ /dev/null
@@ -1,118 +0,0 @@
-
-//For projectiles that actually represent clouds of projectiles
-/obj/projectile/bullet/pellet
- name = "shrapnel" //'shrapnel' sounds more dangerous (i.e. cooler) than 'pellet'
- damage = 20
- //icon_state = "bullet" //TODO: would be nice to have it's own icon state
- var/pellets = 4 //number of pellets
- var/range_step = 2 //projectile will lose a fragment each time it travels this distance. Can be a non-integer.
- var/base_spread = 90 //lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone
- var/spread_step = 10 //higher means the pellets spread more across body parts with distance
-
-/obj/projectile/bullet/pellet/proc/get_pellets(var/distance)
- var/pellet_loss = round((distance - 1)/range_step) //pellets lost due to distance
- return max(pellets - pellet_loss, 1)
-
-/obj/projectile/bullet/pellet/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier)
- if (pellets < 0) return 1
-
- var/total_pellets = get_pellets(distance)
- var/spread = max(base_spread - (spread_step*distance), 0)
-
- //shrapnel explosions miss prone mobs with a chance that increases with distance
- var/prone_chance = 0
- if(!base_spread)
- prone_chance = max(spread_step*(distance - 2), 0)
-
- var/hits = 0
- for (var/i in 1 to total_pellets)
- if(target_mob.lying && target_mob != original && prob(prone_chance))
- continue
-
- //pellet hits spread out across different zones, but 'aim at' the targeted zone with higher probability
- //whether the pellet actually hits the def_zone or a different zone should still be determined by the parent using get_zone_with_miss_chance().
- var/old_zone = def_zone
- def_zone = ran_zone(def_zone, spread)
- if (..()) hits++
- def_zone = old_zone //restore the original zone the projectile was aimed at
-
- pellets -= hits //each hit reduces the number of pellets left
- if (hits >= total_pellets || pellets <= 0)
- return 1
- return 0
-
-/obj/projectile/bullet/pellet/get_structure_damage()
- var/distance = get_dist(loc, starting)
- return ..() * get_pellets(distance)
-
-/obj/projectile/bullet/pellet/Move()
- . = ..()
-
- //If this is a shrapnel explosion, allow mobs that are prone to get hit, too
- if(. && !base_spread && isturf(loc))
- for(var/mob/living/M in loc)
- if(M.lying || !M.CanPass(src, loc)) //Bump if lying or if we would normally Bump.
- if(Bump(M)) //Bump will make sure we don't hit a mob multiple times
- return
-
-//Explosive grenade projectile, borrowed from fragmentation grenade code.
-/obj/projectile/bullet/pellet/fragment
- damage = 10
- armor_penetration = 30
- range_step = 2 //projectiles lose a fragment each time they travel this distance. Can be a non-integer.
-
- base_spread = 0 //causes it to be treated as a shrapnel explosion instead of cone
- spread_step = 20
-
- silenced = 1 //embedding messages are still produced so it's kind of weird when enabled.
- no_attack_log = 1
- muzzle_type = null
-
-/obj/projectile/bullet/pellet/fragment/strong
- damage = 15
- armor_penetration = 20
-
-/obj/projectile/bullet/pellet/fragment/weak
- damage = 4
- armor_penetration = 40
-
-/obj/projectile/bullet/pellet/fragment/rubber
- name = "stingball shrapnel"
- damage = 3
- agony = 8
- sharp = FALSE
- edge = FALSE
- damage_flag = ARMOR_MELEE
-
-/obj/projectile/bullet/pellet/fragment/rubber/strong
- damage = 8
- agony = 16
-
-// Tank rupture fragments
-/obj/projectile/bullet/pellet/fragment/tank
- name = "metal fragment"
- damage = 9 //Big chunks flying off.
- range_step = 2 //controls damage falloff with distance. projectiles lose a "pellet" each time they travel this distance. Can be a non-integer.
-
- base_spread = 0 //causes it to be treated as a shrapnel explosion instead of cone
- spread_step = 20
-
- armor_penetration = 20
-
- silenced = 1
- no_attack_log = 1
- muzzle_type = null
- pellets = 3
-
-/obj/projectile/bullet/pellet/fragment/tank/small
- name = "small metal fragment"
- damage = 6
- armor_penetration = 5
- pellets = 5
-
-/obj/projectile/bullet/pellet/fragment/tank/big
- name = "large metal fragment"
- damage = 17
- armor_penetration = 10
- range_step = 5 //controls damage falloff with distance. projectiles lose a "pellet" each time they travel this distance. Can be a non-integer.
- pellets = 1
diff --git a/code/modules/projectiles/projectile/projectile-hitscan_visuals.dm b/code/modules/projectiles/projectile/projectile-hitscan_visuals.dm
new file mode 100644
index 000000000000..f9a370eee631
--- /dev/null
+++ b/code/modules/projectiles/projectile/projectile-hitscan_visuals.dm
@@ -0,0 +1,195 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Hitscan Visuals *//
+
+/**
+ * returns a /datum/point based on where we currently are
+ */
+/obj/projectile/proc/get_tracer_point()
+ RETURN_TYPE(/datum/point)
+ var/datum/point/point = new
+ if(trajectory_moving_to)
+ // we're in move. use next px/py to respect 1. kick forwards 2. deflections
+ point.x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px
+ point.y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py
+ else
+ point.x = (x - 1) * WORLD_ICON_SIZE + current_px
+ point.y = (y - 1) * WORLD_ICON_SIZE + current_py
+ point.z = z
+ return point
+
+/**
+ * * returns a /datum/point based on where we'll be when we loosely intersect a tile
+ * * returns null if we'll never intersect it
+ * * returns our current point if we're already loosely intersecting it
+ * * loosely intersecting means that we are level with the tile in either x or y.
+ */
+/obj/projectile/proc/get_intersection_point(turf/colliding)
+ RETURN_TYPE(/datum/point)
+ ASSERT(!isnull(angle))
+
+ // calculate where we are
+ var/our_x
+ var/our_y
+ if(trajectory_moving_to)
+ // we're in move. use next px/py to respect 1. kick forwards 2. deflections
+ our_x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px
+ our_y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py
+ else
+ our_x = (x - 1) * WORLD_ICON_SIZE + current_px
+ our_y = (y - 1) * WORLD_ICON_SIZE + current_py
+
+ // calculate how far we have to go to touch their closest x / y axis
+ var/d_to_reach_x
+ var/d_to_reach_y
+
+ if(colliding.x != x)
+ switch(calculated_sdx)
+ if(0)
+ return
+ if(1)
+ if(colliding.x < x)
+ return
+ d_to_reach_x = (((colliding.x - 1) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx
+ if(-1)
+ if(colliding.x > x)
+ return
+ d_to_reach_x = (((colliding.x - 0) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx
+ else
+ d_to_reach_x = 0
+
+ if(colliding.y != y)
+ switch(calculated_sdy)
+ if(0)
+ return
+ if(1)
+ if(colliding.y < y)
+ return
+ d_to_reach_y = (((colliding.y - 1) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy
+ if(-1)
+ if(colliding.y > y)
+ return
+ d_to_reach_y = (((colliding.y - 0) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy
+ else
+ d_to_reach_y = 0
+
+ var/needed_distance = max(d_to_reach_x, d_to_reach_y)
+
+ // calculate if we'll actually be touching the tile once we go that far
+ var/future_x = our_x + needed_distance * calculated_dx
+ var/future_y = our_y + needed_distance * calculated_dy
+ // let's be slightly lenient and do 1 instead of 0.5
+ if(future_x < (colliding.x - 1) * WORLD_ICON_SIZE && future_x > (colliding.x) * WORLD_ICON_SIZE + 1 && \
+ future_y < (colliding.y - 1) * WORLD_ICON_SIZE && future_y > (colliding.y) * WORLD_ICON_SIZE + 1)
+ return // not gonna happen
+
+ // make the point based on how far we need to go
+ var/datum/point/point = new
+ point.x = future_x
+ point.y = future_y
+ point.z = z
+ return point
+
+/**
+ * records the start of a hitscan
+ *
+ * this can edit the point passed in!
+ */
+/obj/projectile/proc/record_hitscan_start(datum/point/point, muzzle_marker, kick_forwards)
+ if(!hitscanning)
+ return
+ if(isnull(point))
+ point = get_tracer_point()
+ tracer_vertices = list(point)
+ tracer_muzzle_flash = muzzle_marker
+
+ // kick forwards
+ point.shift_in_projectile_angle(angle, kick_forwards)
+
+/**
+ * ends the hitscan tracer
+ *
+ * this can edit the point passed in!
+ */
+/obj/projectile/proc/record_hitscan_end(datum/point/point, impact_marker, kick_forwards)
+ if(!hitscanning)
+ return
+ if(isnull(point))
+ point = get_tracer_point()
+ tracer_vertices += point
+ tracer_impact_effect = impact_marker
+
+ // kick forwards
+ point.shift_in_projectile_angle(angle, kick_forwards)
+
+/**
+ * records a deflection (change in angle, aka generate new tracer)
+ */
+/obj/projectile/proc/record_hitscan_deflection(datum/point/point)
+ if(!hitscanning)
+ return
+ if(isnull(point))
+ point = get_tracer_point()
+ // there's no way you need more than 25
+ // if this is hit, fix your shit, don't bump this up; there's absolutely no reason for example,
+ // to simulate reflectors working !!25!! times.
+ if(length(tracer_vertices) >= 25)
+ CRASH("tried to add more than 25 vertices to a hitscan tracer")
+ tracer_vertices += point
+
+/obj/projectile/proc/render_hitscan_tracers(duration = tracer_duration)
+ // don't stay too long
+ ASSERT(duration >= 0 && duration <= 10 SECONDS)
+ // check everything
+ if(!has_tracer || !duration || !length(tracer_vertices))
+ return
+ var/list/atom/movable/beam_components = list()
+
+ // muzzle
+ if(muzzle_type && tracer_muzzle_flash)
+ var/datum/point/starting = tracer_vertices[1]
+ var/atom/movable/muzzle_effect = starting.instantiate_movable_with_unmanaged_offsets(muzzle_type)
+ if(muzzle_effect)
+ // turn it
+ var/matrix/muzzle_transform = matrix()
+ muzzle_transform.Turn(original_angle)
+ muzzle_effect.transform = muzzle_transform
+ muzzle_effect.color = color
+ muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color)
+ // add to list
+ beam_components += muzzle_effect
+ // impact
+ if(impact_type && tracer_impact_effect)
+ var/datum/point/starting = tracer_vertices[length(tracer_vertices)]
+ var/atom/movable/impact_effect = starting.instantiate_movable_with_unmanaged_offsets(impact_type)
+ if(impact_effect)
+ // turn it
+ var/matrix/impact_transform = matrix()
+ impact_transform.Turn(angle)
+ impact_effect.transform = impact_transform
+ impact_effect.color = color
+ impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color)
+ // add to list
+ beam_components += impact_effect
+ // path tracers
+ if(tracer_type)
+ var/tempref = "\ref[src]"
+ for(var/i in 1 to length(tracer_vertices) - 1)
+ var/j = i + 1
+ var/datum/point/first_point = tracer_vertices[i]
+ var/datum/point/second_point = tracer_vertices[j]
+ generate_tracer_between_points(first_point, second_point, beam_components, tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref)
+
+ QDEL_LIST_IN(beam_components, duration)
+
+/obj/projectile/proc/cleanup_hitscan_tracers()
+ tracer_vertices = null
+
+/obj/projectile/proc/finalize_hitscan_tracers(datum/point/end_point, impact_effect, kick_forwards)
+ // if end wasn't recorded yet and we're still on a turf, record end
+ if(isnull(tracer_impact_effect) && loc)
+ record_hitscan_end(end_point, impact_marker = impact_effect, kick_forwards = kick_forwards)
+ // render & cleanup
+ render_hitscan_tracers()
+ cleanup_hitscan_tracers()
diff --git a/code/modules/projectiles/projectile/projectile-physics.dm b/code/modules/projectiles/projectile/projectile-physics.dm
new file mode 100644
index 000000000000..ed446daf0ff0
--- /dev/null
+++ b/code/modules/projectiles/projectile/projectile-physics.dm
@@ -0,0 +1,434 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Physics - Configuration *//
+
+/**
+ * sets our angle
+ */
+/obj/projectile/proc/set_angle(new_angle)
+ angle = new_angle
+
+ // update sprite
+ if(!nondirectional_sprite)
+ var/matrix/M = new
+ M.Turn(angle)
+ transform = M
+
+ // update trajectory
+ calculated_dx = sin(new_angle)
+ calculated_dy = cos(new_angle)
+ calculated_sdx = calculated_dx == 0? 0 : (calculated_dx > 0? 1 : -1)
+ calculated_sdy = calculated_dy == 0? 0 : (calculated_dy > 0? 1 : -1)
+
+ var/normalized_to_first_quadrant = MODULUS_F(new_angle, 90)
+ angle_chebyshev_divisor = cos(normalized_to_first_quadrant >= 45? (90 - normalized_to_first_quadrant) : normalized_to_first_quadrant)
+
+ // record our tracer's change
+ if(hitscanning)
+ record_hitscan_deflection()
+
+/**
+ * sets our speed in pixels per decisecond
+ */
+/obj/projectile/proc/set_speed(new_speed)
+ speed = clamp(new_speed, 1, WORLD_ICON_SIZE * 100)
+
+/**
+ * sets our angle and speed
+ */
+/obj/projectile/proc/set_velocity(new_angle, new_speed)
+ // this is so this can be micro-optimized later but for once i'm not going to do it early for no reason
+ set_speed(new_speed)
+ set_angle(new_angle)
+
+/**
+ * todo: this is somewhat mildly terrible
+ */
+/obj/projectile/proc/set_homing_target(atom/A)
+ if(!A || (!isturf(A) && !isturf(A.loc)))
+ return FALSE
+ homing = TRUE
+ homing_target = A
+ homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max)
+ homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max)
+ if(prob(50))
+ homing_offset_x = -homing_offset_x
+ if(prob(50))
+ homing_offset_y = -homing_offset_y
+
+/**
+ * initializes physics vars
+ */
+/obj/projectile/proc/setup_physics()
+ distance_travelled = 0
+
+/**
+ * called after an unhandled forcemove is detected, or other event
+ * that should reset our on-turf state
+ */
+/obj/projectile/proc/reset_physics_to_turf()
+ // we use this because we can center larger than 32x32 projectiles
+ // without disrupting physics this way
+ //
+ // we add by (WORLD_ICON_SIZE / 2) because
+ // pixel_x / pixel_y starts at center,
+ //
+ current_px = pixel_x - base_pixel_x + (WORLD_ICON_SIZE / 2)
+ current_py = pixel_y - base_pixel_y + (WORLD_ICON_SIZE / 2)
+ // interrupt active move logic
+ trajectory_moving_to = null
+
+//* Physics - Processing *//
+
+/obj/projectile/process(delta_time)
+ if(paused)
+ return
+ physics_iteration(min(10 * WORLD_ICON_SIZE, delta_time * speed * SSprojectiles.global_projectile_speed_multiplier), delta_time)
+
+/**
+ * immediately processes hitscan
+ */
+/obj/projectile/proc/physics_hitscan(safety = 250, resuming)
+ // setup
+ if(!resuming)
+ hitscanning = TRUE
+ record_hitscan_start(muzzle_marker = TRUE, kick_forwards = 16)
+
+ // just move as many times as we can
+ while(!QDELETED(src) && loc)
+ // check safety
+ safety--
+ if(safety <= 0)
+ // if you're here, you shouldn't be. do not bump safety up, fix whatever
+ // you're doing because no one should be making projectiles go more than 250
+ // tiles in a single life.
+ stack_trace("projectile hit iteration limit for hitscan")
+ break
+
+ // move forwards by 1 tile length
+ var/pixels_moved = physics_step(WORLD_ICON_SIZE)
+ distance_travelled += pixels_moved
+ // if we're being yanked, yield
+ if(movable_flags & MOVABLE_IN_MOVED_YANK)
+ spawn(0)
+ physics_hitscan(safety, TRUE)
+ return
+
+ // see if we're done
+ if(distance_travelled >= range)
+ legacy_on_range()
+ break
+
+ hitscanning = FALSE
+
+/**
+ * ticks forwards a number of pixels
+ *
+ * todo: potential lazy animate support for performance, as we honestly don't need to animate at full fps if the server's above 20fps
+ *
+ * * delta_tiem is in deciseconds, not seconds.
+ */
+/obj/projectile/proc/physics_iteration(pixels, delta_time, additional_animation_length)
+ // setup iteration
+ var/safety = 15
+ var/pixels_remaining = pixels
+ distance_travelled_this_iteration = 0
+
+ // apply penalty
+ var/penalizing = clamp(trajectory_penalty_applied, 0, pixels_remaining)
+ pixels_remaining -= penalizing
+ trajectory_penalty_applied -= penalizing
+
+ // clamp to max distance
+ pixels_remaining = min(pixels_remaining, range - distance_travelled)
+
+ // move as many times as we need to
+ //
+ // * break if we're loc = null (by deletion or otherwise)
+ // * break if we get paused
+ while(pixels_remaining > 0)
+ // check safety
+ safety--
+ if(safety <= 0)
+ CRASH("ran out of safety! what happened?")
+
+ // move
+ var/pixels_moved = physics_step(pixels_remaining)
+ distance_travelled += pixels_moved
+ distance_travelled_this_iteration += pixels_moved
+ pixels_remaining -= pixels_moved
+ // we're being yanked, yield
+ if(movable_flags & MOVABLE_IN_MOVED_YANK)
+ spawn(0)
+ physics_iteration(pixels_remaining, delta_time, distance_travelled_this_iteration)
+ return
+ // this is also a catch-all for deletion
+ if(!loc || paused)
+ break
+
+ // penalize next one if we were kicked forwards forcefully too far
+ trajectory_penalty_applied = max(0, -pixels_remaining)
+
+ // if we don't have a loc anymore just bail
+ if(!loc)
+ return
+
+ // if we're at max range
+ if(distance_travelled >= range)
+ // todo: egh
+ legacy_on_range()
+ if(QDELETED(src))
+ return
+
+ // process homing
+ physics_tick_homing(delta_time)
+
+ // perform animations
+ // we assume at this point any deflections that should have happened, has happened,
+ // so we just do a naive animate based on our current loc and pixel x/y
+ //
+ // todo: animation needs to take into account angle changes,
+ // but that's expensive as shit so uh lol
+ //
+ // the reason we use distance_travelled_this_iteration is so if something disappears
+ // by forceMove or whatnot,
+ // we won't have it bounce from its previous location to the new one as it's not going
+ // to be accurate anymore
+ //
+ // so instead, as of right now, we backtrack via how much we know we moved.
+ var/final_px = base_pixel_x + current_px - (WORLD_ICON_SIZE / 2)
+ var/final_py = base_pixel_y + current_py - (WORLD_ICON_SIZE / 2)
+ var/anim_dist = distance_travelled_this_iteration + additional_animation_length
+ pixel_x = final_px - (anim_dist * sin(angle))
+ pixel_y = final_py - (anim_dist * cos(angle))
+
+ animate(
+ src,
+ delta_time,
+ flags = ANIMATION_END_NOW,
+ pixel_x = final_px,
+ pixel_y = final_py,
+ )
+
+/**
+ * based on but exactly http://www.cs.yorku.ca/~amana/research/grid.pdf
+ *
+ * move into the next tile, or the specified number of pixels,
+ * whichever is less pixels moved
+ *
+ * this will modify our current_px/current_py as necessary
+ *
+ * @return pixels moved
+ */
+/obj/projectile/proc/physics_step(limit)
+ // distance to move in our angle to get to next turf for horizontal and vertical
+ var/d_next_horizontal = \
+ (calculated_sdx? ((calculated_sdx > 0? (WORLD_ICON_SIZE + 0.5) - current_px : -current_px + 0.5) / calculated_dx) : INFINITY)
+ var/d_next_vertical = \
+ (calculated_sdy? ((calculated_sdy > 0? (WORLD_ICON_SIZE + 0.5) - current_py : -current_py + 0.5) / calculated_dy) : INFINITY)
+ var/turf/move_to_target
+
+ /**
+ * explanation on why current and next are done:
+ *
+ * projectiles track their pixel x/y on turf, not absolute pixel x/y from edge of map
+ * this is done to make it simpler to reason about, but is not necessarily the most simple
+ * or efficient way to do things.
+ *
+ * part of the problems with this approach is that Move() is not infallible. the projectile can be blocked.
+ * if we immediately set current pixel x/y, if the projectile is intercepted by a Bump, we now dont' know the 'real'
+ * position of the projectile because it's out of sync with where it should be
+ *
+ * now, things that require math operations on it don't know the actual location of the projectile until this proc
+ * rolls it back
+ *
+ * so instead, we never touch current px/py until the move is known to be successful, then we set it
+ * to the stored next px/py
+ *
+ * this way, things accessing can mutate our state freely without worrying about needing to handle rollbacks
+ *
+ * this entire system however adds overhead
+ * if we want to not have overhead, we'll need to rewrite hit processing and have it so moves are fully illegal to fail
+ * but doing that is literally not possible because anything can reject a move for any reason whatsoever
+ * and we cannot control that, so, instead, we make projectiles track in absolute pixel x/y coordinates from edge of map
+ *
+ * that way, we don't even need to care about where the .loc is, we just know where the projectile is supposed to be by
+ * knowing where it isn't, and by taking the change in its pixels the projectile controller can tell the projectile
+ * where to go-
+ *
+ * (all shitposting aside, this is for future work; it works right now and we have an API to do set angle, kick forwards, etc)
+ * (so i'm not going to touch this more because it's 4 AM and honestly this entire raycaster is already far less overhead)
+ * (than the old system of a 16-loop of brute forced 2 pixel increments)
+ */
+
+ if(d_next_horizontal == d_next_vertical)
+ // we're diagonal
+ if(d_next_horizontal <= limit)
+ move_to_target = locate(x + calculated_sdx, y + calculated_sdy, z)
+ . = d_next_horizontal
+ if(!move_to_target)
+ // we hit the world edge and weren't transit; time to get deleted.
+ if(hitscanning)
+ finalize_hitscan_tracers(impact_effect = FALSE)
+ qdel(src)
+ return
+ next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
+ next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
+ else if(d_next_horizontal < d_next_vertical)
+ // closer is to move left/right
+ if(d_next_horizontal <= limit)
+ move_to_target = locate(x + calculated_sdx, y, z)
+ . = d_next_horizontal
+ if(!move_to_target)
+ // we hit the world edge and weren't transit; time to get deleted.
+ if(hitscanning)
+ finalize_hitscan_tracers(impact_effect = FALSE)
+ qdel(src)
+ return
+ next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
+ next_py = current_py + d_next_horizontal * calculated_dy
+ else if(d_next_vertical < d_next_horizontal)
+ // closer is to move up/down
+ if(d_next_vertical <= limit)
+ move_to_target = locate(x, y + calculated_sdy, z)
+ . = d_next_vertical
+ if(!move_to_target)
+ // we hit the world edge and weren't transit; time to get deleted.
+ if(hitscanning)
+ finalize_hitscan_tracers(impact_effect = FALSE)
+ qdel(src)
+ return
+ next_px = current_px + d_next_vertical * calculated_dx
+ next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5)
+
+ // if we need to move
+ if(move_to_target)
+ var/atom/old_loc = loc
+ trajectory_moving_to = move_to_target
+ // mark next distance so impact processing can work
+ next_distance = distance_travelled + .
+ if(!Move(move_to_target) && ((loc != move_to_target) || !trajectory_moving_to))
+ // if we don't successfully move, don't change anything, we didn't move.
+ . = 0
+ if(loc == old_loc)
+ stack_trace("projectile failed to move, but is still on turf instead of deleted or relocated.")
+ qdel(src) // bye
+ else
+ // only do these if we successfully move, or somehow end up in that turf anyways
+ if(trajectory_kick_forwards)
+ . += trajectory_kick_forwards
+ trajectory_kick_forwards = 0
+ current_px = next_px
+ current_py = next_py
+ #ifdef CF_PROJECTILE_RAYCAST_VISUALS
+ new /atom/movable/render/projectile_raycast(move_to_target, current_px, current_py, "#77ff77")
+ #endif
+ trajectory_moving_to = null
+ else
+ // not moving to another tile, so, just move on current tile
+ if(trajectory_kick_forwards)
+ trajectory_kick_forwards = 0
+ stack_trace("how did something kick us forwards when we didn't even move?")
+ . = limit
+ current_px += limit * calculated_dx
+ current_py += limit * calculated_dy
+ next_px = current_px
+ next_py = current_py
+ #ifdef CF_PROJECTILE_RAYCAST_VISUALS
+ new /atom/movable/render/projectile_raycast(loc, current_px, current_py, "#ff3333")
+ #endif
+
+#ifdef CF_PROJECTILE_RAYCAST_VISUALS
+GLOBAL_VAR_INIT(projectile_raycast_debug_visual_delay, 2 SECONDS)
+
+/atom/movable/render/projectile_raycast
+ plane = OBJ_PLANE
+ icon = 'icons/system/color_32x32.dmi'
+ icon_state = "white-pixel"
+
+/**
+ * px, py are absolute pixel coordinates on the tile, not pixel_x / pixel_y of this renderer!
+ */
+/atom/movable/render/projectile_raycast/Initialize(mapload, px, py, color)
+ src.pixel_x = px - 1
+ src.pixel_y = py - 1
+ src.color = color
+ . = ..()
+ QDEL_IN(src, GLOB.projectile_raycast_debug_visual_delay)
+#endif
+
+/**
+ * immediately, without processing, kicks us forward a number of pixels
+ *
+ * since we immediately cross over into a turf when entering,
+ * things like mirrors/reflectors will immediately set angle
+ *
+ * it looks ugly and is pretty bad to just reflect off the edge of a turf so said things can
+ * call this proc to kick us forwards by a bit
+ */
+/obj/projectile/proc/physics_kick_forwards(pixels)
+ trajectory_kick_forwards += pixels
+ next_px += pixels * calculated_dx
+ next_py += pixels * calculated_dy
+
+/**
+ * only works during non-hitscan
+ *
+ * this is called once per tick
+ * homing is smoother the higher fps the server / SSprojectiles runs at
+ *
+ * todo: this is somewhat mildly terrible
+ * todo: this has absolutely no arc/animation support; this is bad
+ */
+/obj/projectile/proc/physics_tick_homing(delta_time)
+ if(!homing)
+ return FALSE
+ // checks if they're 1. on a turf, 2. on our z
+ // todo: should we add support for tracking something even if it leaves a turf?
+ if(homing_target?.z != z)
+ // bye bye!
+ return FALSE
+ // todo: this assumes single-tile objects. at some point, we should upgrade this to be unnecessarily expensive and always center-mass.
+ var/dx = (homing_target.x - src.x) * WORLD_ICON_SIZE + (0 - current_px) + homing_offset_x
+ var/dy = (homing_target.y - src.y) * WORLD_ICON_SIZE + (0 - current_py) + homing_offset_y
+ // say it with me, arctan()
+ // is CCW of east if (dx, dy)
+ // and CW of north if (dy, dx)
+ // where dx and dy is distance in x/y pixels from us to them.
+
+ var/nudge_towards = closer_angle_difference(arctan(dy, dx))
+ var/max_turn_speed = homing_turn_speed * delta_time
+
+ set_angle(angle + clamp(nudge_towards, -max_turn_speed, max_turn_speed))
+
+//* Physics - Querying *//
+
+/**
+ * predict what turf we'll be in after going forwards a certain amount of pixels
+ *
+ * doesn't actually sim; so this will go through walls/obstacles!
+ *
+ * * if we go out of bounds, we will return null; this doesn't level-wrap
+ */
+/obj/projectile/proc/physics_predicted_turf_after_iteration(pixels)
+ // -1 at the end if 0, because:
+ //
+ // -32 is go back 1 tile and be at the 1st pixel (as 0 is going back)
+ // 0 is go back 1 tile and be at the 32nd pixel.
+ var/incremented_px = (current_px + pixels * calculated_dx) || - 1
+ var/incremented_py = (current_py + pixels * calculated_dy) || - 1
+
+ var/incremented_tx = floor(incremented_px / 32)
+ var/incremented_ty = floor(incremented_py / 32)
+
+ return locate(x + incremented_tx, y + incremented_ty, z)
+
+/**
+ * predict what turfs we'll hit, excluding the current turf, after going forwards
+ * a certain amount of pixels
+ *
+ * doesn't actually sim; so this will go through walls/obstacles!
+ */
+/obj/projectile/proc/physics_predicted_turfs_during_iteration(pixels)
+ return pixel_physics_raycast(loc, current_px, current_py, angle, pixels)
diff --git a/code/modules/projectiles/projectile/projectile-tracing.dm b/code/modules/projectiles/projectile/projectile-tracing.dm
new file mode 100644
index 000000000000..7c4b4d10014f
--- /dev/null
+++ b/code/modules/projectiles/projectile/projectile-tracing.dm
@@ -0,0 +1,68 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station developers. *//
+
+/obj/projectile/trace
+ atom_flags = ATOM_ABSTRACT | ATOM_NONWORLD
+ invisibility = INVISIBILITY_ABSTRACT
+ hitscan = TRUE
+ has_tracer = FALSE
+ damage = 0
+ nodamage = TRUE
+ projectile_type = PROJECTILE_TYPE_TRACE
+
+ /// did we manage to hit the given target?
+ var/could_hit_target = FALSE
+ /// delete on hitting target?
+ var/del_on_success = TRUE
+ /// do we check opacity?
+ var/check_opacity = FALSE
+ /// do we only care about opacity, and not pass flags or anything else?
+ var/only_opacity = FALSE
+ /// do we only need to reach their turf?
+ var/require_turf_only = FALSE
+ /// target turf, if we only require reaching their turf
+ var/turf/require_turf_cached
+
+/obj/projectile/trace/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
+ if(only_opacity && !blocker.opacity)
+ return TRUE
+ return ..()
+
+/obj/projectile/trace/pre_impact(atom/target, impact_flags, def_zone)
+ if(target == original_target)
+ could_hit_target = TRUE
+ if(del_on_success)
+ return PROJECTILE_IMPACT_DELETE
+ if(get_turf(target) == require_turf_cached)
+ could_hit_target = TRUE
+ if(del_on_success)
+ return PROJECTILE_IMPACT_DELETE
+ . = ..()
+ // tracers only count as 'can move across' if pre_impact() says we should phase/pierce.
+ if(. & PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH)
+ return PROJECTILE_IMPACT_PASSTHROUGH
+ else
+ return PROJECTILE_IMPACT_DELETE
+
+/obj/projectile/trace/Moved()
+ . = ..()
+ if(QDELETED(src))
+ return
+ if(require_turf_cached == loc)
+ could_hit_target = TRUE
+ if(del_on_success)
+ qdel(src)
+ return
+ if(check_opacity && isturf(loc))
+ // *sigh* //
+ var/turf/T = loc
+ if(T.has_opaque_atom)
+ qdel(src)
+
+/**
+ * always call this before firing.
+ */
+/obj/projectile/trace/proc/prepare_trace(atom/target)
+ src.original_target = target
+ if(require_turf_only)
+ src.require_turf_cached = get_turf(target)
diff --git a/code/modules/projectiles/projectile/projectile.dm b/code/modules/projectiles/projectile/projectile.dm
new file mode 100644
index 000000000000..dd6fc8e9bab8
--- /dev/null
+++ b/code/modules/projectiles/projectile/projectile.dm
@@ -0,0 +1,1180 @@
+/**
+ * ## Physics Specifications
+ *
+ * We track physics as absolute pixel on a tile, not byond's pixel x/y
+ * thus the first pixel at bottom left of tile is 1, 1
+ * and the last pixel at top right is 32, 32 (for a world icon size of 32 pixels)
+ *
+ * We cross over to the next tile at above 32, for up/right,
+ * and to the last tile at below 1, for bottom/left.
+ *
+ * The code might handle it based on how it's implemented,
+ * but as long as the error is 1 pixel or below, it's not a big deal.
+ *
+ * The reason we're so accurate (1 pixel/below is pretty insanely strict) is
+ * so players have the projectile act like what the screen says it should be like;
+ * hence why projectiles can realistically path across corners based on their 'hitbox center'.
+ */
+/obj/projectile
+ name = "projectile"
+ icon = 'icons/obj/projectiles.dmi'
+ icon_state = "bullet"
+ density = FALSE
+ anchored = TRUE
+ integrity_flags = INTEGRITY_INDESTRUCTIBLE | INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ depth_level = INFINITY // nothing should be passing over us from depth
+
+ //* Collision Handling *//
+
+ /** PROJECTILE PIERCING
+ * WARNING:
+ * Projectile piercing MUST be done using these variables.
+ * Ordinary passflags will result in can_hit_target being false unless directly clicked on - similar to pass_flags_phase but without even going to process_hit.
+ * The two flag variables below both use pass flags.
+ * In the context of ATOM_PASS_THROWN, it means the projectile will ignore things that are currently "in the air" from a throw.
+ *
+ * Also, projectiles sense hits using Bump(), and then pierce them if necessary.
+ * They simply do not follow conventional movement rules.
+ * NEVER flag a projectile as PHASING movement type.
+ * If you so badly need to make one go through *everything*, override check_pierce() for your projectile to always return PROJECTILE_PIERCE_PHASE/HIT.
+ */
+ /// The "usual" flags of pass_flags is used in that can_hit_target ignores these unless they're specifically targeted/clicked on. This behavior entirely bypasses process_hit if triggered, rather than phasing which uses prehit_pierce() to check.
+ pass_flags = ATOM_PASS_TABLE
+ /// we handle our own go through
+ generic_canpass = FALSE
+ /// If FALSE, allow us to hit something directly targeted/clicked/whatnot even if we're able to phase through it
+ var/phases_through_direct_target = FALSE
+ /// anything with these pass flags are automatically pierced
+ var/pass_flags_pierce = NONE
+ /// anything with these pass flags are automatically phased through
+ var/pass_flags_phase = NONE
+ /// number of times we've pierced something. Incremented BEFORE bullet_act and similar procs run!
+ var/pierces = 0
+ /// What we already hit
+ /// initialized on fire()
+ var/list/impacted
+
+ //* -- Combat - Accuracy -- *//
+ //* These are applied in additional to mob
+ //* evasion and miss handling! Projectiles should *//
+ //* generally be pretty accurate for that reason. *//
+ //* *//
+ //* All accuracy ranges use **manhattan** *//
+ //* distance, not euclidean! *//
+
+ /// if enabled, projectile-side baymiss is entirely disabled
+ ///
+ /// * the target can still forcefully miss us, unfortunately, if it doesn't use the standard API
+ /// * this might be violently fixed in the future
+ var/accuracy_disabled = FALSE
+ /// perfect accuracy below this range (in pixels)
+ ///
+ /// * this means the projectile doesn't enforce inaccuracy; not the target!
+ /// * remember that even if a projectile clips a single pixel on a target turf, it hits.
+ /// * right now, accuracy is slightly more than it should be due to distance being ticked up post-impact.
+ var/accuracy_perfect_range = WORLD_ICON_SIZE * 7
+ /// linear - accuracy outside of perfect range
+ ///
+ /// * [0, 100] inclusive as %
+ var/accuracy_drop_start = 100
+ /// linear - hit % falloff per pixel
+ var/accuracy_drop_slope = 5 / WORLD_ICON_SIZE
+ /// linear - lowest possible accuracy to drop to
+ var/accuracy_drop_end = 20
+ /// alter end result hit probability by this value
+ ///
+ /// todo: is this really the best way?
+ ///
+ /// * this is a multiplier for hit chance if less than 1
+ /// * this is a divisor for miss chance if more than 1
+ /// * 0.5 will turn a 80% hit to a 40%
+ /// * 2 will turn a 80% hit to a 90%
+ /// * 2 will turn a 40% hit to a 70%
+ var/accuracy_overall_modify = 1
+
+ //* Combat - Effects *//
+
+ /// projectile effects
+ ///
+ /// * this is a static typelist on this typepath
+ /// * do not under any circumstances edit this
+ /// * this is /tmp because this should never change on a typepath
+ VAR_PROTECTED/tmp/list/base_projectile_effects
+ /// projectile effects
+ ///
+ /// * this is configured at runtime and can be edited
+ /// * this is non /tmp because this is infact serializable
+ VAR_PROTECTED/list/additional_projectile_effects
+
+ //* Configuration *//
+
+ /// Projectile type bitfield; set all that is relevant
+ var/projectile_type = NONE
+ /// Impact ground on expiry (range, or lifetime)
+ var/impact_ground_on_expiry = FALSE
+
+ //* Physics - Configuration *//
+
+ /// speed, in pixels per second
+ var/speed = 25 * WORLD_ICON_SIZE
+ /// are we a hitscan projectile?
+ var/hitscan = FALSE
+ /// angle, in degrees **clockwise of north**
+ var/angle
+ /// multiplier to distance travelled at the **current** [angle] to get it to chebyshev dist
+ var/angle_chebyshev_divisor
+ /// max distance in pixels
+ ///
+ /// * please set this to a multiple of [WORLD_ICON_SIZE] so we scale with tile size.
+ var/range = WORLD_ICON_SIZE * 50
+ // todo: lifespan
+
+ //* Physics - Homing *//
+ /// Are we homing in on something?
+ var/homing = FALSE
+ /// current homing target
+ var/atom/homing_target
+ /// angle per second
+ ///
+ /// * this is smoother the less time between SSprojectiles fires
+ var/homing_turn_speed = 100
+ /// rand(min, max) for inaccuracy offsets
+ var/homing_inaccuracy_min = 0
+ /// rand(min, max) for inaccuracy offsets
+ var/homing_inaccuracy_max = 0
+ /// pixels; added to the real location of target so we're not exactly on-point
+ var/homing_offset_x = 0
+ /// pixels; added to the real location of target so we're not exactly on-point
+ var/homing_offset_y = 0
+
+ //* Physics - Tracers *//
+
+ /// tracer /datum/point's
+ var/list/tracer_vertices
+ /// first point is a muzzle effect
+ var/tracer_muzzle_flash
+ /// last point is an impact
+ var/tracer_impact_effect
+ /// default tracer duration
+ var/tracer_duration = 5
+
+ //* Physics - State *//
+
+ /// paused? if so, we completely get passed over during processing
+ var/paused = FALSE
+ /// currently hitscanning
+ var/hitscanning = FALSE
+ /// a flag to prevent movement hooks from resetting our physics on a forced movement
+ var/trajectory_ignore_forcemove = FALSE
+ /// cached value: move this much x for this much distance
+ /// basically, dx / distance
+ var/calculated_dx
+ /// cached value: move this much y for this much distance
+ /// basically, dy / distance
+ var/calculated_dy
+ /// cached sign of dx; 1, -1, or 0
+ var/calculated_sdx
+ /// cached sign of dy; 1, -1, or 0
+ var/calculated_sdy
+ /// our current pixel location on turf
+ /// byond pixel_x rounds, and we don't want that
+ ///
+ /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf
+ var/current_px
+ /// our current pixel location on turf
+ /// byond pixel_y rounds, and we don't want that
+ ///
+ /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf
+ var/current_py
+ /// the pixel location we're moving to, or the [current_px] after this iteration step
+ ///
+ /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step.
+ /// * only valid if [trajectory_moving_to] is set
+ var/next_px
+ /// the pixel location we're moving to, or the [current_px] after this iteration step
+ ///
+ /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step.
+ /// * only valid if [trajectory_moving_to] is set
+ var/next_py
+ /// the pixel distance we'll have travelled after the current Move()
+ ///
+ /// * use this during impact processing or you'll be off by anywhere from 1 to 32 pixels.
+ /// * only valid if [trajectory_moving_to] is set
+ var/next_distance
+ /// used to track if we got kicked forwards after calling Move()
+ var/trajectory_kick_forwards = 0
+ /// to avoid going too fast when kicked forwards by a mirror, if we overshoot the pixels we're
+ /// supposed to move this gets set to penalize the next move with a weird algorithm
+ /// that i won't bother explaining
+ var/trajectory_penalty_applied = 0
+ /// currently travelled distance in pixels
+ var/distance_travelled
+ /// if we get forcemoved, this gets reset to 0 as a trip
+ /// this way, we know exactly how far we moved
+ var/distance_travelled_this_iteration
+ /// where the physics loop and/or some other thing moving us is trying to move to
+ /// used to determine where to draw hitscan tracers
+ // todo: this being here is kinda a symptom that things are handled weirdly but whatever
+ // optimally physics loop should handle tracking for stuff like animations, not require on hit processing to check turfs
+ var/turf/trajectory_moving_to
+
+ //* Targeting *//
+
+ /// Originally clicked target
+ var/atom/original_target
+
+ //* legacy below *//
+
+ //Fired processing vars
+ var/fired = FALSE //Have we been fired yet
+
+ var/original_angle = 0 //Angle at firing
+ var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
+ var/spread = 0 //amount (in degrees) of projectile spread
+ animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy
+ var/ricochets = 0
+ var/ricochets_max = 2
+ var/ricochet_chance = 30
+
+ //Hitscan
+ /// do we have a tracer? if not we completely ignore hitscan logic
+ var/has_tracer = TRUE
+ var/tracer_type
+ var/muzzle_type
+ var/impact_type
+
+ var/miss_sounds
+ var/ricochet_sounds
+ var/list/impact_sounds //for different categories, IMPACT_MEAT etc
+
+ //Fancy hitscan lighting effects!
+ var/hitscan_light_intensity = 1.5
+ var/hitscan_light_range = 0.75
+ var/hitscan_light_color_override
+ var/muzzle_flash_intensity = 3
+ var/muzzle_flash_range = 1.5
+ var/muzzle_flash_color_override
+ var/impact_light_intensity = 3
+ var/impact_light_range = 2
+ var/impact_light_color_override
+
+ //Targetting
+ var/yo = null
+ var/xo = null
+ var/turf/starting = null // the projectile's starting turf
+ var/p_x = 16
+ var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
+
+ var/def_zone = BP_TORSO
+ var/mob/firer = null//Who shot it
+ var/silenced = 0 //Attack message
+ var/shot_from = "" // name of the object which shot us
+
+ var/dispersion = 0.0
+
+ // Sub-munitions. Basically, multi-projectile shotgun, rather than pellets.
+ var/use_submunitions = FALSE
+ var/only_submunitions = FALSE // Will the projectile delete itself after firing the submunitions?
+ var/list/submunitions = list() // Assoc list of the paths of any submunitions, and how many they are. [projectilepath] = [projectilecount].
+ var/submunition_spread_max = 30 // Divided by 10 to get the percentile dispersion.
+ var/submunition_spread_min = 5 // Above.
+ /// randomize spread? if so, evenly space between 0 and max on each side.
+ var/submunition_constant_spread = FALSE
+ var/force_max_submunition_spread = FALSE // Do we just force the maximum?
+ var/spread_submunition_damage = FALSE // Do we assign damage to our sub projectiles based on our main projectile damage?
+
+ //? Damage - default handling
+ /// damage amount
+ var/damage = 10
+ /// damage tier - goes hand in hand with [damage_armor]
+ var/damage_tier = BULLET_TIER_DEFAULT
+ /// todo: legacy - BRUTE, BURN, TOX, OXY, CLONE, HALLOSS, ELECTROCUTE, BIOACID are the only things that should be in here
+ var/damage_type = BRUTE
+ /// armor flag for damage - goes hand in hand with [damage_tier]
+ var/damage_flag = ARMOR_BULLET
+ /// damage mode - see [code/__DEFINES/combat/damage.dm]
+ var/damage_mode = NONE
+
+ var/SA_bonus_damage = 0 // Some bullets inflict extra damage on simple animals.
+ var/SA_vulnerability = null // What kind of simple animal the above bonus damage should be applied to. Set to null to apply to all SAs.
+ var/nodamage = 0 //Determines if the projectile will skip any damage inflictions
+ var/taser_effect = 0 //If set then the projectile will apply it's agony damage using stun_effect_act() to mobs it hits, and other damage will be ignored
+ var/legacy_penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate()
+ //Effects
+ var/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech
+ var/flammability = 0 //Amount of fire stacks to add for the above.
+ var/combustion = TRUE //Does this set off flammable objects on fire/hit?
+ var/stun = 0
+ var/weaken = 0
+ var/paralyze = 0
+ var/irradiate = 0
+ var/stutter = 0
+ var/eyeblur = 0
+ var/drowsy = 0
+ var/agony = 0
+ var/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections.
+ var/modifier_type_to_apply = null // If set, will apply a modifier to mobs that are hit by this projectile.
+ var/modifier_duration = null // How long the above modifier should last for. Leave null to be permanent.
+ var/excavation_amount = 0 // How much, if anything, it drills from a mineral turf.
+ /// If this projectile is holy. Silver bullets, etc. Currently no effects.
+ var/holy = FALSE
+
+ // Antimagic
+ /// Should we check for antimagic effects?
+ var/antimagic_check = FALSE
+ /// Antimagic charges to use, if any
+ var/antimagic_charges_used = 0
+ /// Multiplier for damage if antimagic is on the target
+ var/antimagic_damage_factor = 0
+
+ var/embed_chance = 0 //Base chance for a projectile to embed
+
+ var/fire_sound = 'sound/weapons/Gunshot_old.ogg' // Can be overriden in gun.dm's fire_sound var. It can also be null but I don't know why you'd ever want to do that. -Ace
+
+ // todo: currently unimplemneted
+ var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum.
+
+ var/no_attack_log = FALSE
+
+/obj/projectile/Initialize(mapload)
+ if(islist(base_projectile_effects))
+ base_projectile_effects = typelist(NAMEOF(src, base_projectile_effects), base_projectile_effects)
+ return ..()
+
+/obj/projectile/Destroy()
+ // stop processing
+ STOP_PROCESSING(SSprojectiles, src)
+ // cleanup
+ cleanup_hitscan_tracers()
+ return ..()
+
+/obj/projectile/proc/process_legacy_penetration(atom/A)
+ return TRUE
+
+/obj/projectile/proc/legacy_on_range() //if we want there to be effects when they reach the end of their range
+ finalize_hitscan_tracers(impact_effect = FALSE, kick_forwards = 8)
+
+ for(var/datum/projectile_effect/effect as anything in base_projectile_effects)
+ if(effect.hook_lifetime)
+ effect.on_lifetime(src, impact_ground_on_expiry)
+ for(var/datum/projectile_effect/effect as anything in additional_projectile_effects)
+ if(effect.hook_lifetime)
+ effect.on_lifetime(src, impact_ground_on_expiry)
+
+ if(impact_ground_on_expiry)
+ impact(loc, PROJECTILE_IMPACT_IS_EXPIRING)
+
+ expire()
+
+/obj/projectile/proc/fire(set_angle_to, atom/direct_target, no_source_check)
+ if(only_submunitions) // refactor projectiles whwen holy shit this is awful lmao
+ // todo: this should make a muzzle flash
+ qdel(src)
+ return
+
+ // setup impact checking
+ impacted = list()
+ // make sure firer is in it
+ if(firer && !no_source_check)
+ impacted[firer] = TRUE
+ if(ismob(firer))
+ var/atom/buckle_iterating = firer.buckled
+ while(buckle_iterating)
+ if(impacted[buckle_iterating])
+ CRASH("how did we loop in buckle iteration check?")
+ impacted[buckle_iterating] = TRUE
+ if(ismob(buckle_iterating))
+ var/mob/cast_for_next = buckle_iterating
+ buckle_iterating = cast_for_next.buckled
+ else
+ break
+ // set angle if needed
+ if(isnum(set_angle_to))
+ set_angle(set_angle_to)
+ // handle direct hit
+ if(direct_target)
+ if(direct_target.bullet_act(src, PROJECTILE_IMPACT_POINT_BLANK, def_zone) & PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH)
+ impacted[direct_target] = TRUE
+ else
+ // todo: this should make a muzzle flash
+ qdel(src)
+ return
+ // setup physics
+ setup_physics()
+
+ // legacy below
+ var/turf/starting = get_turf(src)
+ if(isnull(angle)) //Try to resolve through offsets if there's no angle set.
+ if(isnull(xo) || isnull(yo))
+ stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!")
+ qdel(src)
+ return
+ var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z)
+ set_angle(get_visual_angle(src, target))
+ if(dispersion)
+ set_angle(angle + rand(-dispersion, dispersion))
+ original_angle = angle
+ forceMove(starting)
+ fired = TRUE
+ // legacy aboev
+
+ // start physics & kickstart movement
+ if(hitscan)
+ physics_hitscan()
+ else
+ START_PROCESSING(SSprojectiles, src)
+ physics_iteration(WORLD_ICON_SIZE, SSprojectiles.wait)
+
+//Spread is FORCED!
+/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0)
+ var/turf/curloc = get_turf(source)
+ var/turf/targloc = get_turf(target)
+
+ if(istype(source, /atom/movable))
+ var/atom/movable/MT = source
+ if(MT.locs && MT.locs.len) // Multi tile!
+ for(var/turf/T in MT.locs)
+ if(get_dist(T, target) < get_turf(curloc))
+ curloc = get_turf(T)
+
+ trajectory_ignore_forcemove = TRUE
+ forceMove(get_turf(source))
+ trajectory_ignore_forcemove = FALSE
+ starting = curloc
+ original_target = target
+ if(targloc || !params)
+ yo = targloc.y - curloc.y
+ xo = targloc.x - curloc.x
+ set_angle(get_visual_angle(src, targloc) + spread)
+
+ if(isliving(source) && params)
+ var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params)
+ p_x = calculated[2]
+ p_y = calculated[3]
+
+ set_angle(calculated[1] + spread)
+ else if(targloc)
+ yo = targloc.y - curloc.y
+ xo = targloc.x - curloc.x
+ set_angle(get_visual_angle(src, targloc) + spread)
+ else
+ stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!")
+ qdel(src)
+
+/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params)
+ var/list/mouse_control = params2list(params)
+ var/p_x = 0
+ var/p_y = 0
+ var/angle = 0
+ if(mouse_control["icon-x"])
+ p_x = text2num(mouse_control["icon-x"])
+ if(mouse_control["icon-y"])
+ p_y = text2num(mouse_control["icon-y"])
+ if(mouse_control["screen-loc"])
+ //Split screen-loc up into X+Pixel_X and Y+Pixel_Y
+ var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
+
+ //Split X+Pixel_X up into list(X, Pixel_X)
+ var/list/screen_loc_X = splittext(screen_loc_params[1],":")
+
+ //Split Y+Pixel_Y up into list(Y, Pixel_Y)
+ var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
+ var/x = text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32
+ var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32
+
+ //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average.
+ var/screenviewX = user.client.current_viewport_width * world.icon_size
+ var/screenviewY = user.client.current_viewport_height * world.icon_size
+
+ var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x
+ var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y
+ angle = arctan(y - oy, x - ox)
+ return list(angle, p_x, p_y)
+
+/obj/projectile/proc/legacy_redirect(x, y, starting, source)
+ reflected = TRUE
+ old_style_target(locate(x, y, z), starting? get_turf(starting) : get_turf(source))
+
+/obj/projectile/proc/old_style_target(atom/target, atom/source)
+ if(!source)
+ source = get_turf(src)
+ starting = get_turf(source)
+ original_target = target
+ set_angle(get_visual_angle(source, target))
+
+/obj/projectile/proc/vol_by_damage()
+ if(damage)
+ return clamp((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then clamp the value between 30 and 100
+ else
+ return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume.
+
+//Checks if the projectile is eligible for embedding. Not that it necessarily will.
+/obj/projectile/proc/can_embed()
+ //embed must be enabled and damage type must be brute
+ if(embed_chance == 0 || damage_type != BRUTE)
+ return 0
+ return 1
+
+/obj/projectile/proc/get_structure_damage()
+ if(damage_type == BRUTE || damage_type == BURN)
+ return damage
+ return 0
+
+/obj/projectile/proc/check_fire(atom/target as mob, mob/living/user as mob) //Checks if you can hit them or not.
+ check_trajectory(target, user, pass_flags, atom_flags)
+
+/**
+ * i hate everything
+ *
+ * todo: refactor guns
+ * projectiles
+ * and everything else
+ *
+ * i am losing my fucking mind
+ * this shouldn't have to fucking exist because the ammo casing and/or gun should be doing it
+ * and submunitions SHOULDNT BE HANDLED HERE!!
+ */
+/obj/projectile/proc/launch_projectile_common(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
+ original_target = target
+ def_zone = check_zone(target_zone)
+ firer = user
+
+ if(use_submunitions && submunitions.len)
+ var/temp_min_spread = 0
+ if(force_max_submunition_spread)
+ temp_min_spread = submunition_spread_max
+ else
+ temp_min_spread = submunition_spread_min
+
+ var/damage_override = null
+
+ if(spread_submunition_damage)
+ damage_override = damage
+ if(nodamage)
+ damage_override = 0
+
+ var/projectile_count = 0
+
+ for(var/proj in submunitions)
+ projectile_count += submunitions[proj]
+
+ damage_override = round(damage_override / max(1, projectile_count))
+
+ for(var/path in submunitions)
+ var/amt = submunitions[path]
+ for(var/count in 1 to amt)
+ var/obj/projectile/SM = new path(get_turf(loc))
+ SM.shot_from = shot_from
+ SM.silenced = silenced
+ if(!isnull(damage_override))
+ SM.damage = damage_override
+ if(submunition_constant_spread)
+ SM.dispersion = 0
+ var/calculated = angle + round((count / amt - 0.5) * submunition_spread_max, 1)
+ SM.launch_projectile(target, target_zone, user, params, calculated)
+ else
+ SM.dispersion = rand(temp_min_spread, submunition_spread_max) / 10
+ SM.launch_projectile(target, target_zone, user, params, angle_override)
+
+/obj/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
+ var/direct_target
+ if(get_turf(target) == get_turf(src))
+ direct_target = target
+
+ preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread)
+ launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread)
+ return fire(angle_override, direct_target)
+
+//called to launch a projectile from a gun
+/obj/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/gun/launcher)
+
+ shot_from = launcher.name
+ silenced = launcher.silenced
+
+ return launch_projectile(target, target_zone, user, params, angle_override, forced_spread)
+
+/obj/projectile/proc/launch_projectile_from_turf(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0)
+ var/direct_target
+ if(get_turf(target) == get_turf(src))
+ direct_target = target
+
+ preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread)
+ launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread)
+ return fire(angle_override, direct_target)
+
+/**
+ * Standard proc to determine damage when impacting something. This does not affect the special damage variables/effect variables, only damage and damtype.
+ * May or may not be called before/after armor calculations.
+ *
+ * @params
+ * - target The atom hit
+ *
+ * @return Damage to apply to target.
+ */
+/obj/projectile/proc/run_damage_vulnerability(atom/target)
+ var/final_damage = damage
+ if(isliving(target))
+ var/mob/living/L = target
+ if(issimple(target))
+ var/mob/living/simple_mob/SM = L
+ if(SM.mob_class & SA_vulnerability)
+ final_damage += SA_bonus_damage
+ if(L.anti_magic_check(TRUE, TRUE, antimagic_charges_used, FALSE))
+ final_damage *= antimagic_damage_factor
+ return final_damage
+
+/**
+ * Probably isn't needed but saves me the time and I can regex this later:
+ * Gets the final `damage` that should be used on something
+ */
+/obj/projectile/proc/get_final_damage(atom/target)
+ return run_damage_vulnerability(target)
+
+// !legacy code above!
+
+//* Collision Handling *//
+
+/obj/projectile/CanAllowThrough()
+ SHOULD_CALL_PARENT(FALSE)
+ return TRUE
+
+/obj/projectile/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
+ // performance
+ SHOULD_CALL_PARENT(FALSE)
+ // always can go through already impacted things
+ if(impacted[blocker])
+ return TRUE
+ return blocker_opinion
+
+/obj/projectile/Crossed(atom/movable/AM)
+ ..()
+ scan_crossed_atom(AM)
+
+// todo: should we inline this?
+/obj/projectile/proc/scan_crossed_atom(atom/movable/target)
+ if(!should_impact(target))
+ return
+ impact(target)
+
+/obj/projectile/Bump(atom/A)
+ . = ..()
+ impact_loop(get_turf(A), A)
+
+/obj/projectile/forceMove(atom/target)
+ var/is_a_jump = isturf(target) != isturf(loc) || target.z != z || !trajectory_ignore_forcemove
+ if(is_a_jump)
+ record_hitscan_end()
+ render_hitscan_tracers()
+ . = ..()
+ if(!.)
+ stack_trace("projectile forcemove failed; please do not try to forcemove projectiles to invalid locations!")
+ distance_travelled_this_iteration = 0
+ if(!trajectory_ignore_forcemove)
+ reset_physics_to_turf()
+ if(is_a_jump)
+ record_hitscan_start()
+
+/obj/projectile/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
+ . = ..()
+ // if we're being yanked, we'll get Moved() again
+ if(movable_flags & MOVABLE_IN_MOVED_YANK)
+ return
+ // not even fired yet
+ if(!fired)
+ return
+ // scan the turf for anything we need to hit
+ scan_moved_turf(loc)
+ // trigger effects
+ for(var/datum/projectile_effect/effect as anything in base_projectile_effects)
+ if(effect.hook_moved)
+ effect.on_moved(src, old_loc)
+ for(var/datum/projectile_effect/effect as anything in additional_projectile_effects)
+ if(effect.hook_moved)
+ effect.on_moved(src, old_loc)
+
+// todo: should we inline this?
+/obj/projectile/proc/scan_moved_turf(turf/tile)
+ if(original_target?.loc != tile)
+ return
+ if(!should_impact(original_target))
+ return
+ impact(original_target)
+
+/**
+ * checks if we're valid to hit a target
+ *
+ * this is called 'should' because it's not called in impact()
+ * this checks if we should try to impact when processing collisions
+ * this doesn't actually prevent us from having an impact call
+ */
+/obj/projectile/proc/should_impact(atom/target, is_colliding_us)
+ // 1. emulate the usual physics / cross
+ // remember that impact_loop() scans all atoms, not just the hit one.
+ if(impacted[target])
+ return FALSE
+ if(QDELETED(target))
+ return FALSE
+ // 1.5: legacy bullshit
+ if(target.is_incorporeal())
+ return FALSE
+ // 2. are they the thing blocking us?
+ if(is_colliding_us)
+ return TRUE
+ // 3. process projectile things
+ if(target == original_target)
+ return TRUE
+ else if(!target.density || (target.pass_flags_self & pass_flags))
+ return FALSE
+ else if(target.layer < PROJECTILE_HIT_THRESHOLD_LAYER)
+ return FALSE
+ return TRUE
+
+/**
+ * this strangely named proc is basically the hit processing loop
+ *
+ * "now why the hells would you do this"
+ *
+ * well, you see, turf movement handling doesn't support what we need to do,
+ * and for good reason.
+ *
+ * most of the time, turf movement handling is more than enough for any game use case.
+ * it is not nearly accurate/comprehensive enough for projectiles
+ * and we're not going to make it that, because that's a ton of overhead for everything else
+ *
+ * so instead, projectiles handle it themselves on a Bump().
+ *
+ * when this happens, the projectile should hit everything that's going to collide it anyways
+ * in the turf, not just one thing; this way, hits are instant for a given collision.
+ *
+ * @params
+ * * was_moving_onto - the turf we were moving to
+ * * bumped - what bumped us?
+ */
+/obj/projectile/proc/impact_loop(turf/was_moving_onto, atom/bumped)
+ var/impact_return
+ // so unfortunately, only one border object is considered here
+ // why?
+ // because you see, for speed reasons we're not going to iterate once just to gather border.
+ // so we assume that 'bumped' is border, or it just doesn't happen.
+
+ // make sure we're not inf looping
+ ASSERT(!(impacted[bumped]))
+ // see if we should impact
+ impact_return = impact(bumped)
+ if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP))
+ return
+
+ // at this point we're technically safe to just move again because
+ // we processed bumped
+ // problem is, that's O(n^2) behavior
+ // we don't want to process just one bumped atom
+ // as turf/Enter() will loop through everything again
+ //
+ // we want to process all of them.
+
+ // at this point you might ask
+ // why not use MOVEMENT_UNSTOPPABLE?
+ // if we did, we wouldn't have the border-prioritization we have
+ // that'd be bad as you'd be hit by a bullet behind a directional window
+
+ // wow, projectiles are annoying to deal with
+
+ // begin: main loop
+
+ // first, targeted atom
+ if(original_target?.loc == was_moving_onto)
+ if(should_impact(original_target))
+ impact_return = impact(bumped)
+ if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP))
+ return
+
+ // then, mobs
+ for(var/mob/mob_target in was_moving_onto)
+ if(!should_impact(mob_target))
+ continue
+ impact_return = impact(mob_target)
+ if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP))
+ return
+
+ // then, objs
+ for(var/obj/obj_target in was_moving_onto)
+ if(!should_impact(obj_target))
+ continue
+ impact_return = impact(obj_target)
+ if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP))
+ return
+
+ // then, the turf
+ if(should_impact(was_moving_onto))
+ impact_return = impact(was_moving_onto)
+ if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP))
+ return
+
+ // if we passed everything and we're still going,
+ // we can safely move onto their turf again, and this time we should succeed.
+ if(trajectory_moving_to)
+ Move(trajectory_moving_to)
+
+//* Lifetime & Deletion *//
+
+// todo: on_lifetime() --> expire()
+
+/**
+ * Called to delete if:
+ *
+ * * ran out of range
+ * * hit something and shouldn't pass through
+ *
+ * @params
+ * * impacting - we're deleting from impact, rather than range
+ */
+/obj/projectile/proc/expire(impacting)
+ qdel(src)
+
+//* Impact Processing *//
+
+/**
+ * Called to perform an impact
+ *
+ * @params
+ * * target - thing being hit
+ * * impact_flags - impact flags to feed in
+ * * def_zone - zone to hit; otherwise this'll be calculated.
+ *
+ * @return resultant impact_flags
+ */
+/obj/projectile/proc/impact(atom/target, impact_flags, def_zone = src.def_zone || BP_TORSO)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ if(impacted[target])
+ return impact_flags | PROJECTILE_IMPACT_PASSTHROUGH | PROJECTILE_IMPACT_DUPLICATE | PROJECTILE_IMPACT_CONTINUE_LOOP
+ impacted[target] = TRUE
+ var/where_we_were = loc
+ impact_flags = pre_impact(target, impact_flags, def_zone)
+ var/keep_going
+
+ // priority 1: delete?
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE)
+ if(hitscanning)
+ finalize_hitscan_tracers()
+ qdel(src)
+ return impact_flags
+ // priority 2: should we hit?
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT)
+ keep_going = TRUE
+ // phasing?
+ if(impact_flags & PROJECTILE_IMPACT_PHASE)
+ impact_flags = on_phase(target, impact_flags, def_zone)
+ // reflect?
+ else if(impact_flags & PROJECTILE_IMPACT_REFLECT)
+ impact_flags = on_reflect(target, impact_flags, def_zone)
+ // else, is passthrough. do nothing
+ else
+ impact_flags = target.bullet_act(src, impact_flags, def_zone, 1)
+ // did we pierce?
+ if(impact_flags & PROJECTILE_IMPACT_PIERCE)
+ keep_going = TRUE
+ impact_flags = on_pierce(target, impact_flags, def_zone)
+ // are we otherwise meant to keep going?
+ else if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH)
+ keep_going = TRUE
+
+ // did anything triggered up above trigger a delete?
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE)
+ if(hitscanning)
+ finalize_hitscan_tracers()
+ qdel(src)
+ return impact_flags
+
+ // trigger projectile effects
+ if(base_projectile_effects || additional_projectile_effects)
+ // todo: can we move this to on_impact_new and have 'blocked' passed in? hrm.
+ for(var/datum/projectile_effect/effect as anything in base_projectile_effects)
+ if(effect.hook_impact)
+ impact_flags = effect.on_impact(src, target, impact_flags, def_zone)
+ for(var/datum/projectile_effect/effect as anything in additional_projectile_effects)
+ if(effect.hook_impact)
+ impact_flags = effect.on_impact(src, target, impact_flags, def_zone)
+ // did anything triggered up above trigger a delete?
+ if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE)
+ if(hitscanning)
+ finalize_hitscan_tracers()
+ qdel(src)
+ return impact_flags
+
+ // see if we should keep going or delete
+ if(keep_going)
+ if(loc == where_we_were)
+ // if we are supposed to keep going and we didn't get yanked, continue the impact loop.
+ impact_flags |= PROJECTILE_IMPACT_CONTINUE_LOOP
+ else
+ // or if we aren't supposed to keep going, delete.
+ if(hitscanning)
+ if(trajectory_moving_to)
+ // create tracers
+ var/datum/point/visual_impact_point = get_intersection_point(trajectory_moving_to)
+ // it's possible to not have an intersection point, if say, angle was being messed with mid-move
+ // this entire system is suboptimal design-wise but atleast it's fast.
+ if(visual_impact_point)
+ // kick it forwards a bit
+ visual_impact_point.shift_in_projectile_angle(angle, 2)
+ // draw
+ finalize_hitscan_tracers(visual_impact_point, impact_effect = TRUE)
+ else
+ finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32)
+ else
+ finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32)
+ expire(TRUE)
+
+ return impact_flags
+
+/**
+ * Called at the start of impact.
+ *
+ * * Hooks to return flags / whatnot should happen here
+ * * You are allowed to edit the projectile here, but it is absolutely not recommended.
+ *
+ * @return new impact_flags
+ */
+/obj/projectile/proc/pre_impact(atom/target, impact_flags, def_zone)
+ if(target.pass_flags_self & pass_flags_phase)
+ return impact_flags | PROJECTILE_IMPACT_PHASE
+ if(target.pass_flags_self & pass_flags_pierce)
+ return impact_flags | PROJECTILE_IMPACT_PIERCE
+ return impact_flags
+
+/**
+ * Called after bullet_act() of the target.
+ *
+ * * Please take into account impact_flags.
+ * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact().
+ * * please see [/atom/proc/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)]
+ * * Args at this point are no longer mutable after the ..() call.
+ *
+ * Things to keep in mind, if you ignore the above and didn't read bullet_act():
+ * * Args are changed directly and passed up, but not passed back down. This means setting efficiency at base of /on_impact doesn't change a subtype's call
+ * * This also means you need to default efficiency to 1 if you have things acting on it, as it won't be propagated for you.
+ * * 'efficiency' is extremely powerful
+ * * impact_flags having PROJECTILE_IMPACT_DELETE is a good sign to delete and do nothing else.
+ *
+ * todo: add PROJECTILE_IMPACT_DELETE_AFTER as opposed to DELETE? so rest of effects can still run
+ *
+ * @return new impact_flags; only PROJECTILE_IMPACT_DELETE is rechecked.
+ */
+/obj/projectile/proc/on_impact(atom/target, impact_flags, def_zone, efficiency = 1)
+ //! legacy shit
+ var/blocked = clamp((1 - efficiency) * 100, 0, 100)
+ if(damage && damage_type == BURN)
+ var/turf/T = get_turf(target)
+ if(T)
+ T.hotspot_expose(700, 5)
+ if(isliving(target))
+ var/mob/living/L = target
+ L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability)
+ if(modifier_type_to_apply)
+ L.add_modifier(modifier_type_to_apply, modifier_duration)
+ //! end
+ return impact_flags
+
+/**
+ * called in bullet_act() to redirect our impact to another atom
+ *
+ * use like this:
+ *
+ * `return proj.impact_redirect(target, args)`
+ *
+ * * You **must** use this, not just `return target.bullet_act(arglist(args))`
+ * * This does book-keeping like adding the target to permutated, ensure the target can't be hit multiple times in a row, and more.
+ * * Don't be too funny about bullet_act_args, it's a directly passed in args list. Don't be stupid.
+ */
+/obj/projectile/proc/impact_redirect(atom/target, list/bullet_act_args)
+ if(impacted[target])
+ return bullet_act_args[BULLET_ACT_ARG_FLAGS] | PROJECTILE_IMPACT_DUPLICATE
+ bullet_act_args[BULLET_ACT_ARG_FLAGS] |= PROJECTILE_IMPACT_INDIRECTED
+ return target.bullet_act(arglist(bullet_act_args))
+
+/**
+ * phasing through
+ *
+ * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact().
+ *
+ * @return new impact flags; only PROJETILE_IMPACT_DELETE is rechecked.
+ */
+/obj/projectile/proc/on_phase(atom/target, impact_flags, def_zone)
+ return impact_flags
+
+/**
+ * reflected off of
+ *
+ * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact().
+ *
+ * @return new impact flags; only PROJETILE_IMPACT_DELETE is rechecked.
+ */
+/obj/projectile/proc/on_reflect(atom/target, impact_flags, def_zone)
+ return impact_flags
+
+/**
+ * piercing through
+ *
+ * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact().
+ *
+ * @return new impact flags; only PROJETILE_IMPACT_DELETE is rechecked.
+ */
+/obj/projectile/proc/on_pierce(atom/target, impact_flags, def_zone)
+ return impact_flags
+
+//* Impact Processing - Combat *//
+
+/**
+ * processes default hit probability for baymiss
+ *
+ * * This is called by things like /mob/living as needed; there is no default baymiss handling on /projectile anymore.
+ * * More than 100 target_opinion means that much % more than 100 of *not missing*.
+ * * e.g. 200 target_opinion makes a 75% inherent hit chance (25% miss chance) to 87.5% hit cahnce (12.5% miss chance)
+ *
+ * todo: 0 to 100 for accuracy might not be amazing; maybe allow negative values evasion-style?
+ *
+ * @params
+ * * target - what we're hitting
+ * * target_opinion - the return from processing hit chance on their side
+ * * distance - distance in pixels
+ * * impact_check - are we checking for impact? this way things like pellets can do their own rolls after 100% hitting
+ *
+ * @return hit probability as % in [0, 100]; > 100 is allowed.
+ */
+/obj/projectile/proc/process_accuracy(atom/target, target_opinion = 100, distance, impact_check)
+ if(isnull(distance))
+ distance = (trajectory_moving_to ? next_distance : distance_travelled) * angle_chebyshev_divisor
+ if(accuracy_disabled)
+ return 100
+ . = 100
+ // perform accuracy curving
+ if(distance > accuracy_perfect_range)
+ . = accuracy_drop_start
+ var/extra_distance = distance - accuracy_perfect_range
+ var/drop_percent = extra_distance * accuracy_drop_slope
+ . = clamp(. - drop_percent, ., accuracy_drop_end)
+ if(accuracy_overall_modify != 1)
+ if(accuracy_overall_modify < 1)
+ // below 1: multiplier for hit chance
+ . *= accuracy_overall_modify
+ else
+ // above 1: divisor for miss chance
+ . = 100 - ((100 - .) / accuracy_overall_modify)
+ if(target_opinion < 100)
+ . *= (target_opinion / 100)
+ else if(target_opinion > 100)
+ . = 100 - ((100 - .) / (target_opinion / 100))
+
+/**
+ * processes zone accuracy
+ *
+ * * this is here to override 'special' baymiss, like 'don't even hit this zone' systems.
+ *
+ * @params
+ * * target - what we're hitting
+ * * target_opinion - the return from processing hit zone on their side
+ * * distance - distance in pixels
+ * * impact_check - are we checking for impact? this way things like pellets can do their own processing
+ */
+/obj/projectile/proc/process_zone_miss(atom/target, target_opinion, distance, impact_check)
+ return target_opinion
+
+/**
+ * Applies the standard damage instance to an entity.
+ *
+ * @params
+ * * target - thing being hit
+ * * efficiency - 0 to 1+ - efficiency of hit, where 0% is full block
+ * * impact_flags - impact flags passed in
+ * * hit_zone - zone to hit
+ *
+ * @return BULLET_ACT_* flags to append into the calling bullet_act().
+ */
+/obj/projectile/proc/inflict_impact_damage(atom/target, efficiency, impact_flags, hit_zone)
+ . = NONE
+
+ //! LEGACY COMBAT CODE
+ // SHIM!!!
+ var/list/shieldcall_modified_args = target.check_damage_instance(damage, damage_type, damage_tier, damage_flag, damage_mode, ATTACK_TYPE_PROJECTILE, src, SHIELDCALL_FLAG_SECOND_CALL, hit_zone)
+ // todo: this handling very obviously should not be here
+ // dear lord this code is a dumpster fire
+ if(shieldcall_modified_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_PIERCE_ATTACK)
+ . |= PROJECTILE_IMPACT_REFLECT
+ if(shieldcall_modified_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK)
+ return
+ // END
+ if(isliving(target))
+ var/mob/living/L = target
+ //Armor
+ var/soaked = L.get_armor_soak(hit_zone, src.damage_flag, src.armor_penetration)
+ var/absorb = L.run_armor_check(hit_zone, src.damage_flag, src.armor_penetration)
+ var/proj_sharp = is_sharp(src)
+ var/proj_edge = has_edge(src)
+ var/final_damage = src.get_final_damage(target) * efficiency
+
+ if ((proj_sharp || proj_edge) && (soaked >= round(src.damage*0.8)))
+ proj_sharp = 0
+ proj_edge = 0
+
+ if ((proj_sharp || proj_edge) && prob(L.legacy_mob_armor(hit_zone, src.damage_flag)))
+ proj_sharp = 0
+ proj_edge = 0
+
+ var/list/impact_sounds = islist(src.impact_sounds)? LAZYACCESS(src.impact_sounds, L.get_bullet_impact_effect_type(hit_zone)) : src.impact_sounds
+ if(length(impact_sounds))
+ playsound(L, pick(impact_sounds), 75)
+ else if(!isnull(impact_sounds))
+ playsound(L, impact_sounds, 75)
+
+ //Stun Beams
+ if(src.taser_effect)
+ L.stun_effect_act(0, src.agony, hit_zone, src)
+ to_chat(L, "You have been hit by [src]!")
+ if(!src.nodamage)
+ L.apply_damage(final_damage, src.damage_type, hit_zone, absorb, soaked, 0, src, sharp=proj_sharp, edge=proj_edge)
+ return
+
+ if(!src.nodamage)
+ L.apply_damage(final_damage, src.damage_type, hit_zone, absorb, soaked, 0, src, sharp=proj_sharp, edge=proj_edge)
+ //! END
+
+ for(var/datum/projectile_effect/effect as anything in base_projectile_effects)
+ if(effect.hook_damage)
+ effect.on_damage(src, target, impact_flags, hit_zone, efficiency)
+ for(var/datum/projectile_effect/effect as anything in additional_projectile_effects)
+ if(effect.hook_damage)
+ effect.on_damage(src, target, impact_flags, hit_zone, efficiency)
+
+ if(legacy_penetrating > 0)
+ if(process_legacy_penetration(target))
+ . |= PROJECTILE_IMPACT_PIERCE | PROJECTILE_IMPACT_PASSTHROUGH
+
+/**
+ * wip algorithm to dampen a projectile when it pierces
+ *
+ * * entity - thing hit
+ * * force - nominal force to resist the damping; generally, projectiles at this lose a moderate chunk of energy, while 2x loses minimal, 0.5x loses a lot.
+ * * tier - effective armor tier of object; modulates actual energy lost
+ */
+/obj/projectile/proc/dampen_on_pierce_experimental(atom/entity, force, tier)
+ var/tdiff = damage_tier - tier
+ var/dmult = src.damage / force
+ var/malus = dmult >= 1 ? ((1 / dmult) ** tdiff * 10) : (10 * ((1 / dmult) / (1 + tdiff)))
+ src.damage = clamp(src.damage - malus, src.damage * 0.5, src.damage)
+
+//* Targeting *//
+
+/**
+ * Checks if something is a valid target when directly clicked.
+ */
+/obj/projectile/proc/is_valid_target(atom/target)
+ if(isobj(target))
+ var/obj/O = target
+ return O.obj_flags & OBJ_RANGE_TARGETABLE
+ else if(isliving(target))
+ return TRUE
+ else if(isturf(target))
+ return target.density
+ return FALSE
diff --git a/code/modules/projectiles/projectile/projectile_effect.dm b/code/modules/projectiles/projectile/projectile_effect.dm
new file mode 100644
index 000000000000..cadfae340b97
--- /dev/null
+++ b/code/modules/projectiles/projectile/projectile_effect.dm
@@ -0,0 +1,51 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * projectile effects
+ *
+ * * can be anonymous type'd, so global cache exists for given projectile typepath, not here!
+ */
+/datum/projectile_effect
+ /// has impact effect
+ var/hook_impact = FALSE
+ /// has moved effect
+ var/hook_moved = FALSE
+ /// has on-range / lifetime expired effect
+ var/hook_lifetime = FALSE
+ /// has damage effect
+ var/hook_damage = FALSE
+
+/**
+ * * you'll note that [efficiency] is not a thing here
+ * * this is because this runs regardless of target's opinion
+ * * this means you should probably be careful and check impact_flags!
+ *
+ * @return new impact flags
+ */
+/datum/projectile_effect/proc/on_impact(obj/projectile/proj, atom/target, impact_flags, def_zone)
+ return impact_flags
+
+/**
+ * * you'll note that [efficiency] **is** a thing here
+ * * this only runs if we're able to damage the target
+ *
+ * @return new impact flags
+ */
+/datum/projectile_effect/proc/on_damage(obj/projectile/proj, atom/target, impact_flags, def_zone, efficiency)
+ return impact_flags
+
+/**
+ * Do not delete the projectile, the projectile does that.
+ *
+ * todo: add way to delete projectile so it doesn't drop stuff i guess for foam and whatnot
+ *
+ * @params
+ * * proj - the projectile
+ * * impact_ground_on_expiry - we should impact the ground on expiry; a projectile var, but relay'd in for override capability later
+ */
+/datum/projectile_effect/proc/on_lifetime(obj/projectile/proj, impact_ground_on_expiry)
+ return
+
+/datum/projectile_effect/proc/on_moved(obj/projectile/proj, atom/old_loc)
+ return
diff --git a/code/modules/projectiles/projectile/animate.dm b/code/modules/projectiles/projectile/subtypes/animate.dm
similarity index 100%
rename from code/modules/projectiles/projectile/animate.dm
rename to code/modules/projectiles/projectile/subtypes/animate.dm
diff --git a/code/modules/projectiles/projectile/subtypes/arc.dm b/code/modules/projectiles/projectile/subtypes/arc.dm
new file mode 100644
index 000000000000..7245b3001119
--- /dev/null
+++ b/code/modules/projectiles/projectile/subtypes/arc.dm
@@ -0,0 +1,82 @@
+
+//////////////
+// Subtypes
+//////////////
+
+// This is a test projectile in the sense that its testing the code to make sure it works,
+// as opposed to a 'can I hit this thing' projectile.
+/obj/projectile/arc/test/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
+ new /obj/effect/explosion(T)
+ T.color = "#FF0000"
+
+// Generic, Hivebot related
+/obj/projectile/arc/blue_energy
+ name = "energy missile"
+ icon_state = "force_missile"
+ damage = 15
+ damage_type = BURN
+
+/obj/projectile/arc/blue_energy/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
+ for(var/mob/living/L in T)
+ impact(L)
+
+// Fragmentation arc shot
+/obj/projectile/arc/fragmentation
+ name = "fragmentation shot"
+ icon_state = "shell"
+ var/list/fragment_types = list(
+ /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment, \
+ /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment/strong
+ )
+ var/fragment_amount = 63 // Same as a grenade.
+ var/spread_range = 7
+
+/obj/projectile/arc/fragmentation/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
+ T.shrapnel_explosion(fragment_amount, spread_range, fragment_types, src, name, firer)
+
+/obj/projectile/arc/fragmentation/mortar
+ icon_state = "mortar"
+ fragment_amount = 10
+ spread_range = 3
+
+// EMP arc shot
+/obj/projectile/arc/emp_blast
+ name = "emp blast"
+ icon_state = "bluespace"
+
+/obj/projectile/arc/emp_blast/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ var/turf/T = target
+ empulse(T, 2, 4, 7, 10) // Normal EMP grenade.
+ return impact_flags
+
+/obj/projectile/arc/emp_blast/weak/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ var/turf/T = target
+ empulse(T, 1, 2, 3, 4) // Sec EMP grenade.
+ return impact_flags
+
+// Radiation arc shot
+/obj/projectile/arc/radioactive
+ name = "radiation blast"
+ icon_state = "green_pellet"
+ icon_scale_x = 2
+ icon_scale_y = 2
+ var/rad_power = RAD_INTENSITY_PROJ_ARC
+
+/obj/projectile/arc/radioactive/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = target
+ radiation_pulse(T, rad_power)
diff --git a/code/modules/projectiles/projectile/beam/beams.dm b/code/modules/projectiles/projectile/subtypes/beam/beams.dm
similarity index 89%
rename from code/modules/projectiles/projectile/beam/beams.dm
rename to code/modules/projectiles/projectile/subtypes/beam/beams.dm
index b90a9342d3a7..617097a40deb 100644
--- a/code/modules/projectiles/projectile/beam/beams.dm
+++ b/code/modules/projectiles/projectile/subtypes/beam/beams.dm
@@ -6,6 +6,7 @@
damage = 40
damage_type = BURN
damage_flag = ARMOR_LASER
+ projectile_type = PROJECTILE_TYPE_BEAM | PROJECTILE_TYPE_PHOTONIC
eyeblur = 4
var/frequency = 1
hitscan = TRUE
@@ -76,14 +77,14 @@
/obj/projectile/beam/heavylaser/cannon
damage = 80
- armor_penetration = 50
+ armor_penetration = 45
light_color = "#FF0D00"
/obj/projectile/beam/xray
name = "xray beam"
icon_state = "xray"
fire_sound = 'sound/weapons/eluger.ogg'
- damage = 25
+ damage = 30
armor_penetration = 50
light_color = "#00CC33"
@@ -131,7 +132,8 @@
name = "emitter beam"
icon_state = "emitter"
fire_sound = 'sound/weapons/emitter.ogg'
- damage = 0 // The actual damage is computed in /code/modules/power/singularity/emitter.dm
+ damage = 40
+ armor_penetration = 70
light_color = "#00CC33"
excavation_amount = 70 // 3 shots to mine a turf
@@ -143,7 +145,6 @@
name = "lasertag beam"
damage = 0
eyeblur = 0
- no_attack_log = 1
damage_type = BURN
damage_flag = ARMOR_LASER
@@ -157,23 +158,27 @@
tracer_type = /obj/effect/projectile/tracer/laser_blue
impact_type = /obj/effect/projectile/impact/laser_blue
-/obj/projectile/beam/lasertag/blue/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/beam/lasertag/blue/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ishuman(target))
var/mob/living/carbon/human/M = target
if(istype(M.wear_suit, /obj/item/clothing/suit/redtag))
- M.afflict_paralyze(20 * 5)
- return 1
+ M.afflict_paralyze(1.5 SECONDS)
/obj/projectile/beam/lasertag/red
icon_state = "laser"
light_color = "#FF0D00"
-/obj/projectile/beam/lasertag/red/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/beam/lasertag/red/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ishuman(target))
var/mob/living/carbon/human/M = target
if(istype(M.wear_suit, /obj/item/clothing/suit/bluetag))
- M.afflict_paralyze(20 * 5)
- return 1
+ M.afflict_paralyze(1.5 SECONDS)
/obj/projectile/beam/lasertag/omni//A laser tag bolt that stuns EVERYONE
icon_state = "omnilaser"
@@ -183,12 +188,14 @@
tracer_type = /obj/effect/projectile/tracer/laser_omni
impact_type = /obj/effect/projectile/impact/laser_omni
-/obj/projectile/beam/lasertag/omni/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/beam/lasertag/omni/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ishuman(target))
var/mob/living/carbon/human/M = target
if((istype(M.wear_suit, /obj/item/clothing/suit/bluetag))||(istype(M.wear_suit, /obj/item/clothing/suit/redtag)))
- M.afflict_paralyze(20 * 5)
- return 1
+ M.afflict_paralyze(1.5 SECONDS)
/obj/projectile/beam/sniper
name = "sniper beam"
@@ -267,18 +274,18 @@
tracer_type = /obj/effect/projectile/tracer/laser_omni
impact_type = /obj/effect/projectile/impact/laser_omni
-/obj/projectile/beam/stun/disabler/on_hit(atom/target, blocked = 0, def_zone)
- . = ..(target, blocked, def_zone)
-
- if(. && istype(target, /mob/living/silicon/robot) && prob(agony))
+/obj/projectile/beam/stun/disabler/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT))
+ return
+ if(istype(target, /mob/living/silicon/robot) && prob(agony))
var/mob/living/silicon/robot/R = target
var/drainamt = agony * (rand(5, 15) / 10)
// 100 to 300 drain
R.drain_energy(DYNAMIC_CELL_UNITS_TO_KJ(drainamt * 10))
if(istype(firer, /mob/living/silicon/robot)) // Mischevious sappers, the swarm drones are.
var/mob/living/silicon/robot/A = firer
- if(A.cell)
- A.cell.give(drainamt * 2)
+ A.cell?.give(drainamt * 2)
/obj/projectile/beam/shock
name = "shock beam"
diff --git a/code/modules/projectiles/projectile/beam/beams_vr.dm b/code/modules/projectiles/projectile/subtypes/beam/beams_vr.dm
similarity index 84%
rename from code/modules/projectiles/projectile/beam/beams_vr.dm
rename to code/modules/projectiles/projectile/subtypes/beam/beams_vr.dm
index 7e2e9e04484c..7e21f84b61cc 100644
--- a/code/modules/projectiles/projectile/beam/beams_vr.dm
+++ b/code/modules/projectiles/projectile/subtypes/beam/beams_vr.dm
@@ -26,9 +26,13 @@
tracer_type = /obj/effect/projectile/tracer/xray
impact_type = /obj/effect/projectile/impact/xray
-/obj/projectile/beam/energy_net/on_hit(var/atom/netted)
- do_net(netted)
- ..()
+/obj/projectile/beam/energy_net/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ if(!ismob(target))
+ return
+ do_net(target)
/obj/projectile/beam/energy_net/proc/do_net(var/mob/M)
var/obj/item/energy_net/net = new (get_turf(M))
@@ -47,7 +51,6 @@
icon_state = "healbeam"
damage = 0 //stops it damaging walls
nodamage = TRUE
- no_attack_log = TRUE
damage_type = BURN
damage_flag = ARMOR_LASER
light_color = "#80F5FF"
@@ -58,7 +61,11 @@
tracer_type = /obj/effect/projectile/tracer/medigun
impact_type = /obj/effect/projectile/impact/medigun
-/obj/projectile/beam/medigun/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/beam/medigun/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(istype(target, /mob/living/carbon/human))
var/mob/living/carbon/human/M = target
if(M.health < M.maxHealth)
@@ -74,4 +81,3 @@
M.adjustFireLoss(-15)
M.adjustToxLoss(-5)
M.adjustOxyLoss(-5)
- return 1
diff --git a/code/modules/projectiles/projectile/beam/blaster.dm b/code/modules/projectiles/projectile/subtypes/beam/blaster.dm
similarity index 100%
rename from code/modules/projectiles/projectile/beam/blaster.dm
rename to code/modules/projectiles/projectile/subtypes/beam/blaster.dm
diff --git a/code/modules/projectiles/projectile/blob.dm b/code/modules/projectiles/projectile/subtypes/blob.dm
similarity index 92%
rename from code/modules/projectiles/projectile/blob.dm
rename to code/modules/projectiles/projectile/subtypes/blob.dm
index 6b643c160f2b..cbb0fae06578 100644
--- a/code/modules/projectiles/projectile/blob.dm
+++ b/code/modules/projectiles/projectile/subtypes/blob.dm
@@ -25,7 +25,11 @@
reagents = null
..()
-/obj/projectile/energy/blob/on_impact(var/atom/A)
+/obj/projectile/energy/blob/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(splatter)
var/turf/location = get_turf(src)
var/datum/effect_system/smoke_spread/chem/S = new /datum/effect_system/smoke_spread/chem
@@ -34,7 +38,6 @@
playsound(location, 'sound/effects/slime_squish.ogg', 30, 1, -3)
spawn(0)
S.start()
- ..()
/obj/projectile/energy/blob/proc/ready_chemicals()
if(reagents)
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/subtypes/bullets.dm
similarity index 84%
rename from code/modules/projectiles/projectile/bullets.dm
rename to code/modules/projectiles/projectile/subtypes/bullets.dm
index c6d6b9ef8960..5f5b67065805 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/subtypes/bullets.dm
@@ -8,7 +8,7 @@
damage_flag = ARMOR_BULLET
embed_chance = 20 //Modified in the actual embed process, but this should keep embed chance about the same
sharp = 1
- var/mob_passthrough_check = 0
+ projectile_type = PROJECTILE_TYPE_KINETIC
muzzle_type = /obj/effect/projectile/muzzle/bullet
miss_sounds = list('sound/weapons/guns/miss1.ogg','sound/weapons/guns/miss2.ogg','sound/weapons/guns/miss3.ogg','sound/weapons/guns/miss4.ogg')
@@ -16,37 +16,16 @@
'sound/weapons/guns/ricochet3.ogg', 'sound/weapons/guns/ricochet4.ogg')
impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL)
-/obj/projectile/bullet/on_hit(var/atom/target, var/blocked = 0)
- if (..(target, blocked))
- var/mob/living/L = target
- shake_camera(L, 3, 2)
-
-/obj/projectile/bullet/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier)
- if(penetrating > 0 && damage > 20 && prob(damage))
- mob_passthrough_check = 1
- else
- mob_passthrough_check = 0
- return ..()
-
-/obj/projectile/bullet/can_embed()
- //prevent embedding if the projectile is passing through the mob
- if(mob_passthrough_check)
- return 0
- return ..()
-
-/obj/projectile/bullet/check_penetrate(var/atom/A)
- if(!A || !A.density) return 1 //if whatever it was got destroyed when we hit it, then I guess we can just keep going
-
- if(istype(A, /obj/vehicle/sealed/mecha))
- return 1 //mecha have their own penetration handling
-
- if(ismob(A))
- if(!mob_passthrough_check)
- return 0
- if(iscarbon(A))
- damage *= 0.7 //squishy mobs absorb KE
- return 1
+/obj/projectile/bullet/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ var/mob/living/L = target
+ if(!istype(L))
+ return
+ shake_camera(L, 3, 2)
+/obj/projectile/bullet/process_legacy_penetration(atom/A)
var/chance = damage
if(istype(A, /turf/simulated/wall))
var/turf/simulated/wall/W = A
@@ -57,14 +36,12 @@
if(D.glass) chance *= 2
else if(istype(A, /obj/structure/girder))
chance = 100
+ else if(ismob(A))
+ chance = damage >= 20 && prob(damage)
- if(prob(chance))
- if(A.opacity)
- //display a message so that people on the other side aren't so confused
- A.visible_message("\The [src] pierces through \the [A]!")
- return 1
-
- return 0
+ . = prob(chance)
+ if(.)
+ damage *= 0.7
/* short-casing projectiles, like the kind used in pistols or SMGs */
@@ -128,7 +105,7 @@
damage = 10 // high rof kinda fucked up lets be real
agony = 10 // brute easily heals, agony not so much
armor_penetration = 30 // reduces shield blockchance
- accuracy = -20 // he do miss actually
+ accuracy_overall_modify = 0.8 // heehoo
speed = 25 * WORLD_ICON_SIZE
/obj/projectile/bullet/pistol/medium/ap/suppressor/turbo // spicy boys
@@ -193,20 +170,15 @@
fire_sound = 'sound/weapons/weaponsounds_shotgunshot.ogg'
damage = 13
pellets = 6
- range_step = 1
- spread_step = 10
+ pellet_loss = 0.66 / WORLD_ICON_SIZE
/obj/projectile/bullet/pellet/shotgun_improvised
name = "shrapnel"
- damage = 1
+ damage = 4
pellets = 10
- range_step = 1
- spread_step = 10
/obj/projectile/bullet/pellet/shotgun/flak
damage = 2 //The main weapon using these fires four at a time, usually with different destinations. Usually.
- range_step = 2
- spread_step = 30
armor_penetration = 10
// This is my boomstick,
@@ -218,8 +190,6 @@
SA_vulnerability = MOB_CLASS_DEMONIC | MOB_CLASS_ABERRATION
embed_chance = -1
pellets = 6
- range_step = 1
- spread_step = 20
holy = TRUE
/obj/projectile/bullet/shotgun/stake
@@ -242,27 +212,31 @@
combustion = FALSE
-/obj/projectile/bullet/shotgun/ion/on_hit(var/atom/target, var/blocked = 0)
- ..()
+/obj/projectile/bullet/shotgun/ion/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
empulse(target, 0, 0, 2, 0) //Only affects what it hits
- return 1
+ . |= PROJECTILE_IMPACT_DELETE
//Frag shot
/obj/projectile/bullet/shotgun/frag12
name ="frag12 slug"
damage = 25
-/obj/projectile/bullet/shotgun/frag12/on_hit(atom/target, blocked = FALSE)
- ..()
+/obj/projectile/bullet/shotgun/frag12/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
explosion(target, -1, 0, 1)
- return 1
+ . |= PROJECTILE_IMPACT_DELETE
/* "Rifle" rounds */
/obj/projectile/bullet/rifle
fire_sound = 'sound/weapons/Gunshot_generic_rifle.ogg'
armor_penetration = 15
- penetrating = 1
+ legacy_penetrating = 1
/obj/projectile/bullet/rifle/a762
fire_sound = 'sound/weapons/weaponsounds_heavyrifleshot.ogg'
@@ -286,7 +260,7 @@
/obj/projectile/bullet/rifle/a762/hp
damage = 40
armor_penetration = -50
- penetrating = 0
+ legacy_penetrating = 0
/obj/projectile/bullet/rifle/a762/hunter // Optimized for killing simple animals and not people, because Balance(tm)
damage = 25
@@ -318,19 +292,19 @@
/obj/projectile/bullet/rifle/a556/hp
damage = 35
armor_penetration = -50
- penetrating = 0
+ legacy_penetrating = 0
/obj/projectile/bullet/rifle/a556/hunter
damage = 15
SA_bonus_damage = 35 // 50 total on animals.
SA_vulnerability = MOB_CLASS_ANIMAL
-/obj/projectile/bullet/rifle/a12_7mm // 14.5×114mm is bigger than a .50 BMG round.
+/obj/projectile/bullet/rifle/a12_7mm
fire_sound = 'sound/weapons/Gunshot_cannon.ogg' // This is literally an anti-tank rifle caliber. It better sound like a fucking cannon.
damage = 80
stun = 3
weaken = 3
- penetrating = 5
+ legacy_penetrating = 5
armor_penetration = 80
hitscan = 1 //so the PTR isn't useless as a sniper weapon
@@ -361,10 +335,12 @@
embed_chance = 0
edge = 1
-/obj/projectile/bullet/burstbullet/on_hit(var/atom/target, var/blocked = 0)
- if(isturf(target))
- explosion(target, -1, 0, 2)
- ..()
+/obj/projectile/bullet/burstbullet/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ explosion(target, -1, 0, 2)
+ . |= PROJECTILE_IMPACT_DELETE
/obj/projectile/bullet/burstbullet/service
name = "charge bullet"
@@ -376,10 +352,13 @@
SA_vulnerability = MOB_CLASS_DEMONIC | MOB_CLASS_ABERRATION
holy = TRUE
-/obj/projectile/bullet/burstbullet/service/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/bullet/burstbullet/service/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isturf(target))
explosion(target, 0, 1, 2)
- ..()
+ return . | PROJECTILE_IMPACT_DELETE
/* Black Powder */
@@ -399,8 +378,7 @@
/obj/projectile/bullet/pellet/blunderbuss //More Damage at close range greater falloff
damage = 10
pellets = 8
- range_step = 0.5 //Very quick falloff
- spread_step = 30
+ pellet_loss = 1.5 / WORLD_ICON_SIZE
/obj/projectile/bullet/pellet/blunderbuss/silver
damage = 5
@@ -418,8 +396,6 @@
/obj/projectile/bullet/pellet/heavy_shotgun //I want this to use similar calcuations to blunderbuss shot for falloff.
damage = 3 //Fires five pellets at a time.
- range_step = 0.75
- spread_step = 30
armor_penetration = 10
/obj/projectile/bullet/pellet/heavy_shotgun/silver
@@ -432,32 +408,33 @@
/obj/projectile/bullet/heavy_shotgun/grit
name = "custom heavy slug"
-/obj/projectile/bullet/heavy_shotgun/grit/on_hit(var/atom/movable/target, var/blocked = 0)
+/obj/projectile/bullet/heavy_shotgun/grit/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isliving(target))
var/mob/living/L = target
var/throwdir = get_dir(firer,L)
- if(prob(10) && !blocked)
+ if(prob(10) && (efficiency < 0.95))
L.afflict_stun(20 * 1)
L.Confuse(1)
L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10)
- return 1
-
/obj/projectile/bullet/pellet/heavy_shotgun/grit
name = "heavy buckshot"
- range_step = 1
-/obj/projectile/bullet/pellet/heavy_shotgun/grit/on_hit(var/atom/movable/target, var/blocked = 0)
+/obj/projectile/bullet/pellet/heavy_shotgun/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isliving(target))
var/mob/living/L = target
var/throwdir = get_dir(firer,L)
- if(prob(10) && !blocked)
- L.afflict_stun(20 * 1)
+ if(prob(10) && (efficiency >= 0.9))
+ L.afflict_stun(2 SECONDS)
L.Confuse(1)
L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10)
- return 1
-
/* Incendiary */
/obj/projectile/bullet/incendiary
@@ -503,10 +480,14 @@
incendiary = 1
flammability = 4
armor_penetration = 40
- penetrating = 5
+ legacy_penetrating = 5
combustion = TRUE
-/obj/projectile/bullet/incendiary/caseless/on_hit(var/atom/movable/target, var/blocked = 0)
+/obj/projectile/bullet/incendiary/caseless/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ // todo: burn this to the ground
if(isliving(target))
var/mob/living/L = target
L.adjustFireLoss(10)
@@ -519,7 +500,7 @@
damage_type = BRUTE
incendiary = 1
flammability = 4
- penetrating = 1
+ legacy_penetrating = 1
combustion = TRUE
diff --git a/code/modules/projectiles/projectile/bullets_vr.dm b/code/modules/projectiles/projectile/subtypes/bullets_vr.dm
similarity index 100%
rename from code/modules/projectiles/projectile/bullets_vr.dm
rename to code/modules/projectiles/projectile/subtypes/bullets_vr.dm
diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/subtypes/change.dm
similarity index 93%
rename from code/modules/projectiles/projectile/change.dm
rename to code/modules/projectiles/projectile/subtypes/change.dm
index a8a60f70ed98..187c92e96e7a 100644
--- a/code/modules/projectiles/projectile/change.dm
+++ b/code/modules/projectiles/projectile/subtypes/change.dm
@@ -8,8 +8,11 @@
combustion = FALSE
-/obj/projectile/change/on_hit(var/atom/change)
- wabbajack(change)
+/obj/projectile/change/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ wabbajack(target)
/obj/projectile/change/proc/wabbajack(var/mob/M)
if(istype(M, /mob/living) && M.stat != DEAD)
diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/subtypes/energy.dm
similarity index 93%
rename from code/modules/projectiles/projectile/energy.dm
rename to code/modules/projectiles/projectile/subtypes/energy.dm
index cc3b78328ffc..48717e3024a0 100644
--- a/code/modules/projectiles/projectile/energy.dm
+++ b/code/modules/projectiles/projectile/subtypes/energy.dm
@@ -4,6 +4,7 @@
damage = 0
damage_type = BURN
damage_flag = ARMOR_ENERGY
+ projectile_type = PROJECTILE_TYPE_ENERGY
var/flash_strength = 10
//releases a burst of light on impact or after travelling a distance
@@ -17,10 +18,11 @@
var/brightness = 7
var/light_colour = "#ffffff"
-/obj/projectile/energy/flash/on_impact(var/atom/A)
- var/turf/T = flash_range? src.loc : get_turf(A)
- if(!istype(T)) return
-
+/obj/projectile/energy/flash/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(!isturf(target))
+ return
+ var/turf/T = get_turf(target)
//blind adjacent people
for (var/mob/living/carbon/M in viewers(T, flash_range))
if(M.eyecheck() < 1)
@@ -56,11 +58,9 @@
incendiary = 1
flammability = 2
-/obj/projectile/energy/flash/flare/on_impact(var/atom/A)
+/obj/projectile/energy/flash/flare/on_impact(atom/target, impact_flags, def_zone, efficiency)
light_colour = pick("#e58775", "#ffffff", "#90ff90", "#a09030")
-
- ..() //initial flash
-
+ . = ..()
//residual illumination
new /obj/effect/particle_effect/smoke/illumination(src.loc, rand(190,240) SECONDS, 8, 3, light_colour) //same lighting power as flare
@@ -206,9 +206,10 @@
to_chat(M, "Your ears start to ring!")
M.update_icons() //Just to apply matrix transform for laying asap
-/obj/projectile/energy/plasmastun/on_hit(var/atom/target)
- bang(target)
+/obj/projectile/energy/plasmastun/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ bang(target)
+ return . | PROJECTILE_IMPACT_DELETE
/obj/projectile/energy/blue_pellet
name = "suppressive pellet"
diff --git a/code/modules/projectiles/projectile/energy_vr.dm b/code/modules/projectiles/projectile/subtypes/energy_vr.dm
similarity index 100%
rename from code/modules/projectiles/projectile/energy_vr.dm
rename to code/modules/projectiles/projectile/subtypes/energy_vr.dm
diff --git a/code/modules/projectiles/projectile/explosive.dm b/code/modules/projectiles/projectile/subtypes/explosive.dm
similarity index 66%
rename from code/modules/projectiles/projectile/explosive.dm
rename to code/modules/projectiles/projectile/subtypes/explosive.dm
index 05d9d1786811..8139b108fe42 100644
--- a/code/modules/projectiles/projectile/explosive.dm
+++ b/code/modules/projectiles/projectile/subtypes/explosive.dm
@@ -8,26 +8,16 @@
damage = 30 //Meaty whack. *Chuckles*
movable_flags = MOVABLE_NO_THROW_SPIN | MOVABLE_NO_THROW_DAMAGE_SCALING | MOVABLE_NO_THROW_SPEED_SCALING
-/obj/projectile/bullet/srmrocket/on_hit(atom/target, blocked=0)
- ..()
+/obj/projectile/bullet/srmrocket/on_impact(atom/target, impact_flags, def_zone, efficiency)
if(!isliving(target)) //if the target isn't alive, so is a wall or something
explosion(target, 0, 1, 2, 4)
else
explosion(target, 0, 0, 2, 4)
- return 1
-
+ return PROJECTILE_IMPACT_DELETE
/obj/projectile/bullet/srmrocket/weak //Used in the jury rigged one.
damage = 10
-/obj/projectile/bullet/srmrocket/weak/on_hit(atom/target, blocked=0)
- ..()
+/obj/projectile/bullet/srmrocket/weak/on_impact(atom/target, impact_flags, def_zone, efficiency)
explosion(target, 0, 0, 2, 4)//No need to have a question.
- return 1
-
-/*Old vars here for reference.
- var/devastation = 0
- var/heavy_blast = 1
- var/light_blast = 2
- var/flash_blast = 4
-*/
+ return PROJECTILE_IMPACT_DELETE
diff --git a/code/modules/projectiles/projectile/force.dm b/code/modules/projectiles/projectile/subtypes/force.dm
similarity index 70%
rename from code/modules/projectiles/projectile/force.dm
rename to code/modules/projectiles/projectile/subtypes/force.dm
index a95cefff5633..05e11a848f6f 100644
--- a/code/modules/projectiles/projectile/force.dm
+++ b/code/modules/projectiles/projectile/subtypes/force.dm
@@ -10,11 +10,12 @@
/obj/projectile/forcebolt/strong
name = "force bolt"
-/obj/projectile/forcebolt/on_hit(var/atom/movable/target, var/blocked = 0)
- if(istype(target))
+/obj/projectile/forcebolt/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(ismovable(target))
+ var/atom/movable/movable_target = target
var/throwdir = get_dir(firer,target)
- target.throw_at_old(get_edge_target_turf(target, throwdir),10,10)
- return 1
+ movable_target.throw_at_old(get_edge_target_turf(target, throwdir),10,10)
/*
/obj/projectile/forcebolt/strong/on_hit(var/atom/target, var/blocked = 0)
diff --git a/code/modules/projectiles/projectile/hook.dm b/code/modules/projectiles/projectile/subtypes/hook.dm
similarity index 95%
rename from code/modules/projectiles/projectile/hook.dm
rename to code/modules/projectiles/projectile/subtypes/hook.dm
index 797063ec8ab8..bcf0837c6888 100644
--- a/code/modules/projectiles/projectile/hook.dm
+++ b/code/modules/projectiles/projectile/subtypes/hook.dm
@@ -58,12 +58,11 @@
..() // Does the regular launching stuff.
-/obj/projectile/energy/hook/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null)
- if(..())
- perform_intent_unique(target)
-
-/obj/projectile/energy/hook/on_impact(var/atom/A)
- perform_intent_unique(get_turf(A))
+/obj/projectile/energy/hook/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ perform_intent_unique(target)
/obj/projectile/energy/hook/proc/ranged_disarm(var/mob/living/carbon/human/H)
if(istype(H))
@@ -116,8 +115,8 @@
else if(firer)
var/obj/T
- if(original in target.contents && istype(original, /obj))
- T = original
+ if(original_target.loc == target && istype(original_target, /obj))
+ T = original_target
var/list/possible_targets = list()
for(var/obj/item/I in target.contents)
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/subtypes/magic.dm
similarity index 94%
rename from code/modules/projectiles/projectile/magic.dm
rename to code/modules/projectiles/projectile/subtypes/magic.dm
index 507121f0248b..7a1ae4faa0dd 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/subtypes/magic.dm
@@ -36,8 +36,8 @@
/obj/item/ammo_casing/magic/honk
projectile_type = /obj/projectile/bullet/honker
-/obj/item/ammo_casing/magic/locker
- projectile_type = /obj/projectile/magic/locker
+// /obj/item/ammo_casing/magic/locker
+ // projectile_type = /obj/projectile/magic/locker
//Spell book ammo casing
/obj/item/ammo_casing/magic/book
diff --git a/code/modules/projectiles/projectile/magnetic.dm b/code/modules/projectiles/projectile/subtypes/magnetic.dm
similarity index 72%
rename from code/modules/projectiles/projectile/magnetic.dm
rename to code/modules/projectiles/projectile/subtypes/magnetic.dm
index 1705a9c1c65b..0133907721bf 100644
--- a/code/modules/projectiles/projectile/magnetic.dm
+++ b/code/modules/projectiles/projectile/subtypes/magnetic.dm
@@ -6,7 +6,7 @@
damage = 65
stun = 1
weaken = 1
- penetrating = 5
+ legacy_penetrating = 5
armor_penetration = 70
/obj/projectile/bullet/magnetic/slug
@@ -53,7 +53,7 @@
agony = 50
incendiary = 1
flammability = 0 //Deuterium and Tritium are both held in water, but the object moving so quickly will ignite the target.
- penetrating = 2
+ legacy_penetrating = 2
embed_chance = 0
armor_penetration = 40
range = WORLD_ICON_SIZE * 20
@@ -63,7 +63,13 @@
var/detonate_mob = 0 //Will this fuelrod explode when it hits a mob?
var/energetic_impact = 0 //Does this fuelrod cause a bright flash on impact with a mob?
-/obj/projectile/bullet/magnetic/fuelrod/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) //Future-proofing. Special effects for impact.
+/obj/projectile/bullet/magnetic/fuelrod/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(searing)
+ efficiency = max(efficiency, 1)
+ if(. & PROJECTILE_IMPACT_BLOCKED)
+ return
+
if(istype(target,/mob/living))
var/mob/living/V = target
if(detonate_mob)
@@ -81,28 +87,21 @@
M.afflict_stun(20 * 2)
M.afflict_paralyze(20 * 10)
- if(searing)
- if(blocked)
- blocked = 0
-
- return ..(target, blocked, def_zone)
-
-/obj/projectile/bullet/magnetic/fuelrod/on_impact(var/atom/A) //Future-proofing, again. In the event new fuel rods are introduced, and have special effects for when they stop flying.
+ // what the fuck??
if(src.loc)
if(detonate_travel && detonate_mob)
visible_message("\The [src] shatters in a violent explosion!")
- explosion(src.loc, 1, 1, 3, 4)
+ explosion(src.loc, 0, 1, 3, 4)
else if(detonate_travel)
visible_message("\The [src] explodes in a shower of embers!")
- explosion(src.loc, -1, 1, 2, 3)
- ..(A)
+ explosion(src.loc, 0, 1, 2, 3)
/obj/projectile/bullet/magnetic/fuelrod/tritium
icon_state = "fuel-tritium"
damage = 100 //Much harder to get than tritium - needs mhydrogen
flammability = -1
armor_penetration = 50
- penetrating = 3
+ legacy_penetrating = 3
/obj/projectile/bullet/magnetic/fuelrod/phoron
name = "blazing fuel rod"
@@ -111,7 +110,7 @@
incendiary = 2
flammability = 2
armor_penetration = 60
- penetrating = 5
+ legacy_penetrating = 5
irradiate = 20
detonate_mob = 1
@@ -123,7 +122,7 @@
flammability = 4
weaken = 2
armor_penetration = 100
- penetrating = 100 //Theoretically, this shouldn't stop flying for a while, unless someone lines it up with a wall or fires it into a mountain.
+ legacy_penetrating = 100 //Theoretically, this shouldn't stop flying for a while, unless someone lines it up with a wall or fires it into a mountain.
irradiate = 120
range = WORLD_ICON_SIZE * 75
searing = 1
@@ -131,14 +130,16 @@
detonate_mob = 1
energetic_impact = 1
-/obj/projectile/bullet/magnetic/fuelrod/supermatter/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) //You cannot touch the supermatter without disentigrating. Assumedly, this is true for condensed rods of it flying at relativistic speeds.
+/obj/projectile/bullet/magnetic/fuelrod/supermatter/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(istype(target,/turf/simulated/wall) || istype(target,/mob/living))
target.visible_message("The [src] burns a perfect hole through \the [target] with a blinding flash!")
playsound(target.loc, 'sound/effects/teleport.ogg', 40, 0)
- return ..(target, blocked, def_zone)
-/obj/projectile/bullet/magnetic/fuelrod/supermatter/check_penetrate()
- return 1
+/obj/projectile/bullet/magnetic/fuelrod/supermatter/pre_impact(atom/target, impact_flags, def_zone)
+ return ..() | PROJECTILE_IMPACT_PIERCE
/obj/projectile/bullet/magnetic/bore
name = "phorogenic blast"
@@ -146,24 +147,26 @@
damage = 20
incendiary = 1
armor_penetration = 20
- penetrating = 0
+ legacy_penetrating = 0
damage_flag = ARMOR_MELEE
irradiate = 20
range = WORLD_ICON_SIZE * 6
-/obj/projectile/bullet/magnetic/bore/Bump(atom/A, forced=0)
- if(istype(A, /turf/simulated/mineral))
- var/turf/simulated/mineral/MI = A
- loc = get_turf(A) // Careful.
- permutated.Add(A)
+/obj/projectile/bullet/magnetic/bore/pre_impact(atom/target, impact_flags, def_zone)
+ if(istype(target, /turf/simulated/mineral))
+ return PROJECTILE_IMPACT_PIERCE | impact_flags
+ return ..()
+
+/obj/projectile/bullet/magnetic/bore/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ if(istype(target, /turf/simulated/mineral))
+ var/turf/simulated/mineral/MI = target
MI.GetDrilled(TRUE)
- return 0
- else if(istype(A, /turf/simulated/wall) || istype(A, /turf/simulated/shuttle/wall)) // Cause a loud, but relatively minor explosion on the wall it hits.
- explosion(A, -1, -1, 1, 3)
- qdel(src)
- return 1
- else
- ..()
+ else if(istype(target, /turf/simulated/wall) || istype(target, /turf/simulated/shuttle/wall))
+ explosion(target, 0, 0, 1, 3)
+ . |= PROJECTILE_IMPACT_DELETE
/obj/projectile/bullet/magnetic/bore/powerful
name = "energetic phorogenic blast"
@@ -171,7 +174,7 @@
damage = 30
incendiary = 2
armor_penetration = 20
- penetrating = 0
+ legacy_penetrating = 0
damage_flag = ARMOR_MELEE
irradiate = 20
range = WORLD_ICON_SIZE * 12
diff --git a/code/modules/projectiles/projectile/subtypes/pellet.dm b/code/modules/projectiles/projectile/subtypes/pellet.dm
new file mode 100644
index 000000000000..cd9891e7f3cc
--- /dev/null
+++ b/code/modules/projectiles/projectile/subtypes/pellet.dm
@@ -0,0 +1,153 @@
+// For projectiles that actually represent clouds of projectiles
+// todo: message handling for this type is all over the place
+// todo: target still makes message of being hit even if they weren't
+// todo: just handle message in this file lmao
+/obj/projectile/bullet/pellet
+ name = "shrapnel" //'shrapnel' sounds more dangerous (i.e. cooler) than 'pellet'
+
+ damage = 20
+
+ /// number of pelelts
+ var/pellets = 4
+ /// distance before pellets start falling off
+ var/pellet_loss_start = WORLD_ICON_SIZE * 2
+ /// pellets lost per pixel moved
+ var/pellet_loss = 0.5 / WORLD_ICON_SIZE
+ /// last distance travelled
+ var/pellet_loss_last_distance = 0
+ /// base spread chance to not hit center mass / the target limb
+ ///
+ /// * in 0 to 100 prob() value
+ var/pellet_zone_spread = 10
+ /// min distance before spread
+ var/pellet_zone_spread_gain_threshold = 2 * WORLD_ICON_SIZE
+ /// spread chance per pixel to not hit center mass / target limb
+ ///
+ /// * in 0 to 100 prob() value
+ var/pellet_zone_spread_gain = 9 / WORLD_ICON_SIZE // complete spread after 2+10 tiles
+
+/obj/projectile/bullet/pellet/scan_moved_turf(turf/tile)
+ ..()
+ if(QDELETED(src))
+ return
+ for(var/mob/victim in tile)
+ if(victim.atom_flags & (ATOM_NONWORLD | ATOM_ABSTRACT))
+ continue
+ if(impacted[victim])
+ continue
+ impact(victim)
+
+/obj/projectile/bullet/pellet/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
+ . = ..()
+ var/travelled = distance_travelled - pellet_loss_last_distance
+ pellet_loss_last_distance = distance_travelled
+ if(pellet_loss_start > 0)
+ var/reduction = min(pellet_loss_start, travelled)
+ travelled -= reduction
+ pellet_loss_start -= reduction
+ if(travelled <= 0)
+ return
+ pellets -= pellet_loss * travelled
+
+/obj/projectile/bullet/pellet/process_accuracy(atom/target, target_opinion, distance, impact_check)
+ if(impact_check)
+ return 100
+ return ..()
+
+/obj/projectile/bullet/pellet/process_zone_miss(atom/target, target_opinion, distance, impact_check)
+ if(impact_check)
+ // if being hit, always direct to our def_zone
+ return def_zone
+ return ..()
+
+// todo: "you are hit by [number of] pellets in the chest" support.
+/obj/projectile/bullet/pellet/inflict_impact_damage(atom/target, efficiency, impact_flags, hit_zone)
+ /**
+ * What's going on here:
+ *
+ * It's too expensive to simulate too many bullet_act()'s for no reason, but pellets
+ * aren't meant to hit one body part or even do one instance to every body part hit.
+ *
+ * Thus, we force process_damage_instance to do your dirty work as it's cheaper to run
+ * lower level shieldcalls/armorcalls than to simulate high level bullet hits.
+ */
+ var/original_hit_zone = hit_zone
+ var/distance_penalty = distance_travelled > pellet_zone_spread_gain_threshold ? (distance_travelled * pellet_zone_spread_gain) : 0
+ var/zone_true_chance = 100 - (pellet_zone_spread + distance_penalty)
+ var/pellet_hit_chance = 100
+ . = NONE
+ if(isliving(target))
+ var/mob/living/living_target = target
+ pellet_hit_chance = living_target.process_baymiss(src, impact_check = FALSE)
+ if(!target.density && (target != original_target))
+ pellet_hit_chance *= 0.2
+ var/hit_pellets = 0
+ for(var/i in 1 to ceil(pellets))
+ if(!prob(pellet_hit_chance))
+ continue
+ // args is just a list ref, so we can do this (directly modify args)
+ hit_zone = ran_zone(def_zone, zone_true_chance)
+ . |= ..()
+ ++hit_pellets
+ pellets -= hit_pellets
+ hit_zone = original_hit_zone
+ if(pellets <= 0)
+ . |= PROJECTILE_IMPACT_DELETE
+ else
+ . |= PROJECTILE_IMPACT_PIERCE
+
+// todo: this is only needed because process_damage_instance isn't used for structured right now!
+/obj/projectile/bullet/pellet/get_structure_damage()
+ return ..() * pellets
+
+//Explosive grenade projectile, borrowed from fragmentation grenade code.
+/obj/projectile/bullet/pellet/fragment
+ damage = 10
+ armor_penetration = 30
+
+ silenced = 1 //embedding messages are still produced so it's kind of weird when enabled.
+ muzzle_type = null
+
+/obj/projectile/bullet/pellet/fragment/strong
+ damage = 15
+ armor_penetration = 20
+
+/obj/projectile/bullet/pellet/fragment/weak
+ damage = 4
+ armor_penetration = 40
+
+/obj/projectile/bullet/pellet/fragment/rubber
+ name = "stingball shrapnel"
+ damage = 3
+ agony = 8
+ sharp = FALSE
+ edge = FALSE
+ damage_flag = ARMOR_MELEE
+
+/obj/projectile/bullet/pellet/fragment/rubber/strong
+ damage = 8
+ agony = 16
+
+// Tank rupture fragments
+/obj/projectile/bullet/pellet/fragment/tank
+ name = "metal fragment"
+ damage = 9 //Big chunks flying off.
+
+ armor_penetration = 20
+
+ silenced = 1
+ muzzle_type = null
+ pellets = 3
+
+/obj/projectile/bullet/pellet/fragment/tank/small
+ name = "small metal fragment"
+ damage = 6
+ armor_penetration = 5
+ pellets = 5
+
+/obj/projectile/bullet/pellet/fragment/tank/big
+ name = "large metal fragment"
+ damage = 17
+ armor_penetration = 10
+ pellet_loss = 0.2 / WORLD_ICON_SIZE
+ pellets = 1
diff --git a/code/modules/projectiles/projectile/reusable.dm b/code/modules/projectiles/projectile/subtypes/reusable.dm
similarity index 73%
rename from code/modules/projectiles/projectile/reusable.dm
rename to code/modules/projectiles/projectile/subtypes/reusable.dm
index a2f5fabbb96d..4c2f5904ff82 100644
--- a/code/modules/projectiles/projectile/reusable.dm
+++ b/code/modules/projectiles/projectile/subtypes/reusable.dm
@@ -4,54 +4,19 @@
name = "reusable bullet"
desc = "How do you even reuse a bullet?"
var/ammo_type = /obj/item/ammo_casing/arrow
- var/dropped = FALSE
//var/fragile = FALSE
//var/durable = FALSE
//var/shattered = 0
//var/broken_type = null
-/obj/projectile/bullet/reusable/on_hit(atom/target, blocked = FALSE)
- . = ..()
- handle_drop()
- //handle_shatter()
-
-/obj/projectile/bullet/reusable/legacy_on_range()
+/obj/projectile/bullet/reusable/expire(impacting)
handle_drop()
- ..()
+ return ..()
/obj/projectile/bullet/reusable/proc/handle_drop()
- if(!dropped)
- var/turf/T = get_turf(src)
- new ammo_type(T)
- dropped = TRUE
-/*
- else
- var/turf/T = get_turf(src)
- new broken_type(T)
- dropped = TRUE
-
-/obj/projectile/bullet/reusable/proc/handle_shatter()
- if(fragile)
- switch(rand(1,100))
- if(1 to 50)
- src.shattered = 1
- if(31 to 100)
- return
- if(durable)
- switch(rand(1,100))
- if(1 to 5)
- src.shattered = 1
- if(6 to 100)
- return
- else
- switch(rand(1,100))
- if(1 to 25)
- src.shattered = 1
- if(16 to 100)
- return
- return
-*/
+ var/turf/T = get_turf(src)
+ new ammo_type(T)
//Arrows
/obj/projectile/bullet/reusable/arrow
@@ -97,14 +62,20 @@
icon_state = "plunger"
ammo_type = /obj/item/ammo_casing/arrow/plunger
-/obj/projectile/bullet/reusable/plunger/on_hit(atom/hit_atom)
+/obj/projectile/bullet/reusable/plunger/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
- var/mob/living/carbon/H = hit_atom
+ // use target abort as this is a target effect.
+ if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT)
+ return
+ var/mob/living/carbon/H = target
+ if(!istype(H))
+ return
var/obj/item/plunger/P
if(!H.wear_mask)
H.equip_to_slot_if_possible(P, SLOT_MASK)
else
handle_drop()
+ return . | PROJECTILE_IMPACT_DELETE
//Foam Darts
/obj/projectile/bullet/reusable/foam
diff --git a/code/modules/projectiles/projectile/scatter.dm b/code/modules/projectiles/projectile/subtypes/scatter.dm
similarity index 100%
rename from code/modules/projectiles/projectile/scatter.dm
rename to code/modules/projectiles/projectile/subtypes/scatter.dm
diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/subtypes/special.dm
similarity index 80%
rename from code/modules/projectiles/projectile/special.dm
rename to code/modules/projectiles/projectile/subtypes/special.dm
index deadb73495d2..1846df5ce77f 100644
--- a/code/modules/projectiles/projectile/special.dm
+++ b/code/modules/projectiles/projectile/subtypes/special.dm
@@ -17,9 +17,12 @@
var/sev3_range = 1
var/sev4_range = 1
-/obj/projectile/ion/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/ion/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
empulse(target, sev1_range, sev2_range, sev3_range, sev4_range)
- return 1
+ return . | PROJECTILE_IMPACT_DELETE
/obj/projectile/ion/small
sev1_range = -1
@@ -41,9 +44,12 @@
sharp = 1
edge = 1
-/obj/projectile/bullet/gyro/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/bullet/gyro/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
explosion(target, -1, 0, 2)
- ..()
+ return . | PROJECTILE_IMPACT_DELETE
/obj/projectile/temp
name = "freeze beam"
@@ -61,8 +67,10 @@
combustion = FALSE
-/obj/projectile/temp/on_hit(atom/target, blocked = FALSE)
- ..()
+/obj/projectile/temp/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isliving(target))
var/mob/living/L = target
@@ -84,8 +92,6 @@
new_temperature = round(new_temperature * temp_factor)
L.bodytemperature = new_temperature
- return 1
-
/obj/projectile/temp/hot
name = "heat beam"
target_temperature = 1000
@@ -133,7 +139,11 @@
combustion = FALSE
-/obj/projectile/energy/floramut/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/energy/floramut/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
var/mob/living/M = target
if(ishuman(target))
var/mob/living/carbon/human/H = M
@@ -163,8 +173,6 @@
// for (var/mob/V in viewers(src))
// V.show_message("The radiation beam dissipates harmlessly through [M]", 3)
M.show_message("The radiation beam dissipates harmlessly through your body.")
- else
- return 1
/obj/projectile/energy/floramut/gene
name = "gamma somatoray"
@@ -188,7 +196,10 @@
light_power = 0.5
light_color = "#FFFFFF"
-/obj/projectile/energy/florayield/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/energy/florayield/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
var/mob/M = target
if(ishuman(target)) //These rays make plantmen fat.
var/mob/living/carbon/human/H = M
@@ -196,16 +207,15 @@
M.nutrition += 30
else if (istype(target, /mob/living/carbon/))
M.show_message("The radiation beam dissipates harmlessly through your body.")
- else
- return 1
-
/obj/projectile/beam/mindflayer
name = "flayer ray"
-
combustion = FALSE
-/obj/projectile/beam/mindflayer/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/beam/mindflayer/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ishuman(target))
var/mob/living/carbon/human/M = target
M.Confuse(rand(5,8))
@@ -229,14 +239,16 @@
combustion = FALSE
-/obj/projectile/bola/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/bola/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(ishuman(target))
var/mob/living/carbon/human/M = target
var/obj/item/handcuffs/legcuffs/bola/B = new(src.loc)
if(!B.place_legcuffs(M,firer))
- if(B)
- qdel(B)
- ..()
+ qdel(B)
/obj/projectile/webball
name = "ball of web"
@@ -248,13 +260,16 @@
combustion = FALSE
-/obj/projectile/webball/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/webball/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(isturf(target.loc))
var/obj/effect/spider/stickyweb/W = locate() in get_turf(target)
if(!W && prob(75))
visible_message("\The [src] splatters a layer of web on \the [target]!")
new /obj/effect/spider/stickyweb(target.loc)
- ..()
/obj/projectile/beam/tungsten
name = "core of molten tungsten"
@@ -272,7 +287,10 @@
tracer_type = /obj/effect/projectile/tungsten/tracer
impact_type = /obj/effect/projectile/tungsten/impact
-/obj/projectile/beam/tungsten/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/beam/tungsten/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_BLOCKED)
+ return
if(isliving(target))
var/mob/living/L = target
L.add_modifier(/datum/modifier/grievous_wounds, 30 SECONDS)
@@ -316,17 +334,13 @@
else if(armor_special)
target.visible_message("\The [src] slams into \the [target]'s [target_limb] with a low rumble!")
- ..()
-
-/obj/projectile/beam/tungsten/on_impact(var/atom/A)
- if(istype(A,/turf/simulated/shuttle/wall) || istype(A,/turf/simulated/wall) || (istype(A,/turf/simulated/mineral) && A.density) || istype(A,/obj/vehicle/sealed/mecha) || istype(A,/obj/machinery/door))
+ if(istype(target,/turf/simulated/shuttle/wall) || istype(target,/turf/simulated/wall) || (istype(target,/turf/simulated/mineral) && target.density) || istype(target,/obj/vehicle/sealed/mecha) || istype(target,/obj/machinery/door))
var/blast_dir = src.dir
- A.visible_message("\The [A] begins to glow!")
+ target.visible_message("\The [target] begins to glow!")
spawn(2 SECONDS)
- var/blastloc = get_step(A, blast_dir)
+ var/blastloc = get_step(target, blast_dir)
if(blastloc)
explosion(blastloc, -1, -1, 2, 3)
- ..()
/obj/projectile/bullet/honker
damage = 0
@@ -384,19 +398,18 @@
light_range = 4
light_power = 3
light_color = "#00ccff"
+ var/heavy = FALSE
-/obj/projectile/plasma/on_hit(var/atom/target, var/blocked = 0)
- explosion(target, -1, 0, 1, 2)
- ..()
+/obj/projectile/plasma/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
-/obj/projectile/plasma/on_impact(var/atom/A)
- if(istype(A,/turf/simulated/shuttle/wall) || istype(A,/turf/simulated/wall) || (istype(A,/turf/simulated/mineral) && A.density) || istype(A,/obj/vehicle/sealed/mecha) || istype(A,/obj/machinery/door))
- var/blast_dir = src.dir
- A.visible_message("\The [A] is engulfed in roiling plasma!")
- var/blastloc = get_step(A, blast_dir)
- if(blastloc)
- explosion(blastloc, -1, 0, 1, 2)
- ..()
+ var/blast_dir = src.dir
+ target.visible_message("\The [target] is engulfed in roiling plasma!")
+ var/blastloc = get_step(target, blast_dir)
+ if(blastloc)
+ explosion(blastloc, -1, 0, heavy? 2 : 1, heavy? 3 : 2)
/obj/projectile/plasma/hot
name ="heavy plasma bolt"
@@ -404,16 +417,4 @@
light_range = 5
light_power = 4
light_color = "#00ccff"
-
-/obj/projectile/plasma/hot/on_hit(var/atom/target, var/blocked = 0)
- explosion(target, -1, 0, 2, 3)
- ..()
-
-/obj/projectile/plasma/hot/on_impact(var/atom/A)
- if(istype(A,/turf/simulated/shuttle/wall) || istype(A,/turf/simulated/wall) || (istype(A,/turf/simulated/mineral) && A.density) || istype(A,/obj/vehicle/sealed/mecha) || istype(A,/obj/machinery/door))
- var/blast_dir = src.dir
- A.visible_message("\The [A] is engulfed in roiling plasma!")
- var/blastloc = get_step(A, blast_dir)
- if(blastloc)
- explosion(blastloc, -1, 0, 2, 3)
- ..()
+ heavy = TRUE
diff --git a/code/modules/projectiles/projectile/trace.dm b/code/modules/projectiles/projectile/subtypes/trace.dm
similarity index 94%
rename from code/modules/projectiles/projectile/trace.dm
rename to code/modules/projectiles/projectile/subtypes/trace.dm
index a01e1574e40b..9c825bc3f757 100644
--- a/code/modules/projectiles/projectile/trace.dm
+++ b/code/modules/projectiles/projectile/subtypes/trace.dm
@@ -20,6 +20,7 @@
nodamage = TRUE
damage = 0
has_tracer = FALSE
+ projectile_type = PROJECTILE_TYPE_TRACE
var/list/hit = list()
/obj/projectile/test/fire(angle, atom/direct_target)
@@ -30,6 +31,3 @@
if(A != src)
hit |= A
return ..()
-
-/obj/projectile/test/projectile_attack_mob()
- return
diff --git a/code/modules/projectiles/unsorted/magic.dm b/code/modules/projectiles/unsorted/magic.dm
index aa8735a1f020..690cd6a04e24 100644
--- a/code/modules/projectiles/unsorted/magic.dm
+++ b/code/modules/projectiles/unsorted/magic.dm
@@ -12,14 +12,17 @@
name = "bolt of death"
icon_state = "pulse1_bl"
-/obj/projectile/magic/death/on_hit(target, var/mob/living/L)
+/obj/projectile/magic/death/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
- if(ismob(target))
- var/mob/M = target
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ if(isliving(target))
+ var/mob/living/L = target
if(L.anti_magic_check())
- M.visible_message("[src] vanishes on contact with [target]!")
- return blocked
- M.death(0)
+ L.visible_message("[src] vanishes on contact with [target]!")
+ . |= PROJECTILE_IMPACT_DELETE
+ return
+ L.death(0)
/obj/projectile/magic/resurrection
name = "bolt of resurrection"
@@ -28,17 +31,21 @@
damage_type = OXY
nodamage = 1
-/obj/projectile/magic/resurrection/on_hit(mob/living/carbon/target)
+/obj/projectile/magic/resurrection/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(isliving(target))
- if(target.anti_magic_check())
- target.visible_message("[src] vanishes on contact with [target]!")
- return blocked
- if(target.revive(full_heal = TRUE))
- to_chat(target, "You rise with a start, you're alive!!!")
- else if(target.stat != DEAD)
- to_chat(target, "You feel great!")
- target.rejuvenate(fix_missing = TRUE)
+ var/mob/living/L = target
+ if(L.anti_magic_check())
+ L.visible_message("[src] vanishes on contact with [L]!")
+ . |= PROJECTILE_IMPACT_DELETE
+ return
+ if(L.revive(full_heal = TRUE))
+ to_chat(L, "You rise with a start, you're alive!!!")
+ else if(L.stat != DEAD)
+ to_chat(L, "You feel great!")
+ L.rejuvenate(fix_missing = TRUE)
/obj/projectile/magic/teleport
name = "bolt of teleportation"
@@ -49,13 +56,16 @@
var/inner_tele_radius = 0
var/outer_tele_radius = 6
-/obj/projectile/magic/teleport/on_hit(mob/target, var/mob/living/L)
+/obj/projectile/magic/teleport/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ismob(target))
- var/mob/M = target
+ var/mob/L = target
if(L.anti_magic_check())
- M.visible_message("[src] fizzles on contact with [target]!")
- return blocked
+ L.visible_message("[src] fizzles on contact with [target]!")
+ . |= PROJECTILE_IMPACT_DELETE
+ return
var/teleammount = 0
var/teleloc = target
if(!isturf(target))
@@ -76,8 +86,10 @@
nodamage = 1
var/list/door_types = list(/obj/structure/simple_door/wood, /obj/structure/simple_door/iron, /obj/structure/simple_door/silver, /obj/structure/simple_door/gold, /obj/structure/simple_door/uranium, /obj/structure/simple_door/sandstone, /obj/structure/simple_door/phoron, /obj/structure/simple_door/diamond)
-/obj/projectile/magic/door/on_hit(atom/target)
+/obj/projectile/magic/door/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(istype(target, /obj/machinery/door))
OpenDoor(target)
else
@@ -291,6 +303,7 @@
if(owner)
C.ChangeOwner(owner)
*/
+
/obj/projectile/magic/spellblade
name = "blade energy"
icon_state = "lavastaff"
@@ -299,14 +312,17 @@
sharp = TRUE
magic = TRUE
-/obj/projectile/magic/spellblade/on_hit(target, var/mob/living/L)
+/obj/projectile/magic/spellblade/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(ismob(target))
- var/mob/M = target
+ var/mob/L = target
if(L.anti_magic_check())
- M.visible_message("[src] vanishes on contact with [target]!")
+ L.visible_message("[src] vanishes on contact with [target]!")
qdel(src)
return
- . = ..()
/obj/projectile/magic/arcane_barrage
name = "arcane bolt"
@@ -318,16 +334,19 @@
magic = TRUE
impact_sounds = 'sound/weapons/barragespellhit.ogg'
-/obj/projectile/magic/arcane_barrage/on_hit(target, var/mob/living/L)
+/obj/projectile/magic/arcane_barrage/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(ismob(target))
- var/mob/M = target
+ var/mob/L = target
if(L.anti_magic_check())
- M.visible_message("[src] vanishes on contact with [target]!")
+ L.visible_message("[src] vanishes on contact with [target]!")
qdel(src)
return
- . = ..()
-
+/* needs more work
/obj/projectile/magic/locker
name = "locker bolt"
icon_state = "locker"
@@ -352,21 +371,24 @@
return ..()
*/
-/obj/projectile/magic/locker/on_hit(target)
- if(created)
- return ..()
- var/obj/structure/closet/decay/C = new(get_turf(src))
- if(LAZYLEN(contents))
- for(var/atom/movable/AM in contents)
+/obj/projectile/magic/locker/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+ if(!created)
+ var/obj/structure/closet/decay/C = new(src)
C.update_icon()
- created = TRUE
- return ..()
+ if(locker_suck && !(target.atom_flags & (ATOM_ABSTRACT | ATOM_NONWORLD)))
+ if(ismovable(target))
+ var/atom/movable/AM = target
+ AM.forceMove(src)
/obj/projectile/magic/locker/Destroy()
locker_suck = FALSE
for(var/atom/movable/AM in contents)
AM.forceMove(get_turf(src))
. = ..()
+*/
/obj/structure/closet/decay
breakout_time = 600
@@ -428,16 +450,17 @@
chain = caster.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY)
..()
-/obj/projectile/magic/aoe/lightning/on_hit(target, var/mob/living/L)
+/obj/projectile/magic/aoe/lightning/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ismob(target))
- var/mob/M = target
+ var/mob/L = target
if(L.anti_magic_check())
- M.visible_message("[src] fizzles on contact with [target]!")
- qdel(src)
- return blocked
+ L.visible_message("[src] fizzles on contact with [target]!")
+ return . | PROJECTILE_IMPACT_DELETE
tesla_zap(src, zap_range, zap_power)
- qdel(src)
+ return . | PROJECTILE_IMPACT_DELETE
/obj/projectile/magic/aoe/lightning/Destroy()
qdel(chain)
@@ -456,17 +479,21 @@
var/exp_flash = 3
var/exp_fire = 2
-/obj/projectile/magic/aoe/fireball/on_hit(target)
+/obj/projectile/magic/aoe/fireball/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(ismob(target))
var/mob/living/M = target
if(M.anti_magic_check())
visible_message("[src] vanishes into smoke on contact with [target]!")
- return
+ return . | PROJECTILE_IMPACT_DELETE
M.take_overall_damage(0,10) //between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, your at about 65 damage if you stop drop and roll immediately
var/turf/T = get_turf(target)
explosion(T, -1, exp_heavy, exp_light, exp_flash, 0)//, flame_range = exp_fire)
+ return . | PROJECTILE_IMPACT_DELETE
+/* requires IMPACT_DELETE_AFTER
/obj/projectile/magic/aoe/fireball/infernal
name = "infernal fireball"
exp_heavy = -1
@@ -474,15 +501,14 @@
exp_flash = 4
exp_fire= 5
-/obj/projectile/magic/aoe/fireball/infernal/on_hit(target)
+/obj/projectile/magic/aoe/fireball/infernal/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
- if(ismob(target))
- var/mob/living/M = target
- if(M.anti_magic_check())
- return
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
var/turf/T = get_turf(target)
for(var/i=0, i<50, i+=10)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i)
+*/
/obj/projectile/magic/nuclear
name = "\proper blazing manliness"
@@ -491,7 +517,10 @@
var/mob/living/victim = null
var/used = 0
-/obj/projectile/magic/nuclear/on_hit(target)
+/obj/projectile/magic/nuclear/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(used)
return
if(ismob(target))
@@ -500,16 +529,12 @@
used = 1
visible_message("[victim] slams into [target] with explosive force!")
explosion(src, 2, 3, 4, -1, TRUE, FALSE, 5)
+ return PROJECTILE_IMPACT_DELETE
else
used = 1
victim.take_overall_damage(30,30)
explosion(src, -1, -1, -1, -1, FALSE, FALSE, 5)
- return
-
-/obj/projectile/magic/nuclear/Destroy()
- for(var/atom/movable/AM in contents)
- AM.forceMove(get_turf(src))
- . = ..()
+ return PROJECTILE_IMPACT_DELETE
//Spellcards
@@ -533,8 +558,10 @@
damage = 4
var/fire_stacks = 4
-/obj/projectile/magic/spellcard/book/spark/on_hit(atom/target, blocked = FALSE)
+/obj/projectile/magic/spellcard/book/spark/on_impact(atom/target, impact_flags, def_zone, efficiency)
. = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
var/mob/living/carbon/M = target
if(ismob(target))
if(M.anti_magic_check())
diff --git a/code/modules/random_map/drop/drop_types.dm b/code/modules/random_map/drop/drop_types.dm
index 9bd0d665fd50..c39f4ae18cc5 100644
--- a/code/modules/random_map/drop/drop_types.dm
+++ b/code/modules/random_map/drop/drop_types.dm
@@ -37,7 +37,7 @@ var/global/list/datum/supply_drop_loot/supply_drop
/obj/item/storage/belt/security/tactical/bandolier,
/obj/item/clothing/accessory/storage/black_drop_pouches,
/obj/item/storage/backpack/dufflebag/sec,
- /obj/item/shield/energy,
+ /obj/item/shield/transforming/energy,
/obj/item/gun/energy/ionrifle,
/obj/item/gun/energy/xray,
/obj/item/storage/box/emps,
@@ -58,7 +58,7 @@ var/global/list/datum/supply_drop_loot/supply_drop
/obj/item/storage/belt/security/tactical/bandolier,
/obj/item/clothing/accessory/storage/black_drop_pouches,
/obj/item/storage/backpack/dufflebag/sec,
- /obj/item/shield/riot/tele,
+ /obj/item/shield/transforming/telescopic,
/obj/item/storage/box/emps,
/obj/item/storage/box/flashbangs,
/obj/item/gun/ballistic/automatic/sts35,
@@ -85,7 +85,7 @@ var/global/list/datum/supply_drop_loot/supply_drop
/obj/item/gun/ballistic/automatic/bullpup,
/obj/item/ammo_magazine/a7_62mm/ap,
/obj/item/ammo_magazine/a7_62mm,
- /obj/item/shield/energy,
+ /obj/item/shield/transforming/energy,
/obj/item/grenade/explosive/frag,
/obj/item/grenade/explosive/frag,
/obj/item/grenade/smokebomb,
@@ -107,7 +107,7 @@ var/global/list/datum/supply_drop_loot/supply_drop
/obj/item/clothing/suit/armor/riot,
/obj/item/clothing/gloves/arm_guard/riot,
/obj/item/clothing/shoes/leg_guard/riot,
- /obj/item/shield/riot/tele,
+ /obj/item/shield/transforming/telescopic,
/obj/item/storage/box/flashbangs,
/obj/item/storage/box/handcuffs,
/obj/item/melee/baton,
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 9855c529c98f..70a9ac9731a8 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -402,7 +402,7 @@
var/mob/living/L = target
if(ishuman(L))
var/mob/living/carbon/human/H = L
- if(H.check_shields(0, null, null, null, "the spray") == 1) //If they block the spray, it does nothing.
+ if(H.atom_shieldcall_handle_touch(null, SHIELDCALL_CONTACT_FLAG_NEUTRAL, SHIELDCALL_CONTACT_SPECIFIC_CHEMICAL_SPRAY) & SHIELDCALL_FLAGS_BLOCK_ATTACK)
amount = 0
perm = L.reagent_permeability()
return trans_to_mob(target, amount, CHEM_TOUCH, perm, copy)
diff --git a/code/modules/reagents/machinery/reagent_dispenser/fuel.dm b/code/modules/reagents/machinery/reagent_dispenser/fuel.dm
index f02c4724a39c..e7e9faab4139 100644
--- a/code/modules/reagents/machinery/reagent_dispenser/fuel.dm
+++ b/code/modules/reagents/machinery/reagent_dispenser/fuel.dm
@@ -84,13 +84,14 @@
return ..()
-/obj/structure/reagent_dispensers/fueltank/bullet_act(var/obj/projectile/Proj)
- if(Proj.get_structure_damage())
- if(istype(Proj.firer))
- message_admins("[key_name_admin(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) (JMP).")
- log_game("[key_name(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]).")
+/obj/structure/reagent_dispensers/fueltank/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(proj.get_structure_damage())
+ if(istype(proj.firer))
+ message_admins("[key_name_admin(proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) (JMP).")
+ log_game("[key_name(proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]).")
- if(!istype(Proj ,/obj/projectile/beam/lasertag) && !istype(Proj ,/obj/projectile/beam/practice) )
+ if(!istype(proj ,/obj/projectile/beam/lasertag) && !istype(proj ,/obj/projectile/beam/practice) )
explode()
/obj/structure/reagent_dispensers/fueltank/legacy_ex_act()
diff --git a/code/modules/reagents/machinery/reagent_dispenser/oil.dm b/code/modules/reagents/machinery/reagent_dispenser/oil.dm
index 0ee657bc919d..e28b3074f4e1 100644
--- a/code/modules/reagents/machinery/reagent_dispenser/oil.dm
+++ b/code/modules/reagents/machinery/reagent_dispenser/oil.dm
@@ -21,8 +21,9 @@
)
starting_capacity = 5000
-/obj/structure/reagent_dispensers/cookingoil/bullet_act(var/obj/projectile/Proj)
- if(Proj.get_structure_damage())
+/obj/structure/reagent_dispensers/cookingoil/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(proj.get_structure_damage())
explode()
/obj/structure/reagent_dispensers/cookingoil/legacy_ex_act()
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 575fcd642760..586eea6f7c45 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -246,8 +246,10 @@
var/hit_area = affecting.name
- if((user != target) && H.check_shields(7, src, user, "\the [src]"))
- return
+ if(user != target)
+ var/list/shieldcall_results = target.run_mob_defense(7, attack_type = ATTACK_TYPE_MELEE, weapon = src, hit_zone = hit_area, clickchain = new /datum/event_args/actor/clickchain(user))
+ if(shieldcall_results[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_ATTACK_BLOCKED)
+ return
if (target != user && H.legacy_mob_armor(target_zone, "melee") > 5 && prob(50))
for(var/mob/O in viewers(world.view, user))
diff --git a/code/modules/research/designs/weapons.dm b/code/modules/research/designs/weapons.dm
index 350c6ea1ae39..e966586ce3a4 100644
--- a/code/modules/research/designs/weapons.dm
+++ b/code/modules/research/designs/weapons.dm
@@ -331,14 +331,14 @@
id = "chargesword"
req_tech = list(TECH_COMBAT = 6, TECH_MAGNET = 4, TECH_ENGINEERING = 5, TECH_ILLEGAL = 4, TECH_ARCANE = 1)
materials_base = list(MAT_PLASTEEL = 3500, MAT_GLASS = 1000, MAT_LEAD = 2250, MAT_METALHYDROGEN = 500)
- build_path = /obj/item/melee/energy/sword/charge
+ build_path = /obj/item/melee/transforming/energy/sword/charge
/datum/design/science/weapon/melee/eaxe
design_name = "Energy Axe"
id = "chargeaxe"
req_tech = list(TECH_COMBAT = 6, TECH_MAGNET = 5, TECH_ENGINEERING = 4, TECH_ILLEGAL = 4)
materials_base = list(MAT_PLASTEEL = 3500, MAT_OSMIUM = 2000, MAT_LEAD = 2000, MAT_METALHYDROGEN = 500)
- build_path = /obj/item/melee/energy/axe/charge
+ build_path = /obj/item/melee/transforming/energy/axe/charge
/datum/design/science/weapon/grenade
abstract_type = /datum/design/science/weapon/grenade
diff --git a/code/modules/resleeving/mirror.dm b/code/modules/resleeving/mirror.dm
index b68cb6791991..92bebfefa35a 100644
--- a/code/modules/resleeving/mirror.dm
+++ b/code/modules/resleeving/mirror.dm
@@ -81,6 +81,10 @@
forceMove(MT)
MT.imp = src
+/obj/item/implant/mirror/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from)
+ . = ..()
+ target.mirror = null
+
/obj/item/implant/mirror/positronic
name = "Synthetic Mirror"
desc = "An altered form of the common mirror designed to work with synthetic brains."
diff --git a/code/modules/shieldgen/energy_field.dm b/code/modules/shieldgen/energy_field.dm
index 8d195c0f97f2..666014f6393f 100644
--- a/code/modules/shieldgen/energy_field.dm
+++ b/code/modules/shieldgen/energy_field.dm
@@ -58,7 +58,7 @@
user.do_attack_animation(src)
user.setClickCooldown(user.get_attack_speed())
-/obj/effect/energy_field/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual)
+/obj/effect/energy_field/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon)
adjust_strength(damage / 20)
return damage
diff --git a/code/modules/shieldgen/energy_shield.dm b/code/modules/shieldgen/energy_shield.dm
index 4f3d459109f1..434a484ad33d 100644
--- a/code/modules/shieldgen/energy_shield.dm
+++ b/code/modules/shieldgen/energy_shield.dm
@@ -231,15 +231,15 @@
if(!disabled_for)
take_damage_legacy(rand(10,15) / severity, SHIELD_DAMTYPE_PHYSICAL)
-
// Fire
/obj/effect/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(!disabled_for)
take_damage_legacy(rand(5,10), SHIELD_DAMTYPE_HEAT)
-
// Projectiles
-/obj/effect/shield/bullet_act(var/obj/projectile/proj)
+/obj/effect/shield/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ impact_flags &= ~PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT
+ . = ..()
if(proj.damage_type == BURN)
take_damage_legacy(proj.get_structure_damage(), SHIELD_DAMTYPE_HEAT)
else if (proj.damage_type == BRUTE)
@@ -247,7 +247,6 @@
else //TODO - This will never happen because of get_structure_damage() only returning values for BRUTE and BURN damage types
take_damage_legacy(proj.get_structure_damage(), SHIELD_DAMTYPE_EM)
-
// Attacks with hand tools. Blocked by Hyperkinetic flag.
/obj/effect/shield/attackby(var/obj/item/I as obj, var/mob/user as mob)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
diff --git a/code/modules/shieldgen/sheldwallgen.dm b/code/modules/shieldgen/sheldwallgen.dm
index d5cbc172a198..7220b26436b0 100644
--- a/code/modules/shieldgen/sheldwallgen.dm
+++ b/code/modules/shieldgen/sheldwallgen.dm
@@ -213,11 +213,9 @@
src.cleanup(8)
..()
-/obj/machinery/shieldwallgen/bullet_act(var/obj/projectile/Proj)
- storedpower -= 400 * Proj.get_structure_damage()
- ..()
- return
-
+/obj/machinery/shieldwallgen/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ storedpower -= 400 * proj.get_structure_damage()
//////////////Containment Field START
/obj/machinery/shieldwall
@@ -277,18 +275,15 @@
else
gen_secondary.storedpower -= power_usage
-
-/obj/machinery/shieldwall/bullet_act(var/obj/projectile/Proj)
+/obj/machinery/shieldwall/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
if(needs_power)
var/obj/machinery/shieldwallgen/G
if(prob(50))
G = gen_primary
else
G = gen_secondary
- G.storedpower -= 400 * Proj.get_structure_damage()
- ..()
- return
-
+ G.storedpower -= 400 * proj.get_structure_damage()
/obj/machinery/shieldwall/legacy_ex_act(severity)
if(needs_power)
diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm
index 9459cc25fba8..9a639edbff8b 100644
--- a/code/modules/shuttles/shuttle_console.dm
+++ b/code/modules/shuttles/shuttle_console.dm
@@ -136,16 +136,6 @@
to_chat(user, "You short out the console's ID checking system. It's now available to everyone!")
return 1
-/obj/machinery/computer/shuttle_control/bullet_act(var/obj/projectile/Proj)
- visible_message("\The [Proj] ricochets off \the [src]!")
-
-/obj/machinery/computer/shuttle_control/legacy_ex_act()
- return
-
-/obj/machinery/computer/shuttle_control/emp_act()
- return
-
-
GLOBAL_LIST_BOILERPLATE(papers_dockingcode, /obj/item/paper/dockingcodes)
/hook/roundstart/proc/populate_dockingcodes()
for(var/paper in GLOB.papers_dockingcode)
diff --git a/code/modules/species/promethean/promethean_blob.dm b/code/modules/species/promethean/promethean_blob.dm
index 510e07807926..835fe946bced 100644
--- a/code/modules/species/promethean/promethean_blob.dm
+++ b/code/modules/species/promethean/promethean_blob.dm
@@ -195,11 +195,10 @@
set_light(max(1,min(5,rad_glow/15)), max(1,min(10,rad_glow/25)), color)
update_icon()
-/mob/living/simple_mob/slime/promethean/bullet_act(obj/projectile/P)
+/mob/living/simple_mob/slime/promethean/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(humanform)
- return humanform.bullet_act(P)
- else
- return ..()
+ return proj.impact_redirect(humanform, args)
+ return ..()
/mob/living/simple_mob/slime/promethean/death(gibbed, deathmessage = "rapidly loses cohesion, splattering across the ground...")
if(humanform)
diff --git a/code/modules/species/xenomorphs/alien_facehugger.dm b/code/modules/species/xenomorphs/alien_facehugger.dm
index 6d3c82a512c3..159d148bed6f 100644
--- a/code/modules/species/xenomorphs/alien_facehugger.dm
+++ b/code/modules/species/xenomorphs/alien_facehugger.dm
@@ -66,9 +66,9 @@ var/const/MAX_ACTIVE_TIME = 400
Die()
return
-/obj/item/clothing/mask/facehugger/bullet_act()
+/obj/item/clothing/mask/facehugger/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
Die()
- return
/obj/item/clothing/mask/facehugger/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(exposed_temperature > T0C+80)
@@ -351,7 +351,7 @@ var/const/MAX_ACTIVE_TIME = 400
Die()
return
-/mob/living/simple_mob/animal/space/alien/facehugger/bullet_act()
+/mob/living/simple_mob/animal/space/alien/facehugger/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
Die()
return
diff --git a/code/modules/spells/spell_projectile.dm b/code/modules/spells/spell_projectile.dm
index 60538b4ba7b1..8b3e0be5ea11 100644
--- a/code/modules/spells/spell_projectile.dm
+++ b/code/modules/spells/spell_projectile.dm
@@ -6,7 +6,6 @@
var/spell/targeted/projectile/carried
- penetrating = 0
range = WORLD_ICON_SIZE * 10 //set by the duration of the spell
var/proj_trail = 0 //if it leaves a trail
@@ -47,10 +46,12 @@
prox_cast(carried.choose_prox_targets(user = carried.holder, spell_holder = src))
return 1
-/obj/projectile/spell_projectile/on_impact()
+/obj/projectile/spell_projectile/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
if(loc && carried)
prox_cast(carried.choose_prox_targets(user = carried.holder, spell_holder = src))
- return 1
/obj/projectile/spell_projectile/seeking
name = "seeking spell"
diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm
index 078cc2ac2ae4..d1cb091cd77a 100644
--- a/code/modules/spells/targeted/ethereal_jaunt.dm
+++ b/code/modules/spells/targeted/ethereal_jaunt.dm
@@ -105,5 +105,3 @@
/obj/effect/dummy/spell_jaunt/legacy_ex_act(blah)
return
-/obj/effect/dummy/spell_jaunt/bullet_act(blah)
- return
diff --git a/code/modules/surgery/generic.dm b/code/modules/surgery/generic.dm
index 55b3c517a8da..4624eae98cc1 100644
--- a/code/modules/surgery/generic.dm
+++ b/code/modules/surgery/generic.dm
@@ -76,7 +76,7 @@
/obj/item/surgical/scalpel/laser3 = 95, \
/obj/item/surgical/scalpel/laser2 = 85, \
/obj/item/surgical/scalpel/laser1 = 75, \
- /obj/item/melee/energy/sword = 5
+ /obj/item/melee/transforming/energy/sword = 5
)
priority = 2
req_open = 0
diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm
index 4f5bff255204..e0f11fce24b9 100644
--- a/code/modules/surgery/implant.dm
+++ b/code/modules/surgery/implant.dm
@@ -158,6 +158,15 @@
// IMPLANT/ITEM REMOVAL SURGERY
//////////////////////////////////////////////////////////////////
+/obj/item/proc/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from)
+ removing_from.implants -= src
+
+ target.update_hud_sec_implants()
+
+ loc = get_turf(target)
+ add_blood(target)
+ update_icon()
+
/datum/surgery_step/cavity/implant_removal
allowed_tools = list(
/obj/item/surgical/hemostat = 100, \
@@ -190,49 +199,19 @@
/datum/surgery_step/cavity/implant_removal/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
- var/find_prob = 0
-
if (affected.implants.len)
- var/obj/item/obj = pick(affected.implants)
-
- if(istype(obj,/obj/item/implant))
- var/obj/item/implant/imp = obj
- if (imp.islegal())
- find_prob +=60
- else
- find_prob +=40
- else
- find_prob +=50
-
- if (prob(find_prob))
- user.visible_message("[user] takes something out of incision on [target]'s [affected.name] with \the [tool]!", \
- "You take [obj] out of incision on [target]'s [affected.name]s with \the [tool]!" )
- affected.implants -= obj
-
- target.update_hud_sec_implants()
-
- //Handle possessive brain borers.
- if(istype(obj,/mob/living/simple_mob/animal/borer))
- var/mob/living/simple_mob/animal/borer/worm = obj
- if(worm.controlling)
- target.release_control()
- worm.detatch()
- worm.leave_host()
- else
- obj.loc = get_turf(target)
- obj.add_blood(target)
- obj.update_icon()
- if(istype(obj,/obj/item/implant))
- var/obj/item/implant/imp = obj
- imp.imp_in = null
- imp.implanted = 0
- if(istype(obj, /obj/item/implant/mirror))
- target.mirror = null
- else if(istype(tool,/obj/item/nif)){var/obj/item/nif/N = tool;N.unimplant(target)}
- else
- user.visible_message("[user] removes \the [tool] from [target]'s [affected.name].", \
- "There's something inside [target]'s [affected.name], but you just missed it this time." )
+ var/obj/item/obj = input("What do you want to extract?") in affected.implants
+
+ user.visible_message("[user] takes something out of incision on [target]'s [affected.name] with \the [tool]!", \
+ "You take [obj] out of incision on [target]'s [affected.name]s with \the [tool]!" )
+
+ obj.surgically_remove(target, affected)
+
+ if(istype(tool, /obj/item/nif))
+ var/obj/item/nif/N = tool
+ N.unimplant(target)
+
else
user.visible_message("[user] could not find anything inside [target]'s [affected.name], and pulls \the [tool] out.", \
"You could not find anything inside [target]'s [affected.name]." )
diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm
index 27b14622f94d..deaf039bbe40 100644
--- a/code/modules/vehicles/sealed.dm
+++ b/code/modules/vehicles/sealed.dm
@@ -176,7 +176,7 @@
* * silent - suppress user messages
* * suppressed - suppress external messages
*/
-/obj/vehicle/sealed/proc/mob_exit(mob/exiting, datum/event_args/actor/actor, atom/new_loc, silent, suppressed)
+/obj/vehicle/sealed/proc/mob_exit(mob/exiting, datum/event_args/actor/actor, atom/new_loc = drop_location(), silent, suppressed)
var/old_control_flags = occupants[exiting]
remove_occupant(exiting)
exiting.forceMove(new_loc)
diff --git a/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm b/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm
index c575a01e3aa6..b2de5f21e69a 100644
--- a/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm
+++ b/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm
@@ -11,11 +11,3 @@
/obj/projectile/beam/pulse/heavy
name = "heavy pulse laser"
icon_state = "pulse1_bl"
- var/life = 20
-
-/obj/projectile/beam/pulse/heavy/Bump(atom/A)
- A.bullet_act(src, def_zone)
- src.life -= 10
- if(life <= 0)
- qdel(src)
- return
diff --git a/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm
index 90966f171338..6e9b11c38847 100644
--- a/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm
+++ b/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm
@@ -83,11 +83,11 @@
if(!istype(P))
return
- P.accuracy -= user.get_accuracy_penalty()
+ P.accuracy_overall_modify *= 1 - (user.get_accuracy_penalty() / 100)
// Some modifiers make it harder or easier to hit things.
for(var/datum/modifier/M in user.modifiers)
if(!isnull(M.accuracy))
- P.accuracy += 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/vehicles/sealed/mecha/mecha.dm b/code/modules/vehicles/sealed/mecha/mecha.dm
index e06cb7c084a5..f5e86771f096 100644
--- a/code/modules/vehicles/sealed/mecha/mecha.dm
+++ b/code/modules/vehicles/sealed/mecha/mecha.dm
@@ -304,16 +304,16 @@
src.legacy_eject_occupant()
for(var/mob/M in src) //Be Extra Sure
M.forceMove(get_turf(src))
- M.loc.Entered(M)
if(M != src.occupant_legacy)
step_rand(M)
+
for(var/atom/movable/A in src.cargo)
A.forceMove(get_turf(src))
var/turf/T = get_turf(A)
if(T)
T.Entered(A)
step_rand(A)
-
+ cargo = list()
if(prob(30))
explosion(get_turf(loc), 0, 0, 1, 3)
@@ -918,6 +918,7 @@
///////////////////////////////////
//ATM, the ignore_threshold is literally only used for the pulse rifles beams used mostly by deathsquads.
+// todo: this is uh, not a check, this is a **roll**.
/obj/vehicle/sealed/mecha/proc/check_for_internal_damage(var/list/possible_int_damage,var/ignore_threshold=null)
if(!islist(possible_int_damage) || !length(possible_int_damage)) return
if(prob(30))
@@ -960,16 +961,6 @@
occupant_message("Internal fire extinquished.")
if(MECHA_INT_TANK_BREACH)
occupant_message("Damaged internal tank has been sealed.")
- return
-
-
-////////////////////////////////////////
-//////// Health related procs ////////
-////////////////////////////////////////
-
-/obj/vehicle/sealed/mecha/bullet_act(obj/projectile/Proj)
- . = ..()
-
/obj/vehicle/sealed/mecha/proc/take_damage_legacy(amount, type="brute")
update_damage_alerts()
@@ -1158,17 +1149,16 @@
return
-// todo: MAKE INFLICT_DAMAGE_INSTANCE() A THING ON HIT HANDLING PR!!
-/obj/vehicle/sealed/mecha/bullet_act(var/obj/projectile/Proj) //wrapper
- if(istype(Proj, /obj/projectile/test))
- var/obj/projectile/test/Test = Proj
- Test.hit |= occupant_legacy // Register a hit on the occupant_legacy, for things like turrets, or in simple-mob cases stopping friendly fire in firing line mode.
- return
+/obj/vehicle/sealed/mecha/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ if(istype(proj, /obj/projectile/test))
+ var/obj/projectile/test/Test = proj
+ Test.hit |= occupant_legacy // Register a hit on the occupant, for things like turrets, or in simple-mob cases stopping friendly fire in firing line mode.
+ return ..()
- src.log_message("Hit by projectile. Type: [Proj.name]([Proj.damage_flag]).",1)
- call((proc_res["dynbulletdamage"]||src), "dynbulletdamage")(Proj) //calls equipment
- ..()
- return
+ src.log_message("Hit by projectile. Type: [proj.name]([proj.damage_flag]).",1)
+ impact_flags |= call((proc_res["dynbulletdamage"]||src), "dynbulletdamage")(proj) //calls equipment
+ impact_flags |= PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE
+ return ..()
/obj/vehicle/sealed/mecha/proc/dynbulletdamage(var/obj/projectile/Proj)
var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR]
@@ -1210,7 +1200,7 @@
if(pass_damage < temp_damage_minimum)//too pathetic to really damage you.
src.occupant_message("The armor deflects incoming projectile.")
src.visible_message("The [src.name] armor deflects\the [Proj]")
- return
+ return PROJECTILE_IMPACT_BLOCKED
else if(Proj.armor_penetration < temp_minimum_penetration) //If you don't have enough pen, you won't do full damage
src.occupant_message("\The [Proj] struggles to pierce \the [src] armor.")
@@ -1230,25 +1220,21 @@
src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),ignore_threshold)
//AP projectiles have a chance to cause additional damage
- if(Proj.penetrating)
- var/distance = get_dist(Proj.starting, get_turf(loc))
- var/hit_occupant = 1 //only allow the occupant_legacy to be hit once
- for(var/i in 1 to min(Proj.penetrating, round(Proj.damage/15)))
+ if(Proj.legacy_penetrating)
+ var/hit_occupant = 1 //only allow the occupant to be hit once
+ for(var/i in 1 to min(Proj.legacy_penetrating, round(Proj.damage/15)))
if(src.occupant_legacy && hit_occupant && prob(20))
- Proj.projectile_attack_mob(src.occupant_legacy, distance)
+ Proj.impact(occupant_legacy)
hit_occupant = 0
else
if(pass_damage > internal_damage_minimum) //Only decently painful attacks trigger a chance of mech damage.
src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT), 1)
- Proj.penetrating--
+ Proj.legacy_penetrating--
if(prob(15))
break //give a chance to exit early
- Proj.on_hit(src) //on_hit just returns if it's argument is not a living mob so does this actually do anything?
- return
-
//This refer to whenever you are caught in an explosion.
/obj/vehicle/sealed/mecha/legacy_ex_act(severity)
var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR]
@@ -1267,19 +1253,13 @@
src.log_append_to_last("Armor saved, changing severity to [severity].")
switch(severity)
if(1.0)
- src.take_damage_legacy(initial(src.integrity), "bomb")
+ src.take_damage_legacy(initial(src.integrity)/1.25, "bomb")
if(2.0)
- if (prob(30))
- src.take_damage_legacy(initial(src.integrity), "bomb")
- else
- src.take_damage_legacy(initial(src.integrity)/2, "bomb")
- src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1)
+ src.take_damage_legacy(initial(src.integrity)/2.5, "bomb")
+ src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1)
if(3.0)
- if (prob(5))
- qdel(src)
- else
- src.take_damage_legacy(initial(src.integrity)/5, "bomb")
- src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1)
+ src.take_damage_legacy(initial(src.integrity)/8, "bomb")
+ src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1)
return
/*Will fix later -Sieve
@@ -2724,7 +2704,6 @@
// removing.mobility_flags = NONE
removing.clear_alert("charge")
removing.clear_alert("mech damage")
- removing.reset_perspective()
if(occupant_legacy == removing)
occupant_legacy = null
diff --git a/code/modules/vehicles/sealed/mecha/mecha_actions.dm b/code/modules/vehicles/sealed/mecha/mecha_actions.dm
index 188179b6e542..a0ec46bdf318 100644
--- a/code/modules/vehicles/sealed/mecha/mecha_actions.dm
+++ b/code/modules/vehicles/sealed/mecha/mecha_actions.dm
@@ -68,7 +68,7 @@
return
var/obj/vehicle/sealed/mecha/chassis = target
chassis.lights()
- button_icon_state = "mech_lights_[chassis.lights ? "off" : "on"]"
+ button_icon_state = "mech_lights_[chassis.lights ? "on" : "off"]"
update_buttons()
/datum/action/mecha/mech_toggle_internals
@@ -80,7 +80,7 @@
if(.)
return
var/obj/vehicle/sealed/mecha/chassis = target
- button_icon_state = "mech_internals_[chassis.use_internal_tank ? "off" : "on"]"
+ button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]"
update_buttons()
chassis.internal_tank()
@@ -105,7 +105,7 @@
return
var/obj/vehicle/sealed/mecha/chassis = target
chassis.strafing()
- button_icon_state = "mech_strafe_[chassis.strafing ? "off" : "on"]"
+ button_icon_state = "mech_strafe_[chassis.strafing ? "on" : "off"]"
update_buttons()
/datum/action/mecha/mech_defence_mode
@@ -118,7 +118,7 @@
return
var/obj/vehicle/sealed/mecha/chassis = target
chassis.defence_mode()
- button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "off" : "on"]"
+ button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "on" : "off"]"
update_buttons()
/datum/action/mecha/mech_overload_mode
@@ -131,7 +131,7 @@
return
var/obj/vehicle/sealed/mecha/chassis = target
chassis.overload()
- button_icon_state = "mech_overload_[chassis.overload ? "off" : "on"]"
+ button_icon_state = "mech_overload_[chassis.overload ? "on" : "off"]"
update_buttons()
/datum/action/mecha/mech_smoke
@@ -155,7 +155,7 @@
return
var/obj/vehicle/sealed/mecha/chassis = target
chassis.zoom()
- button_icon_state = "mech_zoom_[chassis.zoom ? "off" : "on"]"
+ button_icon_state = "mech_zoom_[chassis.zoom ? "on" : "off"]"
update_buttons()
/datum/action/mecha/mech_toggle_thrusters
@@ -168,7 +168,7 @@
return
var/obj/vehicle/sealed/mecha/chassis = target
chassis.thrusters()
- button_icon_state = "mech_thrusters_[chassis.thrusters ? "off" : "on"]"
+ button_icon_state = "mech_thrusters_[chassis.thrusters ? "on" : "off"]"
update_buttons()
/datum/action/mecha/mech_cycle_equip //I'll be honest, i don't understand this part, buuuuuut it works!
@@ -241,7 +241,7 @@
if(.)
return
var/obj/vehicle/sealed/mecha/chassis = target
- button_icon_state = "mech_phasing_[chassis.phasing ? "off" : "on"]"
+ button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]"
update_buttons()
chassis.phasing()
@@ -254,7 +254,7 @@
if(.)
return
var/obj/vehicle/sealed/mecha/chassis = target
- button_icon_state = "mech_phasing_[chassis.cloaked ? "off" : "on"]"
+ button_icon_state = "mech_phasing_[chassis.cloaked ? "on" : "off"]"
update_buttons()
chassis.toggle_cloaking()
diff --git a/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm b/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm
index 210ec44013fe..c7f2b5ee2392 100644
--- a/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm
+++ b/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm
@@ -20,16 +20,6 @@
crowbar_salvage = new
return
-/obj/effect/decal/mecha_wreckage/legacy_ex_act(severity)
- if(severity < 2)
- spawn
- qdel(src)
- return
-
-/obj/effect/decal/mecha_wreckage/bullet_act(var/obj/projectile/Proj)
- return
-
-
/obj/effect/decal/mecha_wreckage/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/weldingtool))
var/obj/item/weldingtool/WT = W
diff --git a/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm b/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm
index 49381b860169..10e46a3ccd1c 100644
--- a/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm
+++ b/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm
@@ -128,9 +128,11 @@
icon_state = "shell"
damage = 1000 // In order to 1-hit any other mech and royally fuck anyone unfortunate enough to get in the way.
-/obj/projectile/bullet/cannon/on_hit(var/atom/target, var/blocked = 0)
+/obj/projectile/bullet/cannon/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
explosion(target, 0, 0, 2, 4)
- return 1
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/cannon/weak
name = "8.8 cm KwK 36"
diff --git a/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm b/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm
index abbbddf2bf1e..4565776996a8 100644
--- a/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm
+++ b/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm
@@ -27,13 +27,6 @@
icon_scale_x = 1.2
icon_scale_y = 1.2
-/obj/vehicle/sealed/mecha/working/ripley/Destroy()
- for(var/atom/movable/A in src.cargo)
- A.forceMove(loc)
- step_rand(A)
- cargo.Cut()
- ..()
-
/obj/vehicle/sealed/mecha/working/ripley/firefighter
desc = "Standard APLU chassis was refitted with additional thermal protection and cistern."
name = "APLU \"Firefighter\""
diff --git a/code/modules/vehicles_legacy/Securitrain_vr.dm b/code/modules/vehicles_legacy/Securitrain_vr.dm
index 005f7ac2b577..81be49d1a6ba 100644
--- a/code/modules/vehicles_legacy/Securitrain_vr.dm
+++ b/code/modules/vehicles_legacy/Securitrain_vr.dm
@@ -101,12 +101,11 @@
..()
//cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead
-/obj/vehicle_old/train/security/bullet_act(var/obj/projectile/Proj)
+/obj/vehicle_old/train/security/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(has_buckled_mobs() && prob(70))
- var/mob/living/M = pick(buckled_mobs)
- M.bullet_act(Proj)
- return
- ..()
+ var/mob/buckled = pick(buckled_mobs)
+ return proj.impact_redirect(buckled, args)
+ return ..()
/obj/vehicle_old/train/security/update_icon()
if(open)
diff --git a/code/modules/vehicles_legacy/bike.dm b/code/modules/vehicles_legacy/bike.dm
index 121c533d38f4..9e5ee11e95c7 100644
--- a/code/modules/vehicles_legacy/bike.dm
+++ b/code/modules/vehicles_legacy/bike.dm
@@ -174,12 +174,11 @@
..()
-/obj/vehicle_old/bike/bullet_act(var/obj/projectile/Proj)
+/obj/vehicle_old/bike/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(has_buckled_mobs() && prob(protection_percent))
var/mob/living/L = pick(buckled_mobs)
- L.bullet_act(Proj)
- return
- ..()
+ return L.bullet_act(arglist(args))
+ return ..()
/obj/vehicle_old/bike/update_icon()
cut_overlays()
diff --git a/code/modules/vehicles_legacy/cargo_train.dm b/code/modules/vehicles_legacy/cargo_train.dm
index 0f5c981c533d..084640716307 100644
--- a/code/modules/vehicles_legacy/cargo_train.dm
+++ b/code/modules/vehicles_legacy/cargo_train.dm
@@ -86,7 +86,7 @@
/*
//cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead
-/obj/vehicle_old/train/cargo/bullet_act(var/obj/projectile/Proj)
+/obj/vehicle_old/train/cargo/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(has_buckled_mobs() && prob(70))
var/mob/living/L = pick(buckled_mobs)
L.bullet_act(Proj)
diff --git a/code/modules/vehicles_legacy/rover_vr.dm b/code/modules/vehicles_legacy/rover_vr.dm
index cbc180b2767d..ef0305f9abb4 100644
--- a/code/modules/vehicles_legacy/rover_vr.dm
+++ b/code/modules/vehicles_legacy/rover_vr.dm
@@ -102,12 +102,11 @@
..()
//cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead
-/obj/vehicle_old/train/rover/bullet_act(var/obj/projectile/Proj)
+/obj/vehicle_old/train/rover/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(has_buckled_mobs() && prob(70))
- var/mob/living/M = pick(buckled_mobs)
- M.bullet_act(Proj)
- return
- ..()
+ var/mob/buckled = pick(buckled_mobs)
+ return proj.impact_redirect(buckled, args)
+ return ..()
/obj/vehicle_old/train/rover/update_icon()
if(open)
diff --git a/code/modules/vehicles_legacy/train.dm b/code/modules/vehicles_legacy/train.dm
index 1cd8e8bab08f..32f45a77dabe 100644
--- a/code/modules/vehicles_legacy/train.dm
+++ b/code/modules/vehicles_legacy/train.dm
@@ -55,12 +55,11 @@
add_attack_logs(D,M,"Ran over with [src.name]")
//trains are commonly open topped, so there is a chance the projectile will hit the mob riding the train instead
-/obj/vehicle_old/train/bullet_act(var/obj/projectile/Proj)
+/obj/vehicle_old/train/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(has_buckled_mobs() && prob(70))
- var/mob/living/L = pick(buckled_mobs)
- L.bullet_act(Proj)
- return
- ..()
+ var/mob/buckled = pick(buckled_mobs)
+ return proj.impact_redirect(buckled, args)
+ return ..()
/obj/vehicle_old/train/update_icon()
if(open)
diff --git a/code/modules/vehicles_legacy/vehicle.dm b/code/modules/vehicles_legacy/vehicle.dm
index 8e5b29152079..80de4b285178 100644
--- a/code/modules/vehicles_legacy/vehicle.dm
+++ b/code/modules/vehicles_legacy/vehicle.dm
@@ -13,6 +13,8 @@
anchored = 1
animate_movement=1
light_range = 3
+ // todo: uses old integrity for now
+ integrity_flags = INTEGRITY_INDESTRUCTIBLE
buckle_allowed = TRUE
buckle_flags = BUCKLING_PASS_PROJECTILES_UPWARDS
@@ -121,9 +123,9 @@
else
..()
-/obj/vehicle_old/bullet_act(var/obj/projectile/Proj)
- health -= Proj.get_structure_damage()
- ..()
+/obj/vehicle_old/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ health -= proj.get_structure_damage()
healthcheck()
/obj/vehicle_old/proc/adjust_health(amount)
diff --git a/code/modules/vore/fluffstuff/custom_items.dm b/code/modules/vore/fluffstuff/custom_items.dm
index 0ac75f8d2924..531e04b41d11 100644
--- a/code/modules/vore/fluffstuff/custom_items.dm
+++ b/code/modules/vore/fluffstuff/custom_items.dm
@@ -66,50 +66,6 @@
if(!parts)
qdel(src)
-/*
-//JoanRisu:Joan Risu
-/obj/item/flame/lighter/zippo/fluff/joan
- name = "Federation Zippo Lighter"
- desc = "A red zippo lighter with the United Federation Logo on it."
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "joanzip"
-
-//JoanRisu:Joan Risu
-/obj/item/sword/fluff/joanaria
- name = "Aria"
- desc = "A beautifully crafted rapier owned by Joan Risu. It has a thin blade and is used for quick attacks."
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "joanaria"
- icon_override = 'icons/vore/custom_items_vr.dmi'
- item_state = "joanariamob"
- origin_tech = "materials=7"
- damage_force = 15
- sharp = 1
- edge = 1
- attack_sound = 'sound/weapons/bladeslice.ogg'
-
-
-/obj/item/sword/fluff/joanaria/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
-
- if(default_parry_check(user, attacker, damage_source) && prob(75))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
-
-//joanrisu:Katarina Eine
-/obj/item/material/knife/tacknife/combatknife/fluff/katarina
- name = "tactical Knife"
- desc = "A tactical knife with a small butterly engraved on the blade."
-*/
-
-/obj/item/material/knife/tacknife/combatknife/fluff/katarina/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
-
- if(default_parry_check(user, attacker, damage_source) && prob(75))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
//For General use
/obj/item/sword/fluff/joanaria/scisword
@@ -119,87 +75,6 @@
icon_state = "scisword"
origin_tech = "materials=7"
-/*
-//john.wayne9392:Harmony Prechtl
-/obj/item/twohanded/fireaxe/fluff/mjollnir
- name = "Mjollnir"
- desc = "Large hammer that looks like it can do a great deal of damage if properly used."
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "harmonymjollnir"
- origin_tech = "materials=7"
- attack_verb = list("attacked", "hammered", "smashed", "slammed", "crushed")
-
-//JoanRisu:Joan Risu
-/obj/item/card/id/centcom/station/fluff/joanbadge
- name = "Faded Badge"
- desc = "A faded badge, backed with leather, that reads 'NT Security Force' across the front."
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "joanbadge"
- registered_name = "Joan Risu"
- assignment = "Centcom Officer"
-
-
-/obj/item/card/id/centcom/station/fluff/joanbadge/attack_self(mob/user)
- . = ..()
- if(.)
- return
- if(isliving(user))
- user.visible_message("[user] flashes their golden security badge.\nIt reads:NT Security.","You display the faded badge.\nIt reads: NT Security.")
-
-/obj/item/card/id/centcom/station/fluff/joanbadge/attack(mob/living/carbon/human/M, mob/living/user)
- if(isliving(user))
- user.visible_message("[user] invades [M]'s personal space, thrusting [src] into their face insistently.","You invade [M]'s personal space, thrusting [src] into their face insistently.")
-
-//JoanRisu:Joan Risu
-/obj/item/pda/heads/hos/joanpda
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "pda-joan"
-
-//Vorrarkul:Lucina Dakarim
-/obj/item/pda/heads/cmo/fluff/lucinapda
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "pda-lucina"
-
-//john.wayne9392:Harmony Prechtl
-/obj/item/modkit_conversion/fluff/harmonyspace
- name = "Harmony's captain space suit modkit"
- desc = "A kit containing all the needed tools and parts to modify a Captain's hardsuit. It has green and yellow parts inside."
-
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "harmony_kit"
-
- from_helmet = /obj/item/clothing/head/helmet/space/capspace
- from_suit = /obj/item/clothing/suit/armor/captain
- to_helmet = /obj/item/clothing/head/helmet/space/capspace/fluff/harmhelm
- to_suit = /obj/item/clothing/suit/armor/captain/fluff/harmsuit
-
-//john.wayne9392:Harmony Prechtl
-/obj/item/modkit_conversion/fluff/harmonysuit
- name = "Harmony's captain suit modkit"
- desc = "A sewing kit containing all the needed tools and fabric to modify a Captain's suit and hat. It has green and yellow fabrics inside."
-
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "harmony_kit"
-
- from_helmet = /obj/item/clothing/head/caphat
- from_suit = /obj/item/clothing/under/rank/captain
- to_helmet = /obj/item/clothing/head/centhat/fluff/harmhat
- to_suit = /obj/item/clothing/under/rank/captain/fluff/harmuniform
-
-//scree:Scree
-/obj/item/modkit_conversion/fluff/screekit
- name = "Scree's hardsuit modification kit"
- desc = "A kit containing all the needed tools and parts to modify a hardsuit for a specific user. This one looks like it's fitted for a winged creature."
-
- icon = 'icons/vore/custom_items_vr.dmi'
- icon_state = "modkit"
-
- from_helmet = /obj/item/clothing/head/helmet/space/void
- from_suit = /obj/item/clothing/suit/space/void
- to_helmet = /obj/item/clothing/head/helmet/space/void/engineering/hazmat/fluff/screehelm
- to_suit = /obj/item/clothing/suit/space/void/engineering/hazmat/fluff/screespess
-*/
-
//General Use
/obj/item/flag
name = "Nanotrasen Banner"
@@ -1254,6 +1129,7 @@
..()
//jacknoir413:Areax Third
+// todo: check sprite, if it matches citmain just integrate this to citrp proper.
/obj/item/melee/baton/fluff/stunstaff
name = "Electrostaff"
desc = "Six-foot long staff from dull, rugged metal, with two thin spikes protruding from each end. Small etching near to the middle of it reads 'Children Of Nyx Facilities: Product No. 12'."
@@ -1270,6 +1146,10 @@
attack_verb = list("beaten")
lightcolor = "#CC33FF"
+ passive_parry = /datum/passive_parry/melee{
+ parry_chance_melee = 30;
+ }
+
//Two Handed
var/wielded = 0
var/base_name = "stunstaff"
@@ -1294,13 +1174,6 @@
update_icon()
..()
-/obj/item/melee/baton/fluff/stunstaff/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(wielded && default_parry_check(user, attacker, damage_source) && prob(30))
- user.visible_message("\The [user] parries [attack_text] with \the [src]!")
- playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1)
- return 1
- return 0
-
/obj/item/melee/baton/fluff/stunstaff/update_icon()
icon_state = "[base_icon][wielded][status]"
item_state = icon_state
diff --git a/code/modules/xenoarcheaology/artifacts/artifact.dm b/code/modules/xenoarcheaology/artifacts/artifact.dm
index b29df3796257..a55ec3da15ef 100644
--- a/code/modules/xenoarcheaology/artifacts/artifact.dm
+++ b/code/modules/xenoarcheaology/artifacts/artifact.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/xenoarchaeology.dmi'
icon_state = "ano00"
var/icon_num = 0
+ integrity_flags = INTEGRITY_INDESTRUCTIBLE
density = TRUE
var/datum/artifact_effect/my_effect
var/datum/artifact_effect/secondary_effect
@@ -223,7 +224,7 @@
if(secondary_effect && secondary_effect.trigger == TRIGGER_TOXIN && prob(25))
secondary_effect.ToggleActivate(0)
else if(istype(W,/obj/item/melee/baton) && W:status ||\
- istype(W,/obj/item/melee/energy) ||\
+ istype(W,/obj/item/melee/transforming/energy) ||\
istype(W,/obj/item/melee/cultblade) ||\
istype(W,/obj/item/card/emag) ||\
istype(W,/obj/item/multitool))
@@ -274,16 +275,17 @@
to_chat(M, "You accidentally touch [src].")
..()
-/obj/machinery/artifact/bullet_act(var/obj/projectile/P)
- if(istype(P,/obj/projectile/bullet))
+/obj/machinery/artifact/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(istype(proj,/obj/projectile/bullet))
if(my_effect.trigger == TRIGGER_FORCE)
my_effect.ToggleActivate()
if(secondary_effect && secondary_effect.trigger == TRIGGER_FORCE && prob(25))
secondary_effect.ToggleActivate(0)
- else if(istype(P,/obj/projectile/beam) ||\
- istype(P,/obj/projectile/ion) ||\
- istype(P,/obj/projectile/energy))
+ else if(istype(proj,/obj/projectile/beam) ||\
+ istype(proj,/obj/projectile/ion) ||\
+ istype(proj,/obj/projectile/energy))
if(my_effect.trigger == TRIGGER_ENERGY)
my_effect.ToggleActivate()
if(secondary_effect && secondary_effect.trigger == TRIGGER_ENERGY && prob(25))
diff --git a/code/modules/xenoarcheaology/tools/coolant_tank.dm b/code/modules/xenoarcheaology/tools/coolant_tank.dm
index ba9ac92b9f42..feeeadb1fca8 100644
--- a/code/modules/xenoarcheaology/tools/coolant_tank.dm
+++ b/code/modules/xenoarcheaology/tools/coolant_tank.dm
@@ -9,9 +9,10 @@
. = ..()
reagents.add_reagent("coolant", 1000)
-/obj/structure/reagent_dispensers/coolanttank/bullet_act(var/obj/projectile/Proj)
- if(Proj.get_structure_damage())
- if(!istype(Proj ,/obj/projectile/beam/lasertag) && !istype(Proj ,/obj/projectile/beam/practice) ) // TODO: make this not terrible
+/obj/structure/reagent_dispensers/coolanttank/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
+ . = ..()
+ if(proj.get_structure_damage())
+ if(!istype(proj ,/obj/projectile/beam/lasertag) && !istype(proj ,/obj/projectile/beam/practice) ) // TODO: make this not terrible
explode()
/obj/structure/reagent_dispensers/coolanttank/legacy_ex_act()
diff --git a/code/modules/xenobio/items/weapons.dm b/code/modules/xenobio/items/weapons.dm
index 243054319cad..e3a2c4b73cde 100644
--- a/code/modules/xenobio/items/weapons.dm
+++ b/code/modules/xenobio/items/weapons.dm
@@ -91,7 +91,11 @@
/obj/projectile/beam/stun/xeno/weak //Weaker variant for non-research equipment, turrets, or rapid fire types.
agony = 3
-/obj/projectile/beam/stun/xeno/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null)
+/obj/projectile/beam/stun/xeno/on_impact(atom/target, impact_flags, def_zone, efficiency)
+ . = ..()
+ if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)
+ return
+
if(istype(target, /mob/living))
var/mob/living/L = target
if(L.mob_class & MOB_CLASS_SLIME)
@@ -106,5 +110,3 @@
if(H.species && H.species.get_species_id() == SPECIES_ID_PROMETHEAN)
if(agony == initial(agony)) // ??????
agony = round((14 * agony) - agony) //60-4 = 56, 56 / 4 = 14. Prior was flat 60 - agony of the beam to equate to 60.
-
- ..()
diff --git a/code/modules/xenobio2/mob/xeno procs.dm b/code/modules/xenobio2/mob/xeno procs.dm
index ad6846e281e0..99c7e66db44e 100644
--- a/code/modules/xenobio2/mob/xeno procs.dm
+++ b/code/modules/xenobio2/mob/xeno procs.dm
@@ -143,7 +143,7 @@ Divergence proc, used in mutation to make unique datums.
/mob/living/simple_mob/xeno/proc/BuildReagentLists()
return
-/mob/living/simple_mob/xeno/bullet_act(var/obj/projectile/P)
+/mob/living/simple_mob/xeno/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
//Shamelessly stolen from ablative armor.
if((traitdat.traits[TRAIT_XENO_CHROMATIC]) && istype(P, /obj/projectile/beam))
visible_message(")\The beam reflects off of the [src]!")
diff --git a/code/modules/xenobio2/mob/xeno.dm b/code/modules/xenobio2/mob/xeno.dm
index 4f801e75df5f..6b9cef60afb4 100644
--- a/code/modules/xenobio2/mob/xeno.dm
+++ b/code/modules/xenobio2/mob/xeno.dm
@@ -92,7 +92,7 @@ Also includes Life and New
if(!health)
set_stat(DEAD)
-/mob/living/simple_mob/xeno/bullet_act(var/obj/projectile/Proj)
+/mob/living/simple_mob/xeno/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(istype(Proj, /obj/projectile/beam/stun/xeno))
var/obj/projectile/beam/stun/xeno/hit = Proj
stasis += hit.stasisforce
diff --git a/config/entries/game.txt b/config/entries/game.txt
index 8f155dab9f77..b83f9f2e7cde 100644
--- a/config/entries/game.txt
+++ b/config/entries/game.txt
@@ -11,15 +11,9 @@ STARLIGHT
## Set to 0 to disable holidays (you monster)
ALLOW_HOLIDAYS
-## Engine submap probabilities
-## Supermatter
-ENGINE_SUBMAP EngineSubmap_SM 40
-## R-UST Fusion
-ENGINE_SUBMAP EngineSubmap_RUST 40
-## Tesla
-ENGINE_SUBMAP EngineSubmap_Tesla 20
-## Singulo
-ENGINE_SUBMAP EngineSubmap_Singulo 20
+## Engine submap probabilities by name
+# todo: add the rest once names are standardized
+# ENGINE_SUBMAP ProcEngine_Triumph_RUST
## Alert Levels
# ALERT_DESC_GREEN TEXT HERE
diff --git a/icons/effects/defensive/README.md b/icons/effects/defensive/README.md
new file mode 100644
index 000000000000..750cab6abc41
--- /dev/null
+++ b/icons/effects/defensive/README.md
@@ -0,0 +1,3 @@
+# Effects - Defensive
+
+Basically shields, parries, etc.
diff --git a/icons/effects/defensive/main_parry.dmi b/icons/effects/defensive/main_parry.dmi
new file mode 100644
index 000000000000..307ef5b4a7e8
Binary files /dev/null and b/icons/effects/defensive/main_parry.dmi differ
diff --git a/icons/items/melee/basic.dmi b/icons/items/melee/basic.dmi
new file mode 100644
index 000000000000..c1207c837d18
Binary files /dev/null and b/icons/items/melee/basic.dmi differ
diff --git a/icons/items/melee/transforming.dmi b/icons/items/melee/transforming.dmi
new file mode 100644
index 000000000000..0a1c652185cf
Binary files /dev/null and b/icons/items/melee/transforming.dmi differ
diff --git a/icons/items/shields/basic.dmi b/icons/items/shields/basic.dmi
new file mode 100644
index 000000000000..6afb78610041
Binary files /dev/null and b/icons/items/shields/basic.dmi differ
diff --git a/icons/items/shields/transforming.dmi b/icons/items/shields/transforming.dmi
new file mode 100644
index 000000000000..56d1329a3486
Binary files /dev/null and b/icons/items/shields/transforming.dmi differ
diff --git a/icons/mob/items/lefthand_melee.dmi b/icons/mob/items/lefthand_melee.dmi
index 9b9c5035fdf9..9d8c1daa0d54 100644
Binary files a/icons/mob/items/lefthand_melee.dmi and b/icons/mob/items/lefthand_melee.dmi differ
diff --git a/icons/mob/items/righthand_melee.dmi b/icons/mob/items/righthand_melee.dmi
index 9609132d0a7b..1cfcd5f622c5 100644
Binary files a/icons/mob/items/righthand_melee.dmi and b/icons/mob/items/righthand_melee.dmi differ
diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi
index 1d4e812f6708..cda3aaf191cc 100644
Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ
diff --git a/icons/obj/weapons_vr.dmi b/icons/obj/weapons_vr.dmi
index efb220ac8ab6..1ca53b66f4c7 100644
Binary files a/icons/obj/weapons_vr.dmi and b/icons/obj/weapons_vr.dmi differ
diff --git a/maps/away_missions/140x140/zoo.dmm b/maps/away_missions/140x140/zoo.dmm
index 84e18f701d06..e87147a3208a 100644
--- a/maps/away_missions/140x140/zoo.dmm
+++ b/maps/away_missions/140x140/zoo.dmm
@@ -73,7 +73,7 @@
/obj/item/stolenpackage,
/obj/item/plastique,
/obj/item/plastique,
-/obj/item/melee/energy/sword/pirate,
+/obj/item/melee/transforming/energy/sword/cutlass,
/turf/space,
/area/awaymission/zoo)
"aj" = (
diff --git a/maps/away_missions/archive/spacebattle.dmm b/maps/away_missions/archive/spacebattle.dmm
index 48106f306f10..0c972979b977 100644
--- a/maps/away_missions/archive/spacebattle.dmm
+++ b/maps/away_missions/archive/spacebattle.dmm
@@ -270,7 +270,7 @@
/area/awaymission/spacebattle/syndicate3)
"bw" = (
/obj/structure/table/reinforced,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/simulated/floor,
/area/awaymission/spacebattle/syndicate1)
"bx" = (
@@ -888,7 +888,7 @@
/turf/simulated/floor,
/area/awaymission/spacebattle/cruiser)
"eR" = (
-/obj/item/shield/energy,
+/obj/item/shield/transforming/energy,
/turf/simulated/floor,
/area/awaymission/spacebattle/cruiser)
"eT" = (
@@ -929,7 +929,7 @@
/area/awaymission/spacebattle/cruiser)
"fa" = (
/obj/spawner/corpse/syndicatesoldier,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/obj/effect/debris/cleanable/blood,
/turf/simulated/floor,
/area/awaymission/spacebattle/cruiser)
diff --git a/maps/away_missions/archive/stationCollision.dmm b/maps/away_missions/archive/stationCollision.dmm
index 74a28eeb4968..94520c133f68 100644
--- a/maps/away_missions/archive/stationCollision.dmm
+++ b/maps/away_missions/archive/stationCollision.dmm
@@ -2777,7 +2777,7 @@
/turf/simulated/floor,
/area/awaymission/arrivalblock)
"kN" = (
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/obj/item/clothing/shoes/syndigaloshes,
/obj/item/clothing/under/syndicate,
/obj/effect/decal/remains/human{
diff --git a/maps/rift/levels/rift-11-orbital.dmm b/maps/rift/levels/rift-11-orbital.dmm
index 90f246634f87..fc113d7d0de8 100644
--- a/maps/rift/levels/rift-11-orbital.dmm
+++ b/maps/rift/levels/rift-11-orbital.dmm
@@ -6284,7 +6284,7 @@
/obj/item/clothing/suit/armor/tdome/red,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor/dark,
/area/tdome/tdome1)
"tj" = (
@@ -6447,7 +6447,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/green,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor/dark,
/area/tdome/tdome1)
"vT" = (
@@ -6570,7 +6570,7 @@
/obj/item/clothing/suit/armor/tdome/green,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor/dark,
/area/tdome/tdome1)
"xw" = (
@@ -9484,7 +9484,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/red,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor/dark,
/area/tdome/tdome1)
"Uk" = (
diff --git a/maps/sectors/piratebase_192/levels/piratebase_192.dmm b/maps/sectors/piratebase_192/levels/piratebase_192.dmm
index adb8f29930a7..59da0041ba63 100644
--- a/maps/sectors/piratebase_192/levels/piratebase_192.dmm
+++ b/maps/sectors/piratebase_192/levels/piratebase_192.dmm
@@ -5314,14 +5314,14 @@
/area/piratebase/captain)
"Wc" = (
/obj/structure/table/rack/shelf/steel,
-/obj/item/melee/energy/sword/pirate{
+/obj/item/melee/transforming/energy/sword/cutlass{
pixel_x = 5
},
-/obj/item/melee/energy/sword/pirate{
+/obj/item/melee/transforming/energy/sword/cutlass{
pixel_x = 5;
pixel_y = 5
},
-/obj/item/melee/energy/sword/pirate{
+/obj/item/melee/transforming/energy/sword/cutlass{
pixel_x = 5;
pixel_y = 10
},
diff --git a/maps/sectors/tradeport_140/levels/tradeport_140.dmm b/maps/sectors/tradeport_140/levels/tradeport_140.dmm
index 2684410d2157..04dd84918912 100644
--- a/maps/sectors/tradeport_140/levels/tradeport_140.dmm
+++ b/maps/sectors/tradeport_140/levels/tradeport_140.dmm
@@ -471,7 +471,7 @@
req_access = list(160)
},
/obj/structure/table/marble,
-/obj/item/melee/energy/hfmachete,
+/obj/item/melee/transforming/hfmachete,
/turf/simulated/floor/carpet/bcarpet,
/area/tradeport/cyndishow)
"bU" = (
diff --git a/maps/sectors/tradeport_192/levels/tradeport_192.dmm b/maps/sectors/tradeport_192/levels/tradeport_192.dmm
index fb348e5f8447..14ff915c4c12 100644
--- a/maps/sectors/tradeport_192/levels/tradeport_192.dmm
+++ b/maps/sectors/tradeport_192/levels/tradeport_192.dmm
@@ -3061,7 +3061,7 @@
req_access = list(160)
},
/obj/structure/table/marble,
-/obj/item/melee/energy/hfmachete,
+/obj/item/melee/transforming/hfmachete,
/turf/simulated/floor/carpet/bcarpet,
/area/tradeport/cyndishow)
"kD" = (
diff --git a/maps/templates/admin/dhael_centcom.dmm b/maps/templates/admin/dhael_centcom.dmm
index 247c12e9fe5e..91ea585fafb8 100644
--- a/maps/templates/admin/dhael_centcom.dmm
+++ b/maps/templates/admin/dhael_centcom.dmm
@@ -229,17 +229,17 @@
/area/centcom/specops)
"ay" = (
/obj/structure/table/rack,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/obj/effect/floor_decal/industrial/outline/yellow,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/obj/effect/floor_decal/industrial/outline/yellow,
/obj/structure/window/reinforced{
dir = 1
@@ -10688,17 +10688,17 @@
/area/centcom/specops)
"GH" = (
/obj/structure/table/rack,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/obj/effect/floor_decal/industrial/outline/yellow,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -11332,7 +11332,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/red,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -12059,7 +12059,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/green,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -12076,7 +12076,7 @@
/obj/item/clothing/suit/armor/tdome/green,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -13315,7 +13315,7 @@
/obj/item/clothing/suit/armor/tdome/red,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark"
},
diff --git a/maps/templates/admin/ert_base.dmm b/maps/templates/admin/ert_base.dmm
index 96879d65dce4..5844bc5022bf 100644
--- a/maps/templates/admin/ert_base.dmm
+++ b/maps/templates/admin/ert_base.dmm
@@ -1357,16 +1357,16 @@
/area/shuttle/specops/centcom)
"cL" = (
/obj/structure/table/rack/steel,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/turf/simulated/shuttle/floor/black,
/area/shuttle/specops/centcom)
"cM" = (
diff --git a/maps/templates/admin/kk_mercship.dmm b/maps/templates/admin/kk_mercship.dmm
index 16b08549308f..f889b1fa0f65 100644
--- a/maps/templates/admin/kk_mercship.dmm
+++ b/maps/templates/admin/kk_mercship.dmm
@@ -9345,18 +9345,18 @@
/turf/simulated/floor/tiled/techmaint,
/area/ship/manta/bridge)
"VF" = (
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/obj/structure/table/steel_reinforced,
/obj/machinery/atmospherics/component/unary/vent_scrubber/on{
dir = 4
diff --git a/maps/templates/admin/mercbase.dmm b/maps/templates/admin/mercbase.dmm
index 59eb33bbafb5..bc8c94c333a0 100644
--- a/maps/templates/admin/mercbase.dmm
+++ b/maps/templates/admin/mercbase.dmm
@@ -489,18 +489,18 @@
/area/antag/antag_base)
"aP" = (
/obj/structure/table/rack,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/obj/machinery/recharger/wallcharger{
pixel_x = 5;
pixel_y = 32
diff --git a/maps/templates/admin/skipjack.dmm b/maps/templates/admin/skipjack.dmm
index caad2f925597..e757e7953c50 100644
--- a/maps/templates/admin/skipjack.dmm
+++ b/maps/templates/admin/skipjack.dmm
@@ -1099,7 +1099,7 @@
/area/shuttle/skipjack)
"cK" = (
/obj/structure/table/rack,
-/obj/item/melee/energy/sword/pirate,
+/obj/item/melee/transforming/energy/sword/cutlass,
/obj/item/clothing/suit/space/pirate,
/obj/item/clothing/suit/space/pirate,
/obj/item/tank/oxygen,
diff --git a/maps/templates/admin/thunderdome.dmm b/maps/templates/admin/thunderdome.dmm
index 31f85fdaa411..8cb99cbba953 100644
--- a/maps/templates/admin/thunderdome.dmm
+++ b/maps/templates/admin/thunderdome.dmm
@@ -26,7 +26,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/red,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor{
icon_state = "dark";
dir = 5
@@ -43,7 +43,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/green,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor{
icon_state = "dark";
dir = 5
@@ -77,7 +77,7 @@
/obj/item/clothing/suit/armor/tdome/red,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark";
dir = 5
@@ -128,7 +128,7 @@
/obj/item/clothing/suit/armor/tdome/green,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark";
dir = 5
diff --git a/maps/templates/shuttles/overmaps/generic/cruiser.dmm b/maps/templates/shuttles/overmaps/generic/cruiser.dmm
index 5472b593c6ca..071f0063aa0b 100644
--- a/maps/templates/shuttles/overmaps/generic/cruiser.dmm
+++ b/maps/templates/shuttles/overmaps/generic/cruiser.dmm
@@ -5404,16 +5404,16 @@
/obj/item/melee/baton/loaded,
/obj/item/melee/baton/loaded,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/obj/item/material/knife/tacknife/survival,
/obj/item/material/knife/tacknife/survival,
/obj/item/material/knife/tacknife/survival,
diff --git a/maps/templates/shuttles/overmaps/generic/shelter_6.dmm b/maps/templates/shuttles/overmaps/generic/shelter_6.dmm
index 242fc42c3824..e0a277ddfda5 100644
--- a/maps/templates/shuttles/overmaps/generic/shelter_6.dmm
+++ b/maps/templates/shuttles/overmaps/generic/shelter_6.dmm
@@ -65,14 +65,14 @@
/obj/item/material/knife/machete,
/obj/item/material/knife/machete,
/obj/item/material/knife/machete,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/obj/item/melee/baton/loaded,
/obj/item/melee/baton/loaded,
/obj/item/melee/baton/loaded,
diff --git a/maps/triumph/levels/flagship.dmm b/maps/triumph/levels/flagship.dmm
index e79f15c02043..9f62a245cd19 100644
--- a/maps/triumph/levels/flagship.dmm
+++ b/maps/triumph/levels/flagship.dmm
@@ -1399,7 +1399,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/red,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -1851,17 +1851,17 @@
/area/centcom/specops)
"fK" = (
/obj/structure/table/rack,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/obj/effect/floor_decal/industrial/outline/yellow,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/obj/effect/floor_decal/industrial/outline/yellow,
/obj/structure/window/reinforced{
dir = 1
@@ -4455,17 +4455,17 @@
/area/centcom/security)
"nz" = (
/obj/structure/table/rack,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
-/obj/item/shield/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
+/obj/item/shield/transforming/energy,
/obj/effect/floor_decal/industrial/outline/yellow,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -7634,7 +7634,7 @@
/obj/item/clothing/suit/armor/tdome/red,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -8273,7 +8273,7 @@
/obj/structure/table/rack,
/obj/item/clothing/under/color/green,
/obj/item/clothing/shoes/brown,
-/obj/item/melee/energy/axe,
+/obj/item/melee/transforming/energy/axe,
/turf/unsimulated/floor{
icon_state = "dark"
},
@@ -10908,7 +10908,7 @@
/obj/item/clothing/suit/armor/tdome/green,
/obj/item/clothing/head/helmet/thunderdome,
/obj/item/melee/baton/loaded,
-/obj/item/melee/energy/sword,
+/obj/item/melee/transforming/energy/sword,
/turf/unsimulated/floor{
icon_state = "dark"
},
diff --git a/sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg b/sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg
new file mode 100644
index 000000000000..10ed875b8d0f
Binary files /dev/null and b/sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg differ
diff --git a/sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg b/sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg
new file mode 100644
index 000000000000..b7ae0e247d01
Binary files /dev/null and b/sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg differ
diff --git a/sound/soundbytes/effects/combat/block-metal-on-wood-1.ogg b/sound/soundbytes/effects/combat/block-metal-on-wood-1.ogg
new file mode 100644
index 000000000000..cf018635f375
Binary files /dev/null and b/sound/soundbytes/effects/combat/block-metal-on-wood-1.ogg differ
diff --git a/sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg b/sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg
new file mode 100644
index 000000000000..25694ec55785
Binary files /dev/null and b/sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg differ
diff --git a/sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg b/sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg
new file mode 100644
index 000000000000..d2829db00c37
Binary files /dev/null and b/sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg differ
diff --git a/sound/soundbytes/effects/combat/block-wood-on-wood-2.ogg b/sound/soundbytes/effects/combat/block-wood-on-wood-2.ogg
new file mode 100644
index 000000000000..816db1f7171c
Binary files /dev/null and b/sound/soundbytes/effects/combat/block-wood-on-wood-2.ogg differ
diff --git a/sound/soundbytes/effects/combat/parry-cycle.ogg b/sound/soundbytes/effects/combat/parry-cycle.ogg
new file mode 100644
index 000000000000..3429031bd94d
Binary files /dev/null and b/sound/soundbytes/effects/combat/parry-cycle.ogg differ
diff --git a/sound/soundbytes/effects/combat/parry-metal1.ogg b/sound/soundbytes/effects/combat/parry-metal1.ogg
new file mode 100644
index 000000000000..c0f98249cd3b
Binary files /dev/null and b/sound/soundbytes/effects/combat/parry-metal1.ogg differ
diff --git a/sound/soundbytes/effects/combat/parry-metal2.ogg b/sound/soundbytes/effects/combat/parry-metal2.ogg
new file mode 100644
index 000000000000..58c1233ceae4
Binary files /dev/null and b/sound/soundbytes/effects/combat/parry-metal2.ogg differ
diff --git a/sound/soundbytes/effects/combat/parry-wood1.ogg b/sound/soundbytes/effects/combat/parry-wood1.ogg
new file mode 100644
index 000000000000..a01f3dbe0161
Binary files /dev/null and b/sound/soundbytes/effects/combat/parry-wood1.ogg differ
diff --git a/sound/soundbytes/effects/combat/parry-wood2.ogg b/sound/soundbytes/effects/combat/parry-wood2.ogg
new file mode 100644
index 000000000000..398a59e24863
Binary files /dev/null and b/sound/soundbytes/effects/combat/parry-wood2.ogg differ