diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 1c9caafee9ed2..b03d811ca3486 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -97,6 +97,10 @@
#/code/game/area/ @san7890
#/icons/area/ @san7890
+# SmArtKar
+
+/code/modules/projectiles/projectile.dm @SmArtKar
+
# stylemistake
#/code/__DEFINES/chat.dm @stylemistake
diff --git a/_maps/map_files/Graveyard/Graveyard.dmm b/_maps/map_files/Graveyard/Graveyard.dmm
index 57419af58188b..d591e489ecd64 100644
--- a/_maps/map_files/Graveyard/Graveyard.dmm
+++ b/_maps/map_files/Graveyard/Graveyard.dmm
@@ -5204,7 +5204,7 @@
pixel_x = 9;
pixel_y = 9
},
-/obj/item/ammo_casing/caseless/rocket{
+/obj/item/ammo_casing/rocket{
desc = "Your grandpappy brought this home after the war. You're pretty sure it's a dud.";
name = "Dud Rocket";
pixel_x = 4;
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index d8583a113a04f..861151f9178f6 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -3971,7 +3971,7 @@
pixel_x = -3;
pixel_y = 10
},
-/obj/item/ammo_casing/caseless/rocket{
+/obj/item/ammo_casing/rocket{
desc = "Your grandpappy brought this home after the war. You're pretty sure it's a dud.";
name = "Dud Rocket";
pixel_x = -4;
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index 218a3caa7944a..12d3613a8ab54 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -22169,7 +22169,7 @@
/obj/item/gun/energy/tesla_cannon,
/obj/item/gun/energy/laser/thermal/inferno,
/obj/item/gun/energy/laser/thermal/cryo,
-/obj/item/gun/energy/beam_rifle,
+/obj/item/gun/energy/event_horizon,
/obj/item/gun/ballistic/rifle/sniper_rifle,
/turf/open/floor/iron/dark/herringbone,
/area/centcom/central_command_areas/adminroom)
@@ -25119,18 +25119,18 @@
pixel_x = 32
},
/obj/machinery/light/small/directional/east,
-/obj/item/ammo_casing/caseless/rocket/heap,
+/obj/item/ammo_casing/rocket/heap,
/obj/structure/closet/cabinet,
-/obj/item/ammo_casing/caseless/rocket,
-/obj/item/ammo_casing/caseless/rocket,
+/obj/item/ammo_casing/rocket,
+/obj/item/ammo_casing/rocket,
/obj/item/gun/ballistic/rocketlauncher/nobackblast{
pin = /obj/item/firing_pin
},
-/obj/item/ammo_casing/caseless/rocket,
-/obj/item/ammo_casing/caseless/rocket,
-/obj/item/ammo_casing/caseless/rocket/heap,
-/obj/item/ammo_casing/caseless/rocket/weak,
-/obj/item/ammo_casing/caseless/rocket/weak,
+/obj/item/ammo_casing/rocket,
+/obj/item/ammo_casing/rocket,
+/obj/item/ammo_casing/rocket/heap,
+/obj/item/ammo_casing/rocket/weak,
+/obj/item/ammo_casing/rocket/weak,
/obj/item/clothing/under/rank/centcom/official,
/obj/item/clothing/glasses/sunglasses,
/obj/item/radio/headset/headset_cent/alt,
@@ -25702,19 +25702,19 @@
pixel_x = -8;
pixel_y = 20
},
-/obj/item/ammo_casing/caseless/rocket{
+/obj/item/ammo_casing/rocket{
pixel_x = 0;
pixel_y = 2
},
-/obj/item/ammo_casing/caseless/rocket{
+/obj/item/ammo_casing/rocket{
pixel_x = 4;
pixel_y = 2
},
-/obj/item/ammo_casing/caseless/rocket{
+/obj/item/ammo_casing/rocket{
pixel_x = 9;
pixel_y = 2
},
-/obj/item/ammo_casing/caseless/rocket/heap{
+/obj/item/ammo_casing/rocket/heap{
pixel_x = -6;
pixel_y = 1
},
@@ -26579,7 +26579,7 @@
/obj/structure/guncase,
/obj/item/gun/energy/xray,
/obj/item/gun/energy/ionrifle,
-/obj/item/gun/energy/beam_rifle,
+/obj/item/gun/energy/event_horizon,
/obj/item/gun/ballistic/rifle/sniper_rifle,
/turf/open/floor/iron/dark/herringbone,
/area/centcom/central_command_areas/adminroom)
@@ -28254,7 +28254,7 @@
/obj/structure/guncase,
/obj/item/gun/energy/wormhole_projector/core_inserted,
/obj/item/gun/energy/ionrifle/carbine,
-/obj/item/gun/energy/beam_rifle,
+/obj/item/gun/energy/event_horizon,
/obj/item/gun/ballistic/rifle/sniper_rifle,
/turf/open/floor/iron/dark/herringbone,
/area/centcom/central_command_areas/adminroom)
diff --git a/_maps/minigame/Deathmatch/arena_station.dmm b/_maps/minigame/Deathmatch/arena_station.dmm
index 71fc174661474..2298745988170 100644
--- a/_maps/minigame/Deathmatch/arena_station.dmm
+++ b/_maps/minigame/Deathmatch/arena_station.dmm
@@ -917,7 +917,7 @@
/area/deathmatch/fullbright)
"LY" = (
/obj/structure/closet/secure_closet,
-/obj/item/gun/energy/beam_rifle,
+/obj/item/gun/energy/xray,
/turf/open/indestructible/vault,
/area/deathmatch/fullbright)
"Mc" = (
diff --git a/_maps/shuttles/emergency_cruise.dmm b/_maps/shuttles/emergency_cruise.dmm
index f4359a33d2bd0..9e62e89a6cf09 100644
--- a/_maps/shuttles/emergency_cruise.dmm
+++ b/_maps/shuttles/emergency_cruise.dmm
@@ -2479,7 +2479,7 @@
},
/obj/structure/table/reinforced/plastitaniumglass,
/obj/effect/turf_decal/tile/dark_blue/opposingcorners,
-/obj/item/ammo_casing/caseless/rocket/heap,
+/obj/item/ammo_casing/rocket/heap,
/turf/open/floor/iron/dark/textured_large,
/area/shuttle/escape/luxury)
"Uo" = (
diff --git a/_maps/virtual_domains/xeno_nest.dmm b/_maps/virtual_domains/xeno_nest.dmm
index b4bf76b60980a..9c4897bfbdf92 100644
--- a/_maps/virtual_domains/xeno_nest.dmm
+++ b/_maps/virtual_domains/xeno_nest.dmm
@@ -133,7 +133,7 @@
/area/ruin/space/has_grav/powered/virtual_domain)
"F" = (
/obj/structure/table/greyscale,
-/obj/item/gun/energy/beam_rifle,
+/obj/item/gun/energy/xray,
/obj/item/gun/energy/laser{
pixel_x = 4;
pixel_y = -6
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 2c8d2dedb6196..0a0337c1ae6f2 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -178,37 +178,11 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
//Combat object defines
-//Embedded objects
-///Chance for embedded objects to cause pain (damage user)
-#define EMBEDDED_PAIN_CHANCE 15
-///Chance for embedded object to fall out (causing pain but removing the object)
-#define EMBEDDED_ITEM_FALLOUT 5
-///Chance for an object to embed into somebody when thrown
-#define EMBED_CHANCE 45
-///Coefficient of multiplication for the damage the item does while embedded (this*item.w_class)
-#define EMBEDDED_PAIN_MULTIPLIER 2
-///Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class)
-#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4
-///The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1)
+/// The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1)
#define EMBED_THROWSPEED_THRESHOLD 4
-///Coefficient of multiplication for the damage the item does when it falls out or is removed without a surgery (this*item.w_class)
-#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 6
-///A Time in ticks, total removal time = (this*item.w_class)
-#define EMBEDDED_UNSAFE_REMOVAL_TIME 30
-///Chance for embedded objects to cause pain every time they move (jostle)
-#define EMBEDDED_JOSTLE_CHANCE 5
-///Coefficient of multiplication for the damage the item does while
-#define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1
-///This percentage of all pain will be dealt as stam damage rather than brute (0-1)
-#define EMBEDDED_PAIN_STAM_PCT 0.0
-///For thrown weapons, every extra speed it's thrown at above its normal throwspeed will add this to the embed chance
+/// For thrown embedding weapons, every extra speed it's thrown at above its normal throwspeed will add this to the embed chance
#define EMBED_CHANCE_SPEED_BONUS 10
-#define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE)
-#define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1)
-#define EMBED_POINTY list("ignore_throwspeed_threshold" = TRUE)
-#define EMBED_POINTY_SUPERIOR list("embed_chance" = 100, "ignore_throwspeed_threshold" = TRUE)
-
//Gun weapon weight
#define WEAPON_LIGHT 1
#define WEAPON_MEDIUM 2
@@ -248,10 +222,6 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
#define SUPPRESSED_QUIET 1 ///standard suppressed
#define SUPPRESSED_VERY 2 /// no message
-//Projectile Reflect
-#define REFLECT_NORMAL (1<<0)
-#define REFLECT_FAKEPROJECTILE (1<<1)
-
//His Grace.
#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep.
#define HIS_GRACE_PECKISH 20 //Slightly hungry.
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index ddd05e174b63d..b32d0d586d914 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -28,6 +28,7 @@
#define COMPONENT_MOVABLE_IMPACT_NEVERMIND (1<<1) //return true if you destroyed whatever it was you're impacting and there won't be anything for hitby() to run on
///from base of mob/living/hitby(): (mob/living/target, hit_zone, blocked, datum/thrownthing/throwingdatum)
#define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone"
+ #define MOVABLE_IMPACT_ZONE_OVERRIDE (1<<0)
///from /atom/movable/proc/buckle_mob(): (mob/living/M, force, check_loc, buckle_mob_flags)
#define COMSIG_MOVABLE_PREBUCKLE "prebuckle" // this is the last chance to interrupt and block a buckle before it finishes
#define COMPONENT_BLOCK_BUCKLE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_item.dm b/code/__DEFINES/dcs/signals/signals_item.dm
index 1e9acff11e110..af174101c36da 100644
--- a/code/__DEFINES/dcs/signals/signals_item.dm
+++ b/code/__DEFINES/dcs/signals/signals_item.dm
@@ -88,8 +88,6 @@
#define COMSIG_TOOL_IN_USE "tool_in_use"
///from base of [/obj/item/proc/tool_start_check]: (mob/living/user)
#define COMSIG_TOOL_START_USE "tool_start_use"
-///from [/obj/item/proc/disableEmbedding]:
-#define COMSIG_ITEM_DISABLE_EMBED "item_disable_embed"
///from [/obj/effect/mine/proc/triggermine]:
#define COMSIG_MINE_TRIGGERED "minegoboom"
///from [/obj/structure/closet/supplypod/proc/preOpen]:
@@ -174,12 +172,15 @@
#define COMSIG_GUN_CHAMBER_PROCESSED "gun_chamber_processed"
///called in /obj/item/gun/ballistic/process_chamber (casing)
#define COMSIG_CASING_EJECTED "casing_ejected"
+///called in /obj/item/gun/ballistic/sawoff(mob/user, obj/item/saw, handle_modifications) : (mob/user)
+#define COMSIG_GUN_BEING_SAWNOFF "gun_being_sawnoff"
+ #define COMPONENT_CANCEL_SAWING_OFF (1<<0)
+#define COMSIG_GUN_SAWN_OFF "gun_sawn_off"
+
///sent to targets during the process_hit proc of projectiles
#define COMSIG_FIRE_CASING "fire_casing"
///from the base of /obj/item/ammo_casing/ready_proj() : (atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
#define COMSIG_CASING_READY_PROJECTILE "casing_ready_projectile"
-///sent to the projectile after an item is spawned by the projectile_drop element: (new_item)
-#define COMSIG_PROJECTILE_ON_SPAWN_DROP "projectile_on_spawn_drop"
// Jetpack things
// Please kill me
@@ -210,10 +211,7 @@
///called in /obj/item/gun/process_fire (user, target, params, zone_override)
#define COMSIG_GRENADE_ARMED "grenade_armed"
-///from [/obj/item/proc/tryEmbed] sent when trying to force an embed (mainly for projectiles and eating glass)
-#define COMSIG_EMBED_TRY_FORCE "item_try_embed"
- #define COMPONENT_EMBED_SUCCESS (1<<1)
-// FROM [/obj/item/proc/updateEmbedding] sent when an item's embedding properties are changed : ()
+// FROM [/obj/item/proc/set_embed] sent when an item's embedding properties are changed : ()
#define COMSIG_ITEM_EMBEDDING_UPDATE "item_embedding_update"
#define COMSIG_ITEM_ATTACK "item_attack"
@@ -234,12 +232,12 @@
#define COMSIG_ITEM_ATTACK_SECONDARY "item_attack_secondary"
///from base of [obj/item/attack()]: (atom/target, mob/user, proximity_flag, click_parameters)
#define COMSIG_ITEM_AFTERATTACK "item_afterattack"
-///from base of obj/item/embedded(): (atom/target, obj/item/bodypart/part)
+///from base of datum/embedding/proc/embed_into(): (mob/living/carbon/victim, obj/item/bodypart/limb)
#define COMSIG_ITEM_EMBEDDED "item_embedded"
-///from base of datum/component/embedded/safeRemove(): (mob/living/carbon/victim)
+///from base of datum/embedding/proc/remove_embedding(): (mob/living/carbon/victim, obj/item/bodypart/limb)
#define COMSIG_ITEM_UNEMBEDDED "item_unembedded"
-/// from base of obj/item/failedEmbed()
-#define COMSIG_ITEM_FAILED_EMBED "item_failed_embed"
+///from base of datum/embedding/proc/failed_embed(): (mob/living/carbon/victim, hit_zone)
+#define COMSIG_ITEM_FAILED_EMBED "item_unembedded"
/// from base of datum/element/disarm_attack/secondary_attack(), used to prevent shoving: (victim, user, send_message)
#define COMSIG_ITEM_CAN_DISARM_ATTACK "item_pre_disarm_attack"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
index 7db85deec87d5..8deae170a1df4 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -77,10 +77,6 @@
///from /mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop, silent)
#define COMSIG_CARBON_UNEQUIP_SHOECOVER "carbon_unequip_shoecover"
#define COMSIG_CARBON_EQUIP_SHOECOVER "carbon_equip_shoecover"
-///defined twice, in carbon and human's topics, fired when interacting with a valid embedded_object to pull it out (mob/living/carbon/target, /obj/item, /obj/item/bodypart/L)
-#define COMSIG_CARBON_EMBED_RIP "item_embed_start_rip"
-///called when removing a given item from a mob, from mob/living/carbon/remove_embedded_object(mob/living/carbon/target, /obj/item)
-#define COMSIG_CARBON_EMBED_REMOVAL "item_embed_remove_safe"
///Called when someone attempts to cuff a carbon
#define COMSIG_CARBON_CUFF_ATTEMPTED "carbon_attempt_cuff"
#define COMSIG_CARBON_CUFF_PREVENT (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 397e5db191fe3..a8c4d37940f78 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -163,9 +163,9 @@
// /obj/projectile signals (sent to the firer)
-///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb)
+///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb, blocked, pierce_hit)
#define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit"
-///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb)
+///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb, blocked, pierce_hit)
#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit"
///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target)
#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire"
@@ -176,18 +176,25 @@
///sent to targets during the process_hit proc of projectiles
#define COMSIG_PROJECTILE_PREHIT "com_proj_prehit"
#define PROJECTILE_INTERRUPT_HIT (1<<0)
-///from /obj/projectile/pixel_move(): ()
-#define COMSIG_PROJECTILE_PIXEL_STEP "projectile_pixel_step"
+ #define PROJECTILE_INTERRUPT_HIT_PHASE (1<<1)
+///from /obj/projectile/process_movement(): ()
+#define COMSIG_PROJECTILE_MOVE_PROCESS_STEP "projectile_move_process_step"
///sent to self during the process_hit proc of projectiles
#define COMSIG_PROJECTILE_SELF_PREHIT "com_proj_prehit"
-///from the base of /obj/projectile/Range(): ()
+///from the base of /obj/projectile/reduce_range(): ()
#define COMSIG_PROJECTILE_RANGE "projectile_range"
///from the base of /obj/projectile/on_range(): ()
#define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out"
///from the base of /obj/projectile/process(): ()
#define COMSIG_PROJECTILE_BEFORE_MOVE "projectile_before_move"
-///sent to targets during the process_hit proc of projectiles
-#define COMSIG_PELLET_CLOUD_INIT "pellet_cloud_init"
+
+///sent to the projectile after an item is spawned by the projectile_drop element: (new_casing)
+#define COMSIG_PROJECTILE_ON_SPAWN_DROP "projectile_on_spawn_drop"
+
+///sent to the projectile when spawning the item (shrapnel) that may be embedded: (new_item, victim)
+#define COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED "projectile_on_spawn_embedded"
+///sent to the projectile when successfully embedding into something: (new_item, victim)
+#define COMSIG_PROJECTILE_ON_EMBEDDED "projectile_on_embedded"
// /obj/vehicle/sealed/car/vim signals
@@ -219,9 +226,6 @@
/// Prevents click from happening.
#define COMPONENT_CANCEL_EQUIPMENT_CLICK (1<<0)
-/// from /obj/structure/sign/poster/trap_succeeded() : (mob/user)
-#define COMSIG_POSTER_TRAP_SUCCEED "poster_trap_succeed"
-
/// from /obj/machinery/mineral/ore_redemption/pickup_item when it successfully picks something up
#define COMSIG_ORM_COLLECTED_ORE "orm_collected_ore"
diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm
index 11beeef1b3dc9..d58a151498f02 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -32,6 +32,9 @@
/// Gets the sign of x, returns -1 if negative, 0 if 0, 1 if positive
#define SIGN(x) ( ((x) > 0) - ((x) < 0) )
+/// Returns the integer closest to 0 from a division
+#define SIGNED_FLOOR_DIVISION(x, y) (SIGN(x) * FLOOR(abs(x) / y, 1))
+
#define CEILING(x, y) ( -round(-(x) / (y)) * (y) )
#define ROUND_UP(x) ( -round(-(x)))
diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm
index 9b8d6c3918456..5472332845921 100644
--- a/code/__DEFINES/projectiles.dm
+++ b/code/__DEFINES/projectiles.dm
@@ -106,12 +106,27 @@
#define RETURN_PRECISE_POSITION(A) new /datum/position(A)
#define RETURN_PRECISE_POINT(A) new /datum/point(A)
-#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED))
-#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT))
-
///The self charging rate of energy guns that magically recharge themselves, in watts.
#define STANDARD_ENERGY_GUN_SELF_CHARGE_RATE (0.05 * STANDARD_CELL_CHARGE)
/// Macro to turn a number of laser shots into an energy cost, based on the above define
/// e.g. LASER_SHOTS(12, STANDARD_CELL_CHARGE) means 12 shots
#define LASER_SHOTS(X, MAX_CHARGE) (((100 * MAX_CHARGE) - ((100 * MAX_CHARGE) % X)) / (100 * X)) // I wish I could just use round, but it can't be used in datum members
+
+/// How far do the projectile hits the prone mob
+#define MAX_RANGE_HIT_PRONE_TARGETS 10
+
+/// Queued for impact deletion (simple qdel)
+#define PROJECTILE_IMPACT_DELETE "impact_delete"
+/// Queued for range deletion (on_range call)
+#define PROJECTILE_RANGE_DELETE "range_delete"
+
+/// Projectile either hasn't impacted anything, or pierced through the target
+#define PROJECTILE_IMPACT_PASSED "impact_passed"
+/// Projectile has been "deleted" before bullet_act call has occured
+#define PROJECTILE_IMPACT_INTERRUPTED "impact_interrupted"
+/// Projectile has successfully impacted something and is scheduled for deletion
+#define PROJECTILE_IMPACT_SUCCESSFUL "impact_successful"
+
+/// For how long projectile tracers linger
+#define PROJECTILE_TRACER_DURATION 0.3 SECONDS
diff --git a/code/__DEFINES/research/anomalies.dm b/code/__DEFINES/research/anomalies.dm
index 0c9ff52270ae7..0ee5f48362355 100644
--- a/code/__DEFINES/research/anomalies.dm
+++ b/code/__DEFINES/research/anomalies.dm
@@ -2,7 +2,7 @@
#define MAX_CORES_BLUESPACE 8
#define MAX_CORES_GRAVITATIONAL 8
#define MAX_CORES_FLUX 8
-#define MAX_CORES_VORTEX 8
+#define MAX_CORES_VORTEX 1
#define MAX_CORES_PYRO 8
#define MAX_CORES_HALLUCINATION 8
#define MAX_CORES_BIOSCRAMBLER 8
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 808bc194a0e49..ea5e085a712c7 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -1341,6 +1341,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
///Trait which allows mobs to parry mining mob projectiles
#define TRAIT_MINING_PARRYING "mining_parrying"
+/// This atom has a tether attached to it
+#define TRAIT_TETHER_ATTACHED "tether_attached"
+
/**
*
* This trait is used in some interactions very high in the interaction chain to allow
diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
index bd0e61b5483c9..fc5d7dd64d05a 100644
--- a/code/__HELPERS/maths.dm
+++ b/code/__HELPERS/maths.dm
@@ -219,6 +219,23 @@
if(sign == -1)
return min(new_value, threshold * -1)
+/// Takes two values x and y, and returns 1/((1/x) + y)
+/// Useful for providing an additive modifier to a value that is used as a divisor
+/proc/reciprocal_add(x, y)
+ return 1/((1/x)+y)
+
+/// Returns a text string containing N prefixed with a series of zeros with length equal to max_zeros minus log(10, N), rounded down.
+/proc/prefix_zeros_to_number(number, max_zeros)
+ var/zeros = ""
+ var/how_many_zeros = max_zeros - round(log(10, number))
+ for(var/zero in 1 to how_many_zeros)
+ zeros += "0"
+ return "[zeros][number]"
+
+/// 180s an angle
+/proc/reverse_angle(angle)
+ return (angle + 180) % 360
+
/proc/poisson_noise(width, height, radius, seed)
. = list()
if(!seed)
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm
index 1aaea870986b5..0a61ea86eb77e 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices.dm
@@ -40,8 +40,7 @@
decompose_matrix.rotation = arctan(cossine, sine) * flip_sign
/matrix/proc/TurnTo(old_angle, new_angle)
- . = new_angle - old_angle
- Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
+ return Turn(new_angle - old_angle) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
if(!segments)
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index d263344b15a3f..284a8f5d79e7f 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -29,6 +29,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
/* "TRAIT_COMMISSIONED" = TRAIT_COMMISSIONED, */
/* "TRAIT_LIGHTING_DEBUGGED" = TRAIT_LIGHTING_DEBUGGED, */
/* "TRAIT_UNHITTABLE_BY_PROJECTILES" = TRAIT_UNHITTABLE_BY_PROJECTILES, */
+ "TRAIT_TETHER_ATTACHED" = TRAIT_TETHER_ATTACHED,
),
/atom/movable = list(
"TRAIT_ACTIVE_STORAGE" = TRAIT_ACTIVE_STORAGE,
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 94d18de763a23..1257e4424e6b2 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -307,8 +307,7 @@
return
var/mob/living/carbon/carbon_owner = owner
-
- return carbon_owner.help_shake_act(carbon_owner)
+ return carbon_owner.check_self_for_injuries()
/atom/movable/screen/alert/negative
name = "Negative Gravity"
diff --git a/code/controllers/subsystem/processing/projectiles.dm b/code/controllers/subsystem/processing/projectiles.dm
index 2ed43a708ef83..f6238a92df35a 100644
--- a/code/controllers/subsystem/processing/projectiles.dm
+++ b/code/controllers/subsystem/processing/projectiles.dm
@@ -3,22 +3,19 @@ PROCESSING_SUBSYSTEM_DEF(projectiles)
wait = 0
priority = FIRE_PRIORITY_PROJECTILES
stat_tag = "PP"
- flags = SS_NO_INIT | SS_TICKER | SS_HIBERNATE
- var/global_max_tick_moves = 10
- var/global_pixel_speed = 2
- var/global_iterations_per_move = 16
-
-/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed)
- global_pixel_speed = new_speed
- for(var/i in processing)
- var/obj/projectile/P = i
- if(istype(P)) //there's non projectiles on this too.
- P.set_pixel_speed(new_speed)
-
-/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value)
- switch(var_name)
- if(NAMEOF(src, global_pixel_speed))
- set_pixel_speed(var_value)
- return TRUE
- else
- return ..()
+ flags = SS_NO_INIT|SS_TICKER
+ /*
+ * Maximum amount of pixels a projectile can pass per tick *unless* its a hitscan projectile.
+ * This prevents projectiles from turning into essentially hitscans if SSprojectiles starts chugging
+ * and projectiles accumulate a bunch of overtime they try to process next tick to fly through half the map.
+ * Shouldn't really be increased past 5 tiles per tick because this maxes out at 100 FPS (recommended as of now)
+ * and making a projectile faster than that will make it look jumpy because it'll be passing inconsistent
+ * amounts of pixels per tick.
+ */
+ var/max_pixels_per_tick = ICON_SIZE_ALL * 5
+ /*
+ * How many pixels a projectile with a speed value of 1 passes in a tick. Currently all speed values
+ * assume that 1 speed = 1 tile per decisecond, but this is a variable so that admins/debuggers can edit
+ * in order to debug projectile behavior by evenly slowing or speeding all of them up.
+ */
+ var/pixels_per_decisecond = ICON_SIZE_ALL
diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm
index 2c824fc2f73ed..a41e1d84eed03 100644
--- a/code/datums/actions/cooldown_action.dm
+++ b/code/datums/actions/cooldown_action.dm
@@ -171,7 +171,8 @@
if(isnum(override_cooldown_time))
next_use_time = world.time + (override_cooldown_time * cooldown_multiplier)
else
- next_use_time = world.time + (cooldown_time * cooldown_multiplier)
+ next_use_time = world.time + cooldown_time
+ // Don't start a cooldown if we have a cooldown time of 0 seconds
if(next_use_time == world.time)
return
build_all_button_icons(UPDATE_BUTTON_STATUS)
diff --git a/code/datums/actions/mobs/create_legion_turrets.dm b/code/datums/actions/mobs/create_legion_turrets.dm
index d5a5bd7960d9b..ea4b9d1cb1e43 100644
--- a/code/datums/actions/mobs/create_legion_turrets.dm
+++ b/code/datums/actions/mobs/create_legion_turrets.dm
@@ -78,8 +78,7 @@
return
//Now we generate the tracer.
var/angle = get_angle(our_turf, target_turf)
- var/datum/point/vector/V = new(our_turf.x, our_turf.y, our_turf.z, 0, 0, angle)
- generate_tracer_between_points(V, V.return_vector_after_increments(6), /obj/effect/projectile/tracer/legion/tracer, 0, shot_delay, 0, 0, 0, null)
+ our_turf.Beam(target_turf, 'icons/effects/beam.dmi', "blood_light", time = shot_delay)
playsound(src, 'sound/machines/airlockopen.ogg', 100, TRUE)
addtimer(CALLBACK(src, PROC_REF(fire_beam), angle), shot_delay)
@@ -94,6 +93,7 @@
/// Used for the legion turret.
/obj/projectile/beam/legion
name = "blood pulse"
+ icon_state = null
hitsound = 'sound/magic/magic_missile.ogg'
damage = 19
range = 6
@@ -105,11 +105,6 @@
hitscan = TRUE
projectile_piercing = ALL
-/// Used for the legion turret tracer.
-/obj/effect/projectile/tracer/legion/tracer
- icon = 'icons/effects/beam.dmi'
- icon_state = "blood_light"
-
/// Used for the legion turret beam.
/obj/effect/projectile/tracer/legion
icon = 'icons/effects/beam.dmi'
diff --git a/code/datums/actions/mobs/ground_slam.dm b/code/datums/actions/mobs/ground_slam.dm
new file mode 100644
index 0000000000000..e00799196b589
--- /dev/null
+++ b/code/datums/actions/mobs/ground_slam.dm
@@ -0,0 +1,32 @@
+/datum/action/cooldown/mob_cooldown/ground_slam
+ name = "Ground Slam"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ desc = "Slams the ground sending out a shockwave around you."
+ cooldown_time = 10 SECONDS
+ /// The range of the slam
+ var/range = 5
+ /// The delay before the shockwave expands it's range
+ var/delay = 3
+ /// How far hit targets are thrown
+ var/throw_range = 8
+ /// Whether the target can move or not while the slam is occurring
+ var/can_move = FALSE
+
+/datum/action/cooldown/mob_cooldown/ground_slam/Activate(atom/target_atom)
+ disable_cooldown_actions()
+ RegisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move), override = TRUE)
+ do_slam(target_atom)
+ UnregisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE)
+ StartCooldown()
+ enable_cooldown_actions()
+ return TRUE
+
+/// Slams the ground around the source throwing back enemies caught nearby, delay is for the radius increase
+/datum/action/cooldown/mob_cooldown/ground_slam/proc/do_slam(atom/target)
+ wendigo_slam(owner, range, delay, throw_range)
+
+/datum/action/cooldown/mob_cooldown/ground_slam/proc/on_move(atom/source, atom/new_loc)
+ SIGNAL_HANDLER
+ if(!can_move)
+ return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm
index 62f08eee5d1da..91a0ff4c352e2 100644
--- a/code/datums/actions/mobs/projectileattack.dm
+++ b/code/datums/actions/mobs/projectileattack.dm
@@ -16,14 +16,23 @@
var/default_projectile_spread = 0
/// The multiplier to the projectiles speed (a value of 2 makes it twice as slow, 0.5 makes it twice as fast)
var/projectile_speed_multiplier = 1
+ /// Whether the target can move or not while the attack is occurring
+ var/can_move = TRUE
/datum/action/cooldown/mob_cooldown/projectile_attack/Activate(atom/target_atom)
disable_cooldown_actions()
+ RegisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move), override = TRUE)
attack_sequence(owner, target_atom)
+ UnregisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE)
StartCooldown()
enable_cooldown_actions()
return TRUE
+/datum/action/cooldown/mob_cooldown/projectile_attack/proc/on_move(atom/source, atom/new_loc)
+ SIGNAL_HANDLER
+ if(!can_move)
+ return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+
/datum/action/cooldown/mob_cooldown/projectile_attack/proc/attack_sequence(mob/living/firer, atom/target)
shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null)
@@ -40,7 +49,7 @@
if(!isnum(speed_multiplier))
speed_multiplier = projectile_speed_multiplier
our_projectile.speed *= speed_multiplier
- our_projectile.preparePixelProjectile(endloc, startloc, null, projectile_spread)
+ our_projectile.aim_projectile(endloc, startloc, null, projectile_spread)
our_projectile.firer = firer
if(target)
our_projectile.original = target
@@ -151,6 +160,25 @@
SLEEP_CHECK_DEATH(1.5 SECONDS, owner)
return ..()
+/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/wendigo
+ cooldown_time = 10 SECONDS
+ projectile_type = /obj/projectile/colossus/wendigo_shockwave/spiral
+ can_move = FALSE
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/wendigo/create_spiral_attack(mob/living/firer, atom/target, negative = pick(TRUE, FALSE))
+ wendigo_scream(firer)
+ var/shots_spiral = 40
+ var/angle_to_target = get_angle(firer, target)
+ var/spiral_direction = pick(-1, 1)
+ for(var/shot in 1 to shots_spiral)
+ var/shots_per_tick = 5 - enraged * 3
+ var/angle_change = (5 + enraged * shot / 6) * spiral_direction
+ for(var/count in 1 to shots_per_tick)
+ var/angle = angle_to_target + shot * angle_change + count * 360 / shots_per_tick
+ shoot_projectile(firer, target, angle, firer, null, null)
+ SLEEP_CHECK_DEATH(1, firer)
+ SLEEP_CHECK_DEATH(3 SECONDS, firer)
+
/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe
name = "All Directions"
button_icon = 'icons/effects/effects.dmi'
@@ -192,6 +220,12 @@
shoot_projectile(firer, target, null, firer, spread, null)
+/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/wendigo
+ cooldown_time = 10 SECONDS
+ projectile_type = /obj/projectile/colossus/wendigo_shockwave
+ shot_angles = list(-20, -10, 0, 10, 20)
+ projectile_speed_multiplier = 4
+
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/colossus
cooldown_time = 0.5 SECONDS
@@ -327,3 +361,54 @@
colossus.telegraph()
colossus.dir_shots.attack_sequence(firer, target)
SLEEP_CHECK_DEATH(1 SECONDS, firer)
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/alternating_circle
+ name = "Alternating Shots"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ desc = "Fires projectiles around you in an alternating fashion."
+ cooldown_time = 10 SECONDS
+ projectile_type = /obj/projectile/colossus/wendigo_shockwave
+ can_move = FALSE
+ var/enraged = FALSE
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/alternating_circle/attack_sequence(mob/living/firer, atom/target)
+ wendigo_scream(firer)
+ if(enraged)
+ projectile_speed_multiplier = 1
+ else
+ projectile_speed_multiplier = 1.5
+ var/shots_per = 24
+ for(var/shoot_times in 1 to 8)
+ var/offset = shoot_times % 2
+ for(var/shot in 1 to shots_per)
+ var/angle = shot * 360 / shots_per + (offset * 360 / shots_per) * 0.5
+ shoot_projectile(firer, target, angle, firer, null, null)
+ SLEEP_CHECK_DEATH(6 - enraged * 2, firer)
+ SLEEP_CHECK_DEATH(3 SECONDS, firer)
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/wave
+ name = "Wave Shots"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ desc = "Fires projectiles around you in a circular wave."
+ cooldown_time = 10 SECONDS
+ projectile_type = /obj/projectile/colossus/wendigo_shockwave/wave
+ can_move = FALSE
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/wave/attack_sequence(mob/living/firer, atom/target)
+ wendigo_scream(firer)
+ var/shots_per = 6
+ var/difference = 360 / shots_per
+ var/wave_direction = pick(-1, 1)
+ switch(wave_direction)
+ if(-1)
+ projectile_type = /obj/projectile/colossus/wendigo_shockwave/wave/alternate
+ if(1)
+ projectile_type = /obj/projectile/colossus/wendigo_shockwave/wave
+ for(var/shoot_times in 1 to 12)
+ for(var/shot in 1 to shots_per)
+ var/angle = shot * difference + shoot_times * 5 * wave_direction * -1
+ shoot_projectile(firer, target, angle, firer, null, null)
+ SLEEP_CHECK_DEATH(0.6, firer)
+ SLEEP_CHECK_DEATH(3 SECONDS, firer)
diff --git a/code/datums/actions/mobs/teleport.dm b/code/datums/actions/mobs/teleport.dm
new file mode 100644
index 0000000000000..7b7ffddf30b3d
--- /dev/null
+++ b/code/datums/actions/mobs/teleport.dm
@@ -0,0 +1,25 @@
+/datum/action/cooldown/mob_cooldown/teleport
+ name = "Teleport"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ desc = "Allows you to teleport a certain distance away from a position in a random direction."
+ cooldown_time = 10 SECONDS
+ /// The distance from the target
+ var/radius = 6
+
+/datum/action/cooldown/mob_cooldown/teleport/Activate(atom/target_atom)
+ disable_cooldown_actions()
+ teleport_to(target_atom)
+ StartCooldown()
+ enable_cooldown_actions()
+ return TRUE
+
+/// Handles randomly teleporting the owner around the target in view
+/datum/action/cooldown/mob_cooldown/teleport/proc/teleport_to(atom/teleport_target)
+ var/list/possible_ends = view(radius, teleport_target.loc) - view(radius - 1, teleport_target.loc)
+ for(var/turf/closed/cant_teleport_turf in possible_ends)
+ possible_ends -= cant_teleport_turf
+ if(!possible_ends.len)
+ return
+ var/turf/end = pick(possible_ends)
+ do_teleport(owner, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE)
diff --git a/code/datums/components/bayonet_attachable.dm b/code/datums/components/bayonet_attachable.dm
new file mode 100644
index 0000000000000..e4479910f22f4
--- /dev/null
+++ b/code/datums/components/bayonet_attachable.dm
@@ -0,0 +1,212 @@
+/**
+ * Component which allows you to attach a bayonet to an item,
+ * be it a piece of clothing or a tool.
+ */
+/datum/component/bayonet_attachable
+ /// Whenever we can remove the bayonet with a screwdriver
+ var/removable = TRUE
+ /// If passed, we wil simply update our item's icon_state when a bayonet is attached.
+ /// Formatted as parent_base_state-[bayonet_icon_state-state]
+ var/bayonet_icon_state
+ /// If passed, we will use a specific overlay instead of using the knife itself
+ /// The state to take from the bayonet overlay icon if supplied.
+ var/bayonet_overlay
+ /// This is the icon file it grabs the overlay from.
+ var/bayonet_overlay_icon
+ /// Offsets for the bayonet overlay
+ var/offset_x = 0
+ var/offset_y = 0
+ /// If this component allows sawing off the parent gun/should be deleted when the parent gun is sawn off
+ var/allow_sawnoff = FALSE
+
+ // Internal vars
+ /// Currently attached bayonet
+ var/obj/item/bayonet
+ /// Static typecache of all knives that can become bayonets
+ var/static/list/valid_bayonets = typecacheof(list(/obj/item/knife/combat))
+
+/datum/component/bayonet_attachable/Initialize(
+ obj/item/starting_bayonet,
+ offset_x = 0,
+ offset_y = 0,
+ removable = TRUE,
+ bayonet_icon_state = null,
+ bayonet_overlay = "bayonet",
+ bayonet_overlay_icon = 'icons/obj/weapons/guns/bayonets.dmi',
+ allow_sawnoff = FALSE
+)
+
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.removable = removable
+ src.bayonet_icon_state = bayonet_icon_state
+ src.bayonet_overlay = bayonet_overlay
+ src.bayonet_overlay_icon = bayonet_overlay_icon
+ src.offset_x = offset_x
+ src.offset_y = offset_y
+ src.allow_sawnoff = allow_sawnoff
+
+ if (istype(starting_bayonet))
+ add_bayonet(starting_bayonet)
+
+/datum/component/bayonet_attachable/Destroy(force)
+ if(bayonet)
+ remove_bayonet()
+ return ..()
+
+/datum/component/bayonet_attachable/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(on_parent_deconstructed))
+ RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(on_item_exit))
+ RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver))
+ RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state))
+ RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_parent_deleted))
+ RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(on_pre_attack))
+ RegisterSignal(parent, COMSIG_GUN_BEING_SAWNOFF, PROC_REF(on_being_sawnoff))
+ RegisterSignal(parent, COMSIG_GUN_SAWN_OFF, PROC_REF(on_sawn_off))
+
+/datum/component/bayonet_attachable/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_OBJ_DECONSTRUCT,
+ COMSIG_ATOM_EXITED,
+ COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
+ COMSIG_ATOM_UPDATE_ICON_STATE,
+ COMSIG_ATOM_UPDATE_OVERLAYS,
+ COMSIG_ATOM_ATTACKBY,
+ COMSIG_ATOM_EXAMINE,
+ COMSIG_QDELETING,
+ COMSIG_ITEM_PRE_ATTACK,
+ COMSIG_GUN_BEING_SAWNOFF,
+ COMSIG_GUN_SAWN_OFF,
+ ))
+
+/datum/component/bayonet_attachable/proc/on_examine(obj/item/source, mob/examiner, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(isnull(bayonet))
+ examine_list += "It has a bayonet lug on it."
+ return
+
+ examine_list += "It has \a [bayonet] [removable ? "" : "permanently "]affixed to it."
+ if(removable)
+ examine_list += span_info("[bayonet] looks like it can be unscrewed from [bayonet].")
+
+/datum/component/bayonet_attachable/proc/on_pre_attack(obj/item/source, atom/target, mob/living/user, params)
+ SIGNAL_HANDLER
+
+ if (isnull(bayonet) || !(user.istate & ISTATE_HARM))
+ return NONE
+
+ INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, params)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/datum/component/bayonet_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, params)
+ SIGNAL_HANDLER
+
+ if(!is_type_in_typecache(attacking_item, valid_bayonets))
+ return
+
+ if(bayonet)
+ source.balloon_alert(attacker, "already has \a [bayonet]!")
+ return
+
+ if(!attacker.transferItemToLoc(attacking_item, source))
+ return
+
+ add_bayonet(attacking_item, attacker)
+ source.balloon_alert(attacker, "attached")
+ return COMPONENT_NO_AFTERATTACK
+
+/datum/component/bayonet_attachable/proc/add_bayonet(obj/item/new_bayonet, mob/attacher)
+ if(bayonet)
+ CRASH("[type] tried to add a new bayonet when it already had one.")
+
+ bayonet = new_bayonet
+ if(bayonet.loc != parent)
+ bayonet.forceMove(parent)
+ var/obj/item/item_parent = parent
+ item_parent.update_appearance()
+
+/datum/component/bayonet_attachable/proc/remove_bayonet()
+ bayonet = null
+ var/obj/item/item_parent = parent
+ item_parent.update_appearance()
+
+/datum/component/bayonet_attachable/proc/on_item_exit(obj/item/source, atom/movable/gone, direction)
+ SIGNAL_HANDLER
+
+ if(gone == bayonet)
+ remove_bayonet()
+
+/datum/component/bayonet_attachable/proc/on_parent_deconstructed(obj/item/source, disassembled)
+ SIGNAL_HANDLER
+
+ if(QDELETED(bayonet) || !removable)
+ remove_bayonet()
+ return
+
+ bayonet.forceMove(source.drop_location())
+
+/datum/component/bayonet_attachable/proc/on_screwdriver(obj/item/source, mob/user, obj/item/tool)
+ SIGNAL_HANDLER
+
+ if(!bayonet || !removable)
+ return
+
+ INVOKE_ASYNC(src, PROC_REF(unscrew_bayonet), source, user, tool)
+ return ITEM_INTERACT_BLOCKING
+
+/datum/component/bayonet_attachable/proc/unscrew_bayonet(obj/item/source, mob/user, obj/item/tool)
+ tool?.play_tool_sound(source)
+ source.balloon_alert(user, "unscrewed [bayonet]")
+
+ var/obj/item/to_remove = bayonet
+ to_remove.forceMove(source.drop_location())
+ if(source.Adjacent(user) && !issilicon(user))
+ user.put_in_hands(to_remove)
+
+/datum/component/bayonet_attachable/proc/on_update_overlays(obj/item/source, list/overlays)
+ SIGNAL_HANDLER
+
+ if(!bayonet_overlay || !bayonet_overlay_icon)
+ return
+
+ if(!bayonet)
+ return
+
+ var/mutable_appearance/bayonet_appearance = mutable_appearance(bayonet_overlay_icon, bayonet_overlay)
+ bayonet_appearance.pixel_x = offset_x
+ bayonet_appearance.pixel_y = offset_y
+ overlays += bayonet_appearance
+
+/datum/component/bayonet_attachable/proc/on_update_icon_state(obj/item/source)
+ SIGNAL_HANDLER
+
+ if(!bayonet_icon_state)
+ return
+
+ var/base_state = source.base_icon_state || initial(source.icon_state)
+ if(bayonet)
+ source.icon_state = "[base_state]-[bayonet_icon_state]"
+ else if(source.icon_state != base_state)
+ source.icon_state = base_state
+
+/datum/component/bayonet_attachable/proc/on_parent_deleted(obj/item/source)
+ SIGNAL_HANDLER
+ QDEL_NULL(bayonet)
+
+/datum/component/bayonet_attachable/proc/on_being_sawnoff(obj/item/source, mob/user)
+ SIGNAL_HANDLER
+
+ if (!bayonet || allow_sawnoff)
+ return
+ source.balloon_alert(user, "bayonet must be removed!")
+ return COMPONENT_CANCEL_SAWING_OFF
+
+/datum/component/bayonet_attachable/proc/on_sawn_off(obj/item/source, mob/user)
+ SIGNAL_HANDLER
+ if (!allow_sawnoff)
+ qdel(src)
diff --git a/code/datums/components/bullet_intercepting.dm b/code/datums/components/bullet_intercepting.dm
index c176de54b94c5..42163cd8228bd 100644
--- a/code/datums/components/bullet_intercepting.dm
+++ b/code/datums/components/bullet_intercepting.dm
@@ -53,7 +53,7 @@
wearer = null
/// Called when wearer is shot, check if we're going to block the hit
-/datum/component/bullet_intercepting/proc/on_wearer_shot(mob/living/victim, list/signal_args, obj/projectile/bullet)
+/datum/component/bullet_intercepting/proc/on_wearer_shot(mob/living/victim, obj/projectile/bullet)
SIGNAL_HANDLER
if (victim != wearer || victim.stat == DEAD || bullet.armor_flag != block_type )
return
diff --git a/code/datums/components/crafting/equipment.dm b/code/datums/components/crafting/equipment.dm
index ec8ee5d1be36f..109bcd7bc7e21 100644
--- a/code/datums/components/crafting/equipment.dm
+++ b/code/datums/components/crafting/equipment.dm
@@ -240,3 +240,15 @@
tool_behaviors = list(TOOL_CROWBAR)
time = 5 SECONDS
category = CAT_EQUIPMENT
+
+/datum/crafting_recipe/tether_anchor
+ name = "Tether Anchor"
+ result = /obj/item/tether_anchor
+ reqs = list(
+ /obj/item/stack/sheet/iron = 5,
+ /obj/item/stack/rods = 2,
+ /obj/item/stack/cable_coil = 15
+ )
+ tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WRENCH)
+ time = 5 SECONDS
+ category = CAT_EQUIPMENT
diff --git a/code/datums/components/crafting/guncrafting.dm b/code/datums/components/crafting/guncrafting.dm
index f239288b0312e..238ed877f3fa6 100644
--- a/code/datums/components/crafting/guncrafting.dm
+++ b/code/datums/components/crafting/guncrafting.dm
@@ -53,8 +53,8 @@
desc = "A suitcase containing the necessary gun parts to tranform a standard energy gun into a temperature gun. Fantastic at birthday parties and killing indigenious populations of lizardpeople."
/obj/item/weaponcrafting/gunkit/beam_rifle
- name = "particle acceleration rifle part kit (lethal)"
- desc = "The coup de grace of guncrafting. This suitcase contains the highly experimental rig for a particle acceleration rifle. Requires an energy gun, a stabilized flux anomaly and a stabilized gravity anomaly."
+ name = "\improper Event Horizon anti-existential beam rifle part kit (DOOMSDAY DEVICE, DO NOT CONSTRUCT)"
+ desc = "What fevered minds wrought this terrible construction kit? To create a frame to harness the strange energies that flow through the Spinward Sector towards such horrible acts of violence?"
/obj/item/weaponcrafting/gunkit/decloner
name = "decloner part kit (lethal)"
diff --git a/code/datums/components/crafting/ranged_weapon.dm b/code/datums/components/crafting/ranged_weapon.dm
index 383078aff08b3..adfb392d91648 100644
--- a/code/datums/components/crafting/ranged_weapon.dm
+++ b/code/datums/components/crafting/ranged_weapon.dm
@@ -76,23 +76,18 @@
blacklist += subtypesof(/obj/item/gun/energy/e_gun)
/datum/crafting_recipe/beam_rifle
- name = "Particle Acceleration Rifle"
- tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- result = /obj/item/gun/energy/beam_rifle
+ name = "Event Horizon Anti-Existential Beam Rifle"
+ result = /obj/item/gun/energy/event_horizon
reqs = list(
- /obj/item/gun/energy/e_gun = 1,
- /obj/item/assembly/signaler/anomaly/flux = 1,
+ /obj/item/assembly/signaler/anomaly/flux = 2,
/obj/item/assembly/signaler/anomaly/grav = 1,
- /obj/item/stack/cable_coil = 5,
+ /obj/item/assembly/signaler/anomaly/vortex = MAX_CORES_VORTEX,
+ /obj/item/assembly/signaler/anomaly/bluespace = 1,
/obj/item/weaponcrafting/gunkit/beam_rifle = 1,
)
- time = 20 SECONDS
+ time = 30 SECONDS //Maybe the delay will make you reconsider your choices
category = CAT_WEAPON_RANGED
-/datum/crafting_recipe/beam_rifle/New()
- ..()
- blacklist += subtypesof(/obj/item/gun/energy/e_gun)
-
/datum/crafting_recipe/ebow
name = "Energy Crossbow"
tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm
deleted file mode 100644
index dc7dbc2cacf1c..0000000000000
--- a/code/datums/components/embedded.dm
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats,
- and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the
- element survives to embed another day.
-
- - Carbon embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are:
- -- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance
- -- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling)
- -- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals.
-
-
- In addition, there are 2 cases of embedding: embedding, and sticking
-
- - Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy"
-
- - Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky"
- when it has 0 for both pain multiplier and jostle pain multiplier. It's a bit arbitrary, but fairly straightforward.
-
- Stickables differ from embeds in the following ways:
- -- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y"
- -- There is no slicing sound on impact
- -- All damage checks and bloodloss are skipped
-
-*/
-
-/datum/component/embedded
- dupe_mode = COMPONENT_DUPE_ALLOWED
- var/obj/item/bodypart/limb
- var/obj/item/weapon
-
- // all of this stuff is explained in _DEFINES/combat.dm
- var/embed_chance // not like we really need it once we're already stuck in but hey
- var/fall_chance
- var/pain_chance
- var/pain_mult
- var/impact_pain_mult
- var/remove_pain_mult
- var/rip_time
- var/ignore_throwspeed_threshold
- var/jostle_chance
- var/jostle_pain_mult
- var/pain_stam_pct
-
- ///if both our pain multiplier and jostle pain multiplier are 0, we're harmless and can omit most of the damage related stuff
- var/harmful
-
-/datum/component/embedded/Initialize(obj/item/I,
- datum/thrownthing/throwingdatum,
- obj/item/bodypart/part,
- embed_chance = EMBED_CHANCE,
- fall_chance = EMBEDDED_ITEM_FALLOUT,
- pain_chance = EMBEDDED_PAIN_CHANCE,
- pain_mult = EMBEDDED_PAIN_MULTIPLIER,
- remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
- impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
- rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
- ignore_throwspeed_threshold = FALSE,
- jostle_chance = EMBEDDED_JOSTLE_CHANCE,
- jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER,
- pain_stam_pct = EMBEDDED_PAIN_STAM_PCT)
-
- if(!iscarbon(parent) || !isitem(I))
- return COMPONENT_INCOMPATIBLE
-
- if(part)
- limb = part
- src.embed_chance = embed_chance
- src.fall_chance = fall_chance
- src.pain_chance = pain_chance
- src.pain_mult = pain_mult
- src.remove_pain_mult = remove_pain_mult
- src.rip_time = rip_time
- src.impact_pain_mult = impact_pain_mult
- src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
- src.jostle_chance = jostle_chance
- src.jostle_pain_mult = jostle_pain_mult
- src.pain_stam_pct = pain_stam_pct
- src.weapon = I
-
- if(!weapon.isEmbedHarmless())
- harmful = TRUE
-
- weapon.embedded(parent, part)
- START_PROCESSING(SSdcs, src)
- var/mob/living/carbon/victim = parent
-
- limb._embed_object(weapon) // on the inside... on the inside...
- weapon.forceMove(victim)
- RegisterSignals(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING), PROC_REF(weaponDeleted))
- victim.visible_message(span_danger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] [victim]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] your [limb.plaintext_zone]!"))
-
- var/damage = weapon.throwforce
- if(harmful)
- victim.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject)
- playsound(victim,'sound/weapons/bladeslice.ogg', 40)
- if (limb.can_bleed())
- weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
- damage += weapon.w_class * impact_pain_mult
- victim.add_mood_event("embedded", /datum/mood_event/embedded)
-
- if(damage > 0)
- var/armor = victim.run_armor_check(limb.body_zone, MELEE, "Your armor has protected your [limb.plaintext_zone].", "Your armor has softened a hit to your [limb.plaintext_zone].",I.armour_penetration, weak_against_armour = I.weak_against_armour)
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, blocked=armor, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness())
- victim.stamina.adjust(-pain_stam_pct * damage)
-
-/datum/component/embedded/Destroy()
- var/mob/living/carbon/victim = parent
- if(victim && !victim.has_embedded_objects())
- victim.clear_alert(ALERT_EMBEDDED_OBJECT)
- victim.clear_mood_event("embedded")
- if(weapon)
- UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
- weapon = null
- limb = null
- return ..()
-
-/datum/component/embedded/RegisterWithParent()
- RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(jostleCheck))
- RegisterSignal(parent, COMSIG_CARBON_EMBED_RIP, PROC_REF(ripOut))
- RegisterSignal(parent, COMSIG_CARBON_EMBED_REMOVAL, PROC_REF(safeRemove))
- RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(checkTweeze))
- RegisterSignal(parent, COMSIG_MAGIC_RECALL, PROC_REF(magic_pull))
-
-/datum/component/embedded/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_ATOM_ATTACKBY, COMSIG_MAGIC_RECALL))
-
-/datum/component/embedded/process(seconds_per_tick)
- var/mob/living/carbon/victim = parent
-
- if(!victim || !limb) // in case the victim and/or their limbs exploded (say, due to a sticky bomb)
- weapon.forceMove(get_turf(weapon))
- qdel(src)
- return
-
- if(victim.stat == DEAD)
- return
-
- var/damage = weapon.w_class * pain_mult
- var/pain_chance_current = SPT_PROB_RATE(pain_chance / 100, seconds_per_tick) * 100
- if(pain_stam_pct && HAS_TRAIT_FROM(victim, TRAIT_INCAPACITATED, STAMINA)) //if it's a less-lethal embed, give them a break if they're already stamcritted
- pain_chance_current *= 0.2
- damage *= 0.5
- else if(victim.body_position == LYING_DOWN)
- pain_chance_current *= 0.2
-
- if(harmful && prob(pain_chance_current))
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, wound_bonus = CANT_WOUND)
- victim.stamina.adjust(-pain_stam_pct * damage)
- to_chat(victim, span_userdanger("[weapon] embedded in your [limb.plaintext_zone] hurts!"))
-
- var/fall_chance_current = SPT_PROB_RATE(fall_chance / 100, seconds_per_tick) * 100
- if(victim.body_position == LYING_DOWN)
- fall_chance_current *= 0.2
-
- if(prob(fall_chance_current))
- fallOut()
-
-////////////////////////////////////////
-////////////BEHAVIOR PROCS//////////////
-////////////////////////////////////////
-
-
-/// Called every time a carbon with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the carbon is crawling or walking.
-/datum/component/embedded/proc/jostleCheck()
- SIGNAL_HANDLER
-
- var/mob/living/carbon/victim = parent
- var/chance = jostle_chance
- if(victim.m_intent == MOVE_INTENT_WALK || victim.body_position == LYING_DOWN)
- chance *= 0.5
-
- if(harmful && prob(chance))
- var/damage = weapon.w_class * jostle_pain_mult
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, wound_bonus = CANT_WOUND)
- victim.stamina.adjust(-pain_stam_pct * damage)
- to_chat(victim, span_userdanger("[weapon] embedded in your [limb.plaintext_zone] jostles and stings!"))
-
-
-/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls safe_remove()
-/datum/component/embedded/proc/fallOut()
- var/mob/living/carbon/victim = parent
-
- if(harmful)
- var/damage = weapon.w_class * remove_pain_mult
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, wound_bonus = CANT_WOUND)
- victim.stamina.adjust(-pain_stam_pct * damage)
- victim.visible_message(span_danger("[weapon] falls [harmful ? "out" : "off"] of [victim.name]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] falls [harmful ? "out" : "off"] of your [limb.plaintext_zone]!"))
- safeRemove()
-
-
-/// Called when a carbon with an object embedded/stuck to them inspects themselves and clicks the appropriate link to begin ripping the item out. This handles the ripping attempt, descriptors, and dealing damage, then calls safe_remove()
-/datum/component/embedded/proc/ripOut(datum/source, obj/item/I, obj/item/bodypart/limb)
- SIGNAL_HANDLER
-
- if(I != weapon || src.limb != limb)
- return
- var/mob/living/carbon/victim = parent
- var/time_taken = rip_time * weapon.w_class
- INVOKE_ASYNC(src, PROC_REF(complete_rip_out), victim, I, limb, time_taken)
-
-/// everything async that ripOut used to do
-/datum/component/embedded/proc/complete_rip_out(mob/living/carbon/victim, obj/item/I, obj/item/bodypart/limb, time_taken)
- victim.visible_message(span_warning("[victim] attempts to remove [weapon] from [victim.p_their()] [limb.plaintext_zone]."),span_notice("You attempt to remove [weapon] from your [limb.plaintext_zone]... (It will take [DisplayTimeText(time_taken)].)"))
- if(!do_after(victim, time_taken, target = victim))
- return
- if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects))
- qdel(src)
- return
- if(harmful)
- var/damage = weapon.w_class * remove_pain_mult
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, sharpness=SHARP_EDGED) //It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow
- victim.stamina.adjust(-pain_stam_pct * damage)
- victim.emote("scream")
-
- victim.visible_message(span_notice("[victim] successfully rips [weapon] [harmful ? "out" : "off"] of [victim.p_their()] [limb.plaintext_zone]!"), span_notice("You successfully remove [weapon] from your [limb.plaintext_zone]."))
- safeRemove(victim)
-
-/// This proc handles the final step and actual removal of an embedded/stuck item from a carbon, whether or not it was actually removed safely.
-/// If you want the thing to go into someone's hands rather than the floor, pass them in to_hands
-/datum/component/embedded/proc/safeRemove(mob/to_hands)
- SIGNAL_HANDLER
-
- var/mob/living/carbon/victim = parent
- limb._unembed_object(weapon)
- UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING)) // have to do it here otherwise we trigger weaponDeleted()
-
- if(!weapon.unembedded()) // if it hasn't deleted itself due to drop del
- UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
- if(to_hands)
- INVOKE_ASYNC(to_hands, TYPE_PROC_REF(/mob, put_in_hands), weapon)
- else
- weapon.forceMove(get_turf(victim))
-
- qdel(src)
-
-/// Something deleted or moved our weapon while it was embedded, how rude!
-/datum/component/embedded/proc/weaponDeleted()
- SIGNAL_HANDLER
-
- var/mob/living/carbon/victim = parent
- limb._unembed_object(weapon)
-
- if(victim)
- to_chat(victim, span_userdanger("\The [weapon] that was embedded in your [limb.plaintext_zone] disappears!"))
-
- qdel(src)
-
-/// The signal for listening to see if someone is using a hemostat on us to pluck out this object
-/datum/component/embedded/proc/checkTweeze(mob/living/carbon/victim, obj/item/possible_tweezers, mob/user)
- SIGNAL_HANDLER
-
- if(!istype(victim) || possible_tweezers.tool_behaviour != TOOL_HEMOSTAT || user.zone_selected != limb.body_zone)
- return
-
- if(weapon != limb.embedded_objects[1]) // just pluck the first one, since we can't easily coordinate with other embedded components affecting this limb who is highest priority
- return
-
- if(ishuman(victim)) // check to see if the limb is actually exposed
- var/mob/living/carbon/human/victim_human = victim
- if(!victim_human.try_inject(user, limb.body_zone, INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE))
- return TRUE
-
- INVOKE_ASYNC(src, PROC_REF(tweezePluck), possible_tweezers, user)
- return COMPONENT_NO_AFTERATTACK
-
-/// The actual action for pulling out an embedded object with a hemostat
-/datum/component/embedded/proc/tweezePluck(obj/item/possible_tweezers, mob/user)
- var/mob/living/carbon/victim = parent
-
- var/self_pluck = (user == victim)
-
- if(self_pluck)
- user.visible_message(span_danger("[user] begins plucking [weapon] from [user.p_their()] [limb.plaintext_zone]"), span_notice("You start plucking [weapon] from your [limb.plaintext_zone]..."),\
- vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim)
- else
- user.visible_message(span_danger("[user] begins plucking [weapon] from [victim]'s [limb.plaintext_zone]"),span_notice("You start plucking [weapon] from [victim]'s [limb.plaintext_zone]..."), \
- vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim)
- to_chat(victim, span_userdanger("[user] begins plucking [weapon] from your [limb.plaintext_zone]..."))
-
- var/pluck_time = 2.5 SECONDS * weapon.w_class * (self_pluck ? 2 : 1)
- if(!do_after(user, pluck_time, victim))
- if(self_pluck)
- to_chat(user, span_danger("You fail to pluck [weapon] from your [limb.plaintext_zone]."))
- else
- to_chat(user, span_danger("You fail to pluck [weapon] from [victim]'s [limb.plaintext_zone]."))
- to_chat(victim, span_danger("[user] fails to pluck [weapon] from your [limb.plaintext_zone]."))
- return
-
- to_chat(user, span_notice("You successfully pluck [weapon] from [victim]'s [limb.plaintext_zone]."))
- to_chat(victim, span_notice("[user] plucks [weapon] from your [limb.plaintext_zone]."))
- safeRemove(user)
-
-/// Called when an object is ripped out of someone's body by magic or other abnormal means
-/datum/component/embedded/proc/magic_pull(datum/source, mob/living/caster, obj/marked_item)
- SIGNAL_HANDLER
-
- if(marked_item != weapon)
- return
-
- var/mob/living/carbon/victim = parent
-
- if(!harmful)
- victim.visible_message(span_danger("[marked_item] vanishes from [victim.name]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] vanishes from [limb.plaintext_zone]!"))
- return
- var/damage = weapon.w_class * remove_pain_mult
- limb.receive_damage(brute=(1-pain_stam_pct) * damage * 1.5, sharpness=SHARP_EDGED) // Performs exit wounds and flings the user to the caster if nearby
- victim.cause_wound_of_type_and_severity(WOUND_PIERCE, limb, WOUND_SEVERITY_MODERATE)
- victim.stamina.adjust(-pain_stam_pct * damage)
- playsound(get_turf(victim), 'sound/effects/wounds/blood2.ogg', 50, TRUE)
-
- var/dist = get_dist(caster, victim) //Check if the caster is close enough to yank them in
- if(dist < 7)
- victim.throw_at(caster, get_dist(victim, caster) - 1, 1, caster)
- victim.Paralyze(1 SECONDS)
- victim.visible_message(span_alert("[victim] is sent flying towards [caster] as the [marked_item] tears out of them!"), span_alert("You are launched at [caster] as the [marked_item] tears from your body and towards their hand!"))
- victim.visible_message(span_danger("[marked_item] is violently torn from [victim.name]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] is violently torn from your [limb.plaintext_zone]!"))
diff --git a/code/datums/components/mirv.dm b/code/datums/components/mirv.dm
index 0f41df1d2888a..ead33d476d459 100644
--- a/code/datums/components/mirv.dm
+++ b/code/datums/components/mirv.dm
@@ -32,12 +32,12 @@
var/turf/target_turf = get_turf(target)
for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target))
- var/obj/projectile/P = new projectile_type(target_turf)
+ var/obj/projectile/proj = new projectile_type(target_turf)
//Shooting Code:
- P.range = radius+1
+ proj.range = radius+1
if(override_projectile_range)
- P.range = override_projectile_range
- P.preparePixelProjectile(shootat_turf, target)
- P.firer = firer // don't hit ourself that would be really annoying
- P.impacted = list(target = TRUE) // don't hit the target we hit already with the flak
- P.fire()
+ proj.range = override_projectile_range
+ proj.aim_projectile(shootat_turf, target)
+ proj.firer = firer // don't hit ourself that would be really annoying
+ proj.impacted = list(WEAKREF(target) = TRUE) // don't hit the target we hit already with the flak
+ proj.fire()
diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm
index 9c663a469e2b9..4b49096a6e261 100644
--- a/code/datums/components/parry.dm
+++ b/code/datums/components/parry.dm
@@ -23,7 +23,7 @@
/// Callback for special effects upon parrying
var/datum/callback/parry_callback
-/datum/component/parriable_projectile/Initialize(parry_speed_mult = 0.8, parry_damage_mult = 1.15, boost_speed_mult = 0.6, boost_damage_mult = 1.5, parry_trait = TRAIT_MINING_PARRYING, grace_period = 0.25 SECONDS, datum/callback/parry_callback = null)
+/datum/component/parriable_projectile/Initialize(parry_speed_mult = 1.25, parry_damage_mult = 1.15, boost_speed_mult = 1.6, boost_damage_mult = 1.5, parry_trait = TRAIT_MINING_PARRYING, grace_period = 0.25 SECONDS, datum/callback/parry_callback = null)
if(!isprojectile(parent))
return COMPONENT_INCOMPATIBLE
src.parry_speed_mult = parry_speed_mult
@@ -41,13 +41,13 @@
. = ..()
/datum/component/parriable_projectile/RegisterWithParent()
- RegisterSignal(parent, COMSIG_PROJECTILE_PIXEL_STEP, PROC_REF(on_moved))
+ RegisterSignal(parent, COMSIG_PROJECTILE_MOVE_PROCESS_STEP, PROC_REF(on_moved))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(before_move))
RegisterSignal(parent, COMSIG_PROJECTILE_BEFORE_MOVE, PROC_REF(before_move))
RegisterSignal(parent, COMSIG_PROJECTILE_SELF_PREHIT, PROC_REF(before_hit))
/datum/component/parriable_projectile/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_PROJECTILE_PIXEL_STEP, COMSIG_MOVABLE_MOVED, COMSIG_PROJECTILE_BEFORE_MOVE, COMSIG_PROJECTILE_SELF_PREHIT))
+ UnregisterSignal(parent, list(COMSIG_PROJECTILE_MOVE_PROCESS_STEP, COMSIG_MOVABLE_MOVED, COMSIG_PROJECTILE_BEFORE_MOVE, COMSIG_PROJECTILE_SELF_PREHIT))
/datum/component/parriable_projectile/proc/before_move(obj/projectile/source)
SIGNAL_HANDLER
@@ -71,7 +71,7 @@
/datum/component/parriable_projectile/proc/on_moved(obj/projectile/source)
SIGNAL_HANDLER
- if (!isturf(source.loc))
+ if (!isturf(source.loc) || parry_turfs[source.loc])
return
parry_turfs[source.loc] = world.time + grace_period
RegisterSignal(source.loc, COMSIG_CLICK, PROC_REF(on_turf_click))
@@ -86,10 +86,9 @@
return
parriers[user] = world.time + grace_period
-/datum/component/parriable_projectile/proc/before_hit(obj/projectile/source, list/bullet_args)
+/datum/component/parriable_projectile/proc/before_hit(obj/projectile/source, mob/living/user)
SIGNAL_HANDLER
- var/mob/user = bullet_args[2]
if (!istype(user) || !parriers[user] || parried)
return
@@ -97,15 +96,18 @@
return attempt_parry(source, user)
/datum/component/parriable_projectile/proc/attempt_parry(obj/projectile/source, mob/user)
+ if (QDELETED(source) || source.deletion_queued)
+ return NONE
+
if (SEND_SIGNAL(user, COMSIG_LIVING_PROJECTILE_PARRIED, source) & INTERCEPT_PARRY_EFFECTS)
- return
+ return NONE
parried = TRUE
if (source.firer != user)
- if (abs(source.Angle - dir2angle(user)) < 15)
- source.set_angle((source.Angle + 180) % 360 + rand(-3, 3))
+ if (abs(source.angle - dir2angle(user.dir)) < 15)
+ source.set_angle((source.angle + 180) % 360 + rand(-3, 3))
else
- source.set_angle(dir2angle(user) + rand(-3, 3))
+ source.set_angle(dir2angle(user.dir) + rand(-3, 3))
user.visible_message(span_warning("[user] expertly parries [source] with [user.p_their()] bare hand!"), span_warning("You parry [source] with your hand!"))
else
user.visible_message(span_warning("[user] boosts [source] with [user.p_their()] bare hand!"), span_warning("You boost [source] with your hand!"))
@@ -119,4 +121,4 @@
user.playsound_local(source.loc, 'sound/effects/parry.ogg', 50, TRUE)
user.overlay_fullscreen("projectile_parry", /atom/movable/screen/fullscreen/crit/projectile_parry, 2)
addtimer(CALLBACK(user, TYPE_PROC_REF(/mob, clear_fullscreen), "projectile_parry"), 0.25 SECONDS)
- return PROJECTILE_INTERRUPT_HIT
+ return PROJECTILE_INTERRUPT_HIT_PHASE
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index e5dca7d41cafa..ea532224a9650 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -10,7 +10,7 @@
*
* Pellet cloud currently works on two classes of sources: directed (ammo casings), and circular (grenades, landmines).
* -Directed: This means you're shooting multiple pellets, like buckshot. If an ammo casing is defined as having multiple pellets, it will automatically create a pellet cloud
- * and call COMSIG_PELLET_CLOUD_INIT (see [/obj/item/ammo_casing/proc/fire_casing]). Thus, the only projectiles fired will be the ones fired here.
+ * and call COMSIG_FIRE_CASING (see [/obj/item/ammo_casing/proc/fire_casing]). Thus, the only projectiles fired will be the ones fired here.
* The magnitude var controls how many pellets are created.
* -Circular: This results in a big spray of shrapnel flying all around the detonation point when the grenade fires COMSIG_GRENADE_DETONATE or landmine triggers COMSIG_MINE_TRIGGERED.
* The magnitude var controls how big the detonation radius is (the bigger the magnitude, the more shrapnel is created). Grenades can be covered with bodies to reduce shrapnel output.
@@ -80,7 +80,7 @@
/datum/component/pellet_cloud/RegisterWithParent()
RegisterSignal(parent, COMSIG_PREQDELETED, PROC_REF(nullspace_parent))
if(isammocasing(parent))
- RegisterSignal(parent, COMSIG_PELLET_CLOUD_INIT, PROC_REF(create_casing_pellets))
+ RegisterSignal(parent, COMSIG_FIRE_CASING, PROC_REF(create_casing_pellets))
else if(isgrenade(parent))
RegisterSignal(parent, COMSIG_GRENADE_ARMED, PROC_REF(grenade_armed))
RegisterSignal(parent, COMSIG_GRENADE_DETONATE, PROC_REF(create_blast_pellets))
@@ -90,15 +90,15 @@
RegisterSignal(parent, COMSIG_SUPPLYPOD_LANDED, PROC_REF(create_blast_pellets))
/datum/component/pellet_cloud/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_PREQDELETED, COMSIG_PELLET_CLOUD_INIT, COMSIG_GRENADE_DETONATE, COMSIG_GRENADE_ARMED, COMSIG_MOVABLE_MOVED, COMSIG_MINE_TRIGGERED, COMSIG_ITEM_DROPPED))
+ UnregisterSignal(parent, list(COMSIG_PREQDELETED, COMSIG_FIRE_CASING, COMSIG_GRENADE_DETONATE, COMSIG_GRENADE_ARMED, COMSIG_MOVABLE_MOVED, COMSIG_MINE_TRIGGERED, COMSIG_ITEM_DROPPED))
/**
* create_casing_pellets() is for directed pellet clouds for ammo casings that have multiple pellets (buckshot and scatter lasers for instance)
*
* Honestly this is mostly just a rehash of [/obj/item/ammo_casing/proc/fire_casing] for pellet counts > 1, except this lets us tamper with the pellets and hook onto them for tracking purposes.
- * The arguments really don't matter, this proc is triggered by COMSIG_PELLET_CLOUD_INIT which is only for this really, it's just a big mess of the state vars we need for doing the stuff over here.
+ * The arguments really don't matter, while this proc is triggered by COMSIG_FIRE_CASING, it's just a big mess of the state vars we need for doing the stuff over here.
*/
-/datum/component/pellet_cloud/proc/create_casing_pellets(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro)
+/datum/component/pellet_cloud/proc/create_casing_pellets(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro, obj/projectile/proj)
SIGNAL_HANDLER
shooter = user
@@ -108,6 +108,8 @@
// things like mouth executions and gunpoints can multiply the damage and wounds of projectiles, so this makes sure those effects are applied to each pellet instead of just one
var/original_damage = shell.loaded_projectile.damage
+ var/original_stamina = shell.loaded_projectile.stamina
+ var/original_speed = shell.loaded_projectile.speed
var/original_wb = shell.loaded_projectile.wound_bonus
var/original_bwb = shell.loaded_projectile.bare_wound_bonus
@@ -122,6 +124,8 @@
RegisterSignal(shell.loaded_projectile, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(pellet_hit))
RegisterSignals(shell.loaded_projectile, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_QDELETING), PROC_REF(pellet_range))
shell.loaded_projectile.damage = original_damage
+ shell.loaded_projectile.stamina = original_stamina
+ shell.loaded_projectile.speed = original_speed
shell.loaded_projectile.wound_bonus = original_wb
shell.loaded_projectile.bare_wound_bonus = original_bwb
pellets += shell.loaded_projectile
@@ -224,10 +228,10 @@
break
///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets)
-/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/P, atom/movable/firer, atom/target, Angle, hit_zone)
+/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/proj, atom/movable/firer, atom/target, Angle, hit_zone)
SIGNAL_HANDLER
- pellets -= P
+ pellets -= proj
terminated++
hits++
var/obj/item/bodypart/hit_part
@@ -237,60 +241,60 @@
hit_part = hit_carbon.get_bodypart(hit_zone)
if(hit_part)
target = hit_part
- if(P.wound_bonus != CANT_WOUND) // handle wounding
+ if(proj.wound_bonus != CANT_WOUND) // handle wounding
// unfortunately, due to how pellet clouds handle finalizing only after every pellet is accounted for, that also means there might be a short delay in dealing wounds if one pellet goes wide
// while buckshot may reach a target or miss it all in one tick, we also have to account for possible ricochets that may take a bit longer to hit the target
if(isnull(wound_info_by_part[hit_part]))
wound_info_by_part[hit_part] = list(0, 0, 0)
- wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += P.damage // these account for decay
- wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += P.wound_bonus
- wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += P.bare_wound_bonus
- P.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate
+ wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += proj.damage // these account for decay
+ wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += proj.wound_bonus
+ wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += proj.bare_wound_bonus
+ proj.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate
else if(isobj(target))
var/obj/hit_object = target
- if(hit_object.damage_deflection > P.damage || !P.damage)
+ if(hit_object.damage_deflection > proj.damage || !proj.damage)
damage = FALSE
LAZYADDASSOC(targets_hit[target], "hits", 1)
LAZYSET(targets_hit[target], "damage", damage)
if(targets_hit[target]["hits"] == 1)
- RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_qdel), override=TRUE)
- UnregisterSignal(P, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_qdel), override = TRUE)
+ UnregisterSignal(proj, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
if(terminated == num_pellets)
finalize()
///One of our pellets disappeared due to hitting their max range (or just somehow got qdel'd), remove it from our list and check if we're done (terminated == num_pellets)
-/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/P)
+/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/proj)
SIGNAL_HANDLER
- pellets -= P
+ pellets -= proj
terminated++
- UnregisterSignal(P, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
+ UnregisterSignal(proj, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
if(terminated == num_pellets)
finalize()
/// Minor convenience function for creating each shrapnel piece with circle explosions, mostly stolen from the MIRV component
/datum/component/pellet_cloud/proc/pew(atom/target, landmine_victim)
- var/obj/projectile/P = new projectile_type(get_turf(parent))
+ var/obj/projectile/pellet = new projectile_type(get_turf(parent))
//Shooting Code:
- P.spread = 0
- P.original = target
- P.fired_from = parent
- P.firer = parent // don't hit ourself that would be really annoying
- P.impacted = list(parent = TRUE) // don't hit the target we hit already with the flak
- P.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message
- P.preparePixelProjectile(target, parent)
- RegisterSignal(P, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(pellet_hit))
- RegisterSignals(P, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_QDELETING), PROC_REF(pellet_range))
- pellets += P
- P.fire()
+ pellet.spread = 0
+ pellet.original = target
+ pellet.fired_from = parent
+ pellet.firer = parent // don't hit ourself that would be really annoying
+ pellet.impacted = list(WEAKREF(parent) = TRUE) // don't hit the target we hit already with the flak
+ pellet.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message
+ pellet.aim_projectile(target, parent)
+ RegisterSignal(pellet, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(pellet_hit))
+ RegisterSignals(pellet, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_QDELETING), PROC_REF(pellet_range))
+ pellets += pellet
+ pellet.fire()
if(landmine_victim)
- P.process_hit(get_turf(target), target)
+ pellet.impact(target)
///All of our pellets are accounted for, time to go target by target and tell them how many things they got hit by.
/datum/component/pellet_cloud/proc/finalize()
- var/obj/projectile/P = projectile_type
- var/proj_name = initial(P.name)
+ var/obj/projectile/proj_type = projectile_type
+ var/proj_name = initial(proj_type.name)
for(var/atom/target in targets_hit)
var/num_hits = targets_hit[target]["hits"]
@@ -303,24 +307,32 @@
hit_part = null //so the visible_message later on doesn't generate extra text.
else
target = hit_part.owner
- if(wound_info_by_part[hit_part] && (initial(P.damage_type) == BRUTE || initial(P.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds)
+ if(wound_info_by_part[hit_part] && (initial(proj_type.damage_type) == BRUTE || initial(proj_type.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds)
var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE]
var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS]
var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS]
- var/wounding_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling
+ var/wounding_type = (initial(proj_type.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling
+ var/sharpness = initial(proj_type.sharpness)
+
+ if(wounding_type == WOUND_BLUNT && sharpness)
+ if(sharpness & SHARP_EDGED)
+ wounding_type = WOUND_SLASH
+ else if (sharpness & SHARP_POINTY)
+ wounding_type = WOUND_PIERCE
+
wound_info_by_part -= hit_part
// technically this only checks armor worn the moment that all the pellets resolve rather than as each one hits you,
// but this isn't important enough to warrant all the extra loops of mostly redundant armor checks
var/mob/living/carbon/hit_carbon = target
- var/armor_factor = hit_carbon.getarmor(hit_part, initial(P.armor_flag))
+ var/armor_factor = hit_carbon.getarmor(hit_part, initial(proj_type.armor_flag))
armor_factor = min(ARMOR_MAX_BLOCK, armor_factor) //cap damage reduction at 90%
if(armor_factor > 0)
- if(initial(P.weak_against_armour) && armor_factor >= 0)
+ if(initial(proj_type.weak_against_armour) && armor_factor >= 0)
armor_factor *= ARMOR_WEAKENED_MULTIPLIER
damage_dealt *= max(0, 1 - armor_factor*0.01)
- hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness))
+ hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, sharpness)
var/limb_hit_text = ""
if(hit_part)
diff --git a/code/datums/components/scope.dm b/code/datums/components/scope.dm
index bae8298d66403..d0308e92073e5 100644
--- a/code/datums/components/scope.dm
+++ b/code/datums/components/scope.dm
@@ -116,6 +116,9 @@
/datum/component/scope/proc/start_zooming(mob/user)
if(!user.client)
return
+ if(HAS_TRAIT(user, TRAIT_USER_SCOPED))
+ user.balloon_alert(user, "already zoomed!")
+ return
user.client.mouse_override_icon = 'icons/effects/mouse_pointers/scope_hide.dmi'
user.update_mouse_pointer()
user.playsound_local(parent, 'sound/weapons/scope.ogg', 75, TRUE)
@@ -123,6 +126,7 @@
tracker.assign_to_mob(user, range_modifier)
RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, PROC_REF(stop_zooming))
START_PROCESSING(SSprojectiles, src)
+ ADD_TRAIT(user, TRAIT_USER_SCOPED, REF(src))
/**
* We stop zooming, canceling processing, resetting stuff back to normal and deleting our tracker.
@@ -133,12 +137,16 @@
/datum/component/scope/proc/stop_zooming(mob/user)
SIGNAL_HANDLER
+ if(!HAS_TRAIT(user, TRAIT_USER_SCOPED))
+ return
+
STOP_PROCESSING(SSprojectiles, src)
UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
if(user.client)
animate(user.client, 0.2 SECONDS, pixel_x = 0, pixel_y = 0)
user.client.mouse_override_icon = null
user.update_mouse_pointer()
+ REMOVE_TRAIT(user, TRAIT_USER_SCOPED, REF(src))
user.playsound_local(parent, 'sound/weapons/scope.ogg', 75, TRUE, frequency = -1)
tracker = null
user.clear_fullscreen("scope")
diff --git a/code/datums/components/tether.dm b/code/datums/components/tether.dm
index e76f5d5b53cd3..ef3d5472c8cc7 100644
--- a/code/datums/components/tether.dm
+++ b/code/datums/components/tether.dm
@@ -1,39 +1,212 @@
+/// Creates a tether between two objects that limits movement range. Tether requires LOS and can be adjusted by left/right clicking its
/datum/component/tether
- dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ /// Other side of the tether
var/atom/tether_target
+ /// Maximum (and initial) distance that this tether can be adjusted to
var/max_dist
+ /// What the tether is going to be called
var/tether_name
+ /// Current extension distance
+ var/cur_dist
+ /// Embedded item that the tether "should" originate from
+ var/atom/embed_target
+ /// Beam effect
+ var/datum/beam/tether_beam
+ /// Tether module if we were created by one
+ var/obj/item/mod/module/tether/parent_module
+ /// Source, if any, for TRAIT_TETHER_ATTACHED we add
+ var/tether_trait_source
+ /// If TRUE, only add TRAIT_TETHER_ATTACHED to our parent
+ var/no_target_trait
-/datum/component/tether/Initialize(atom/tether_target, max_dist = 4, tether_name)
- if(!isliving(parent) || !istype(tether_target) || !tether_target.loc)
+/datum/component/tether/Initialize(atom/tether_target, max_dist = 7, tether_name, atom/embed_target = null, start_distance = null, \
+ parent_module = null, tether_trait_source = null, no_target_trait = FALSE)
+ if(!ismovable(parent) || !istype(tether_target) || !tether_target.loc)
return COMPONENT_INCOMPATIBLE
src.tether_target = tether_target
+ src.embed_target = embed_target
src.max_dist = max_dist
+ src.parent_module = parent_module
+ src.tether_trait_source = tether_trait_source
+ src.no_target_trait = no_target_trait
+ cur_dist = max_dist
+ if (start_distance != null)
+ cur_dist = start_distance
+ var/datum/beam/beam = tether_target.Beam(parent, "line", 'icons/obj/clothing/modsuit/mod_modules.dmi', emissive = FALSE, beam_type = /obj/effect/ebeam/tether)
+ tether_beam = beam
if (ispath(tether_name, /atom))
var/atom/tmp = tether_name
src.tether_name = initial(tmp.name)
else
src.tether_name = tether_name
- RegisterSignals(parent, list(COMSIG_MOVABLE_PRE_MOVE), PROC_REF(checkTether))
+ if (!isnull(tether_trait_source))
+ ADD_TRAIT(parent, TRAIT_TETHER_ATTACHED, tether_trait_source)
+ if (!no_target_trait)
+ ADD_TRAIT(tether_target, TRAIT_TETHER_ATTACHED, tether_trait_source)
-/datum/component/tether/proc/checkTether(mob/mover, newloc)
+/datum/component/tether/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(check_tether))
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(check_snap))
+ RegisterSignal(tether_target, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(check_tether))
+ RegisterSignal(tether_target, COMSIG_MOVABLE_MOVED, PROC_REF(check_snap))
+ RegisterSignal(tether_target, COMSIG_QDELETING, PROC_REF(on_delete))
+ RegisterSignal(tether_beam.visuals, COMSIG_CLICK, PROC_REF(beam_click))
+ // Also snap if the beam gets deleted, more of a backup check than anything
+ RegisterSignal(tether_beam.visuals, COMSIG_QDELETING, PROC_REF(on_delete))
+
+ if (!isnull(embed_target))
+ RegisterSignal(embed_target, COMSIG_ITEM_UNEMBEDDED, PROC_REF(on_embedded_removed))
+ RegisterSignal(embed_target, COMSIG_QDELETING, PROC_REF(on_delete))
+
+/datum/component/tether/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED))
+ if (!isnull(tether_trait_source))
+ REMOVE_TRAIT(parent, TRAIT_TETHER_ATTACHED, tether_trait_source)
+ if (!QDELETED(tether_target))
+ UnregisterSignal(tether_target, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
+ if (!isnull(tether_trait_source) && !no_target_trait)
+ REMOVE_TRAIT(tether_target, TRAIT_TETHER_ATTACHED, tether_trait_source)
+ if (!QDELETED(tether_beam))
+ UnregisterSignal(tether_beam.visuals, list(COMSIG_CLICK, COMSIG_QDELETING))
+ qdel(tether_beam)
+ if (!QDELETED(embed_target))
+ UnregisterSignal(embed_target, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_QDELETING))
+
+/datum/component/tether/proc/check_tether(atom/source, new_loc)
SIGNAL_HANDLER
- if (get_dist(mover,newloc) > max_dist)
- to_chat(mover, span_userdanger("The [tether_name] runs out of slack and prevents you from moving!"))
+ if (check_snap())
+ return
+
+ if (!isturf(new_loc))
+ to_chat(source, span_warning("[tether_name] prevents you from entering [new_loc]!"))
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+ // If this was called, we know its a movable
+ var/atom/movable/movable_source = source
+ var/atom/movable/anchor = (source == tether_target ? parent : tether_target)
+ if (get_dist(anchor, new_loc) > cur_dist)
+ if (!istype(anchor) || anchor.anchored || !(!anchor.anchored && anchor.move_resist <= movable_source.move_force && anchor.Move(get_step_towards(anchor, new_loc))))
+ to_chat(source, span_warning("[tether_name] runs out of slack and prevents you from moving!"))
+ return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+
var/atom/blocker
- out:
- for(var/turf/T in get_line(tether_target,newloc))
- if (T.density)
- blocker = T
- break out
- for(var/a in T)
- var/atom/A = a
- if(A.density && A != mover && A != tether_target)
- blocker = A
- break out
+ var/anchor_dir = get_dir(source, anchor)
+ for (var/turf/line_turf in get_line(anchor, new_loc))
+ if (line_turf.density && line_turf != anchor.loc && line_turf != source.loc)
+ blocker = line_turf
+ break
+ if (line_turf == anchor.loc || line_turf == source.loc)
+ for (var/atom/in_turf in line_turf)
+ if ((in_turf.flags_1 & ON_BORDER_1) && (in_turf.dir & anchor_dir))
+ blocker = in_turf
+ break
+ else
+ for (var/atom/in_turf in line_turf)
+ if (in_turf.density && in_turf != source && in_turf != tether_target)
+ blocker = in_turf
+ break
+
+ if (!isnull(blocker))
+ break
+
if (blocker)
- to_chat(mover, span_userdanger("The [tether_name] catches on [blocker] and prevents you from moving!"))
+ to_chat(source, span_warning("[tether_name] catches on [blocker] and prevents you from moving!"))
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+
+ if (get_dist(anchor, new_loc) != cur_dist || !ismovable(source))
+ return
+
+ /* Need spacemove refactor from tg station
+ var/datum/drift_handler/handler = movable_source.drift_handler
+ if (handler)
+ handler.remove_angle_force(get_angle(anchor, source))
+ */
+
+/datum/component/tether/proc/check_snap()
+ SIGNAL_HANDLER
+
+ var/atom/atom_target = parent
+ // Something broke us out, snap the tether
+ if (get_dist(atom_target, tether_target) > cur_dist + 1 || !isturf(atom_target.loc) || !isturf(tether_target.loc) || atom_target.z != tether_target.z)
+ atom_target.visible_message(span_warning("[atom_target]'s [tether_name] snaps!"), span_userdanger("Your [tether_name] snaps!"), span_hear("You hear a cable snapping."))
+ qdel(src)
+
+/datum/component/tether/proc/on_delete()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/component/tether/proc/on_embedded_removed(atom/source, mob/living/victim)
+ SIGNAL_HANDLER
+ parent.AddComponent(/datum/component/tether, source, max_dist, tether_name, cur_dist)
+ qdel(src)
+
+/datum/component/tether/proc/beam_click(atom/source, atom/location, control, params, mob/user)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(process_beam_click), source, location, params, user)
+
+/datum/component/tether/proc/process_beam_click(atom/source, atom/location, params, mob/user)
+ var/turf/nearest_turf
+ for (var/turf/line_turf in get_line(get_turf(parent), get_turf(tether_target)))
+ if (user.CanReach(line_turf))
+ nearest_turf = line_turf
+ break
+
+ if (isnull(nearest_turf))
+ return
+
+ if (!user.can_perform_action(nearest_turf))
+ nearest_turf.balloon_alert(user, "cannot reach!")
+ return
+
+ var/list/modifiers = params2list(params)
+ if(LAZYACCESS(modifiers, CTRL_CLICK))
+ location.balloon_alert(user, "cutting the tether...")
+ if (!do_after(user, 2 SECONDS, user))
+ return
+
+ qdel(src)
+ location.balloon_alert(user, "tether cut!")
+ to_chat(parent, span_danger("Your [tether_name] has been cut!"))
+ return
+
+ if (LAZYACCESS(modifiers, RIGHT_CLICK))
+ if (cur_dist >= max_dist)
+ location.balloon_alert(user, "no coil remaining!")
+ return
+ cur_dist += 1
+ location.balloon_alert(user, "tether extended")
+ return
+
+ if (cur_dist <= 1)
+ location.balloon_alert(user, "too short!")
+ return
+
+ if (cur_dist > get_dist(parent, tether_target))
+ cur_dist -= 1
+ location.balloon_alert(user, "tether shortened")
+ return
+
+ if (!ismovable(parent) && !ismovable(tether_target))
+ location.balloon_alert(user, "too short!")
+ return
+
+ var/atom/movable/movable_parent = parent
+ var/atom/movable/movable_target = tether_target
+
+ if (istype(movable_parent) && !movable_parent.anchored && movable_parent.move_resist <= movable_target.move_force && movable_parent.Move(get_step(movable_parent.loc, get_dir(movable_parent, movable_target))))
+ cur_dist -= 1
+ location.balloon_alert(user, "tether shortened")
+ return
+
+ if (istype(movable_target) && !movable_target.anchored && movable_target.move_resist <= movable_parent.move_force && movable_target.Move(get_step(movable_target.loc, get_dir(movable_target, movable_parent))))
+ cur_dist -= 1
+ location.balloon_alert(user, "tether shortened")
+ return
+
+ location.balloon_alert(user, "too short!")
+
+/obj/effect/ebeam/tether
+ mouse_opacity = MOUSE_OPACITY_ICON
diff --git a/code/datums/elements/backblast.dm b/code/datums/elements/backblast.dm
index 169f961b3d373..a00d4db633032 100644
--- a/code/datums/elements/backblast.dm
+++ b/code/datums/elements/backblast.dm
@@ -70,5 +70,5 @@
P.fired_from = weapon
P.firer = user // don't hit ourself that would be really annoying
P.impacted = list(user = TRUE) // don't hit the target we hit already with the flak
- P.preparePixelProjectile(target_turf, weapon)
+ P.aim_projectile(target_turf, weapon)
P.fire()
diff --git a/code/datums/elements/caseless.dm b/code/datums/elements/caseless.dm
index 253810436aaf4..c91729f36946d 100644
--- a/code/datums/elements/caseless.dm
+++ b/code/datums/elements/caseless.dm
@@ -8,7 +8,7 @@
argument_hash_start_idx = 2
var/reusable = FALSE
-/datum/element/caseless/Attach(datum/target, reusable = FALSE)
+/datum/element/caseless/Attach(datum/target, reusable)
. = ..()
if(!isammocasing(target))
return ELEMENT_INCOMPATIBLE
@@ -28,13 +28,11 @@
/datum/element/caseless/proc/on_fired_casing(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro, obj/projectile/proj)
SIGNAL_HANDLER
+ if(!proj)
+ return
if(isgun(fired_from))
var/obj/item/gun/shot_from = fired_from
if(shot_from.chambered == shell)
shot_from.chambered = null //Nuke it. Nuke it now.
- if(istype(shot_from, /obj/item/gun/ballistic/revolver/sol))
- var/obj/item/ammo_box/magazine/internal/cylinder/c35sol/cylinder1 = shot_from.contents[2]
- cylinder1.stored_ammo[1] = null
- return
- QDEL_NULL(shell)
+ qdel(shell)
diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm
deleted file mode 100644
index 1b83f3642c84b..0000000000000
--- a/code/datums/elements/embed.dm
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- The presence of this element allows an item (or a projectile carrying an item) to embed itself in a carbon when it is thrown into a target (whether by hand, gun, or explosive wave) with either
- at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. Items meant to be used as shrapnel for projectiles should have ignore_throwspeed_threshold set to true.
-
- Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them.
- Items simply make their COMSIG_MOVABLE_IMPACT_ZONE check, while projectiles check on COMSIG_PROJECTILE_SELF_ON_HIT.
- Upon a projectile hitting a valid target, it spawns whatever type of payload it has defined, then has that try to embed itself in the target on its own.
-
- Otherwise non-embeddable or stickable items can be made embeddable/stickable through wizard events/sticky tape/admin memes.
-*/
-
-/datum/element/embed
- element_flags = ELEMENT_BESPOKE
- argument_hash_start_idx = 2
- var/initialized = FALSE /// whether we can skip assigning all the vars (since these are bespoke elements, we don't have to reset the vars every time we attach to something, we already know what we are!)
-
- // all of this stuff is explained in _DEFINES/combat.dm
- var/embed_chance
- var/fall_chance
- var/pain_chance
- var/pain_mult
- var/remove_pain_mult
- var/impact_pain_mult
- var/rip_time
- var/ignore_throwspeed_threshold
- var/jostle_chance
- var/jostle_pain_mult
- var/pain_stam_pct
- var/payload_type
-
-/datum/element/embed/Attach(datum/target, embed_chance, fall_chance, pain_chance, pain_mult, remove_pain_mult, impact_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct, projectile_payload=/obj/item/shard)
- . = ..()
-
- if(!isitem(target) && !isprojectile(target))
- return ELEMENT_INCOMPATIBLE
-
- RegisterSignal(target, COMSIG_ELEMENT_ATTACH, PROC_REF(severancePackage))
- if(isitem(target))
- RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(checkEmbed))
- RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(examined))
- RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, PROC_REF(tryForceEmbed))
- RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, PROC_REF(detachFromWeapon))
- if(!initialized)
- src.embed_chance = embed_chance
- src.fall_chance = fall_chance
- src.pain_chance = pain_chance
- src.pain_mult = pain_mult
- src.remove_pain_mult = remove_pain_mult
- src.rip_time = rip_time
- src.impact_pain_mult = impact_pain_mult
- src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
- src.jostle_chance = jostle_chance
- src.jostle_pain_mult = jostle_pain_mult
- src.pain_stam_pct = pain_stam_pct
- initialized = TRUE
- else
- payload_type = projectile_payload
- RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(checkEmbedProjectile))
-
-
-/datum/element/embed/Detach(obj/target)
- . = ..()
- if(isitem(target))
- UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_ATOM_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED))
- else
- UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT, COMSIG_ELEMENT_ATTACH))
-
-
-/// Checking to see if we're gonna embed into a human
-/datum/element/embed/proc/checkEmbed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum, forced=FALSE)
- SIGNAL_HANDLER
-
- if(forced)
- embed_object(weapon, victim, hit_zone, throwingdatum)
- return TRUE
-
- if(blocked || !istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
- return FALSE
-
- if(HAS_TRAIT(victim, TRAIT_GODMODE))
- return FALSE
-
- var/flying_speed = throwingdatum?.speed || weapon.throw_speed
-
- if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)
- return FALSE
-
- if(!roll_embed_chance(weapon, victim, hit_zone, throwingdatum))
- return FALSE
-
- embed_object(weapon, victim, hit_zone, throwingdatum)
- return TRUE
-
-/// Actually sticks the object to a victim
-/datum/element/embed/proc/embed_object(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
- var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts)
- victim.AddComponent(/datum/component/embedded,\
- weapon,\
- throwingdatum,\
- part = limb,\
- embed_chance = embed_chance,\
- fall_chance = fall_chance,\
- pain_chance = pain_chance,\
- pain_mult = pain_mult,\
- remove_pain_mult = remove_pain_mult,\
- rip_time = rip_time,\
- ignore_throwspeed_threshold = ignore_throwspeed_threshold,\
- jostle_chance = jostle_chance,\
- jostle_pain_mult = jostle_pain_mult,\
- pain_stam_pct = pain_stam_pct)
- SEND_SIGNAL(victim, COMSIG_CARBON_EMBED_ADDED, weapon, limb)
-
-///A different embed element has been attached, so we'll detach and let them handle things
-/datum/element/embed/proc/severancePackage(obj/weapon, datum/element/E)
- SIGNAL_HANDLER
-
- if(istype(E, /datum/element/embed))
- Detach(weapon)
-
-///If we don't want to be embeddable anymore (deactivating an e-dagger for instance)
-/datum/element/embed/proc/detachFromWeapon(obj/weapon)
- SIGNAL_HANDLER
-
- Detach(weapon)
-
-///Someone inspected our embeddable item
-/datum/element/embed/proc/examined(obj/item/I, mob/user, list/examine_list)
- SIGNAL_HANDLER
-
- if(I.isEmbedHarmless())
- examine_list += "[I] feels sticky, and could probably get stuck to someone if thrown properly!"
- else
- examine_list += "[I] has a fine point, and could probably embed in someone if thrown properly!"
-
-/**
- * checkEmbedProjectile() is what we get when a projectile with a defined shrapnel_type impacts a target.
- *
- * If we hit a valid target, we create the shrapnel_type object and immediately call tryEmbed() on it, targeting what we impacted. That will lead
- * it to call tryForceEmbed() on its own embed element (it's out of our hands here, our projectile is done), where it will run through all the checks it needs to.
- */
-/datum/element/embed/proc/checkEmbedProjectile(obj/projectile/P, atom/movable/firer, atom/hit, angle, hit_zone)
- SIGNAL_HANDLER
-
- if(!iscarbon(hit) || HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE))
- Detach(P)
- return // we don't care
-
- var/obj/item/payload = new payload_type(get_turf(hit))
- if(istype(payload, /obj/item/shrapnel/bullet))
- payload.name = P.name
- payload.embedding = P.embedding
- payload.updateEmbedding()
- var/mob/living/carbon/C = hit
- var/obj/item/bodypart/limb = C.get_bodypart(hit_zone)
- if(!limb)
- limb = C.get_bodypart()
-
- payload.tryEmbed(limb) // at this point we've created our shrapnel baby and set them up to embed in the target, we can now die in peace as they handle their embed try on their own
- Detach(P)
-
-/**
- * tryForceEmbed() is called here when we fire COMSIG_EMBED_TRY_FORCE from [/obj/item/proc/tryEmbed]. Mostly, this means we're a piece of shrapnel from a projectile that just impacted something, and we're trying to embed in it.
- *
- * The reason for this extra mucking about is avoiding having to do an extra hitby(), and annoying the target by impacting them once with the projectile, then again with the shrapnel, and possibly
- * AGAIN if we actually embed. This way, we save on at least one message.
- *
- * Arguments:
- * * embedding_item- the item we're trying to insert into the target
- * * target- what we're trying to shish-kabob, either a bodypart or a carbon
- * * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting.
- * * forced- if we want this to succeed 100%
- */
-/datum/element/embed/proc/tryForceEmbed(obj/item/embedding_item, atom/target, hit_zone, forced=FALSE)
- SIGNAL_HANDLER
-
- var/obj/item/bodypart/limb
- var/mob/living/carbon/victim
-
- if(iscarbon(target))
- victim = target
- if(!hit_zone)
- limb = pick(victim.bodyparts)
- hit_zone = limb.body_zone
- else if(isbodypart(target))
- limb = target
- hit_zone = limb.body_zone
- victim = limb.owner
-
- if(!forced && !roll_embed_chance(embedding_item, victim, hit_zone))
- return
-
- return checkEmbed(embedding_item, victim, hit_zone, forced=TRUE) // Don't repeat the embed roll, we already did it
-
-/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check
-/datum/element/embed/proc/roll_embed_chance(obj/item/embedding_item, mob/living/victim, hit_zone, datum/thrownthing/throwingdatum)
- var/actual_chance = embed_chance
-
- // MONKESTATION ADDITION START
- if(HAS_TRAIT(victim, TRAIT_EMBED_RESISTANCE))
- if(prob(50))
- return FALSE
- // MONKESTATION ADDITION END
- if(throwingdatum?.speed > embedding_item.throw_speed)
- actual_chance += (throwingdatum.speed - embedding_item.throw_speed) * EMBED_CHANCE_SPEED_BONUS
-
- if(embedding_item.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
- return prob(actual_chance)
-
- var/armor = max(victim.run_armor_check(hit_zone, BULLET, silent=TRUE), victim.run_armor_check(hit_zone, BOMB, silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved
- if(!armor) // we only care about armor penetration if there's actually armor to penetrate
- return prob(actual_chance)
-
- //Keep this above 1, as it is a multiplier for the pen_mod for determining actual embed chance.
- var/penetrative_behaviour = embedding_item.weak_against_armour ? ARMOR_WEAKENED_MULTIPLIER : 1
- var/pen_mod = -(armor * penetrative_behaviour) // if our shrapnel is weak into armor, then we restore our armor to the full value.
- actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on
- if(actual_chance <= 0)
- victim.visible_message(span_danger("[embedding_item] bounces off [victim]'s armor, unable to embed!"), span_notice("[embedding_item] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
- return FALSE
-
- return prob(actual_chance)
diff --git a/code/datums/elements/ranged_armour.dm b/code/datums/elements/ranged_armour.dm
index 61a3bc647d628..cd29f5d86a240 100644
--- a/code/datums/elements/ranged_armour.dm
+++ b/code/datums/elements/ranged_armour.dm
@@ -40,7 +40,7 @@
return ..()
/// Modify or ignore bullet damage based on projectile properties
-/datum/element/ranged_armour/proc/pre_bullet_impact(atom/parent, list/signal_args, obj/projectile/bullet)
+/datum/element/ranged_armour/proc/pre_bullet_impact(atom/parent, obj/projectile/bullet)
SIGNAL_HANDLER
if (bullet.damage >= minimum_projectile_force || (bullet.damage_type in vulnerable_projectile_types))
return
diff --git a/code/datums/elements/relay_attackers.dm b/code/datums/elements/relay_attackers.dm
index 83e83a40e7cee..270ba40ec9608 100644
--- a/code/datums/elements/relay_attackers.dm
+++ b/code/datums/elements/relay_attackers.dm
@@ -57,7 +57,7 @@
relay_attacker(target, attacker, ATTACKER_DAMAGING_ATTACK)
/// Even if another component blocked this hit, someone still shot at us
-/datum/element/relay_attackers/proc/on_bullet_act(atom/target, list/bullet_args, obj/projectile/hit_projectile)
+/datum/element/relay_attackers/proc/on_bullet_act(atom/target, obj/projectile/hit_projectile)
SIGNAL_HANDLER
if(!hit_projectile.is_hostile_projectile())
return
diff --git a/code/datums/elements/selfknockback.dm b/code/datums/elements/selfknockback.dm
index d330b30debc1a..416240b7cfd33 100644
--- a/code/datums/elements/selfknockback.dm
+++ b/code/datums/elements/selfknockback.dm
@@ -47,15 +47,15 @@ clamping the Knockback_Force value below. */
usertarget.throw_at(move_target, knockback_force, knockback_speed)
usertarget.visible_message(span_warning("[usertarget] gets thrown back by the force of \the [I] impacting \the [attacktarget]!"), span_warning("The force of \the [I] impacting \the [attacktarget] sends you flying!"))
-/datum/element/selfknockback/proc/Projectile_SelfKnockback(obj/projectile/P)
+/datum/element/selfknockback/proc/Projectile_SelfKnockback(obj/projectile/proj)
SIGNAL_HANDLER
- if(!P.firer)
+ if(!proj.firer)
return
- var/knockback_force = Get_Knockback_Force(clamp(CEILING((P.damage / 10), 1), 1, 5))
+ var/knockback_force = Get_Knockback_Force(clamp(CEILING((proj.damage / 10), 1), 1, 5))
var/knockback_speed = Get_Knockback_Speed(clamp(knockback_force, 1, 5))
- var/atom/movable/knockback_target = P.firer
- var/move_target = get_edge_target_turf(knockback_target, angle2dir(P.original_angle+180))
+ var/atom/movable/knockback_target = proj.firer
+ var/move_target = get_edge_target_turf(knockback_target, angle2dir(proj.original_angle+180))
knockback_target.throw_at(move_target, knockback_force, knockback_speed)
diff --git a/code/datums/embedding.dm b/code/datums/embedding.dm
new file mode 100644
index 0000000000000..f9bea7fdd818f
--- /dev/null
+++ b/code/datums/embedding.dm
@@ -0,0 +1,606 @@
+/// How quicker is it for someone else to rip out an item?
+#define RIPPING_OUT_HELP_TIME_MULTIPLIER 0.75
+/// How much safer is it for someone else to rip out an item?
+#define RIPPING_OUT_HELP_DAMAGE_MULTIPLIER 0.75
+
+/*
+ * The magical embedding datum which is a container for all embedding interactions an item (or a projectile) can have.
+ * Whenever an item with an embedding datum is thrown into a carbon with either EMBED_THROWSPEED_THRESHOLD throwspeed or ignore_throwspeed_threshold set to TRUE, it will
+ * embed into them, with latter option reserved for sticky items and shrapnel.
+ * Whenever a projectile embeds, the datum is copied onto the shrapnel
+ */
+
+/datum/embedding
+ /// Chance for an object to embed into somebody when thrown
+ var/embed_chance = 45
+ /// Chance for embedded object to fall out (causing pain but removing the object)
+ var/fall_chance = 5
+ /// Chance for embedded objects to cause pain (damage user)
+ var/pain_chance = 15
+ /// Coefficient of multiplication for the damage the item does while embedded (this*item.w_class)
+ var/pain_mult = 2
+ /// Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class)
+ var/impact_pain_mult = 4
+ /// Coefficient of multiplication for the damage the item does when it falls out or is removed without a surgery (this*item.w_class)
+ var/remove_pain_mult = 6
+ /// Time in ticks, total removal time = (this*item.w_class)
+ var/rip_time = 3 SECONDS
+ /// If this should ignore throw speed threshold of 4
+ var/ignore_throwspeed_threshold = FALSE
+ /// Chance for embedded objects to cause pain every time they move (jostle)
+ var/jostle_chance = 5
+ /// Coefficient of multiplication for the damage the item does while
+ var/jostle_pain_mult = 1
+ /// This percentage of all pain will be dealt as stam damage rather than brute (0-1)
+ var/pain_stam_pct = 0
+ /// Traits which make target immune to us embedding into them, any trait from the list works
+ var/list/immune_traits = list(TRAIT_PIERCEIMMUNE)
+
+ /// Thing that we're attached to
+ VAR_FINAL/obj/item/parent
+ /// Mob we've embedded into, if any
+ VAR_FINAL/mob/living/carbon/owner
+ /// Limb we've embedded into in whose contents we reside
+ VAR_FINAL/obj/item/bodypart/owner_limb
+
+/datum/embedding/New(obj/item/creator)
+ . = ..()
+ if (creator)
+ register_on(creator)
+
+/// Registers ourselves with an item
+/datum/embedding/proc/register_on(obj/item/new_parent)
+ if(!isitem(new_parent))
+ CRASH("Embedding datum attempted to register on a non-item object [new_parent] ([new_parent?.type])")
+
+ parent = new_parent
+ RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_qdel))
+
+ RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(try_embed))
+ //RegisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(examined_tags))
+
+/datum/embedding/Destroy(force)
+ if (!parent)
+ return ..()
+ parent.set_embed(null)
+ UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ATOM_EXAMINE))
+ owner = null
+ owner_limb = null
+ parent = null
+ return ..()
+
+/// Creates a copy and sets all of its *relevant* variables
+/// Children should override this with new variables if they add any "generic" ones
+/datum/embedding/proc/create_copy(atom/movable/new_owner)
+ var/datum/embedding/brother = new type(new_owner)
+ brother.embed_chance = embed_chance
+ brother.fall_chance = fall_chance
+ brother.pain_chance = pain_chance
+ brother.pain_mult = pain_mult
+ brother.impact_pain_mult = impact_pain_mult
+ brother.remove_pain_mult = remove_pain_mult
+ brother.rip_time = rip_time
+ brother.ignore_throwspeed_threshold = ignore_throwspeed_threshold
+ brother.jostle_chance = jostle_chance
+ brother.jostle_pain_mult = jostle_pain_mult
+ brother.pain_stam_pct = pain_stam_pct
+ brother.immune_traits = immune_traits.Copy()
+ return brother
+
+///Someone inspected our embeddable item
+/datum/embedding/proc/examined_tags(obj/item/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(is_harmless())
+ examine_list["sticky"] = "[parent] looks sticky, and could probably get stuck to someone if thrown properly!"
+ else
+ examine_list["embeddable"] = "[parent] has a fine point, and could probably embed in someone if thrown properly!"
+
+/// Is passed victim a valid target for us to embed into?
+/datum/embedding/proc/can_embed(atom/movable/source, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
+ if (!istype(victim))
+ return FALSE
+
+ if (HAS_TRAIT(victim, TRAIT_GODMODE))
+ return
+
+ if (immune_traits)
+ for (var/immunity_trait in immune_traits)
+ if (HAS_TRAIT(victim, immunity_trait))
+ return FALSE
+
+ if (isitem(source))
+ var/flying_speed = throwingdatum?.speed || source.throw_speed
+ if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)
+ return FALSE
+
+ return TRUE
+
+/// Attempts to embed an object
+/datum/embedding/proc/try_embed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum)
+ SIGNAL_HANDLER
+
+ if (blocked || !can_embed(parent, victim, hit_zone, throwingdatum))
+ failed_embed(victim, hit_zone)
+ return
+
+ if (!roll_embed_chance(victim, hit_zone, throwingdatum))
+ failed_embed(victim, hit_zone, random = TRUE)
+ return
+
+ var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1]
+ embed_into(victim, limb)
+ return MOVABLE_IMPACT_ZONE_OVERRIDE
+
+/// Attempts to embed shrapnel from a projectile
+/datum/embedding/proc/try_embed_projectile(obj/projectile/source, atom/hit, hit_zone, blocked, pierce_hit)
+ if (pierce_hit)
+ return
+
+ if (blocked >= 100 || !can_embed(source, hit))
+ failed_embed(hit, hit_zone)
+ return
+
+ var/mob/living/carbon/victim = hit
+ var/obj/item/payload = setup_shrapnel(source, victim)
+
+ if (!roll_embed_chance(victim, hit_zone))
+ failed_embed(victim, hit_zone, random = TRUE)
+ return
+
+ var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1]
+ embed_into(victim, limb)
+ SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_EMBEDDED, payload, hit)
+
+/// Used for custom logic while setting up shrapnel payload
+/datum/embedding/proc/setup_shrapnel(obj/projectile/source, mob/living/carbon/victim)
+ var/shrapnel_type = source.shrapnel_type
+ var/obj/item/payload = new shrapnel_type(get_turf(victim))
+ // Detach from parent, we don't want em to delete us
+ source.set_embed(null, dont_delete = TRUE)
+ // Hook signals up first, as payload sends a comsig upon embed update
+ register_on(payload)
+ payload.set_embed(src)
+ if(istype(payload, /obj/item/shrapnel/bullet))
+ payload.name = source.name
+ SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, payload, victim)
+
+/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check
+/datum/embedding/proc/roll_embed_chance(mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
+ var/chance = embed_chance
+
+ // Something threw us really, really fast
+ if (throwingdatum?.speed > parent.throw_speed)
+ chance += (throwingdatum.speed - parent.throw_speed) * EMBED_CHANCE_SPEED_BONUS
+
+ if (is_harmless())
+ return prob(embed_chance)
+
+ // We'll be nice and take the better of bullet and bomb armor, halved
+ var/armor = max(victim.run_armor_check(hit_zone, BULLET, armour_penetration = parent.armour_penetration, silent = TRUE), victim.run_armor_check(hit_zone, BOMB, armour_penetration = parent.armour_penetration, silent = TRUE)) * 0.5
+ // We only care about armor penetration if there's actually armor to penetrate
+ if(!armor)
+ return prob(chance)
+
+ if (parent.weak_against_armour)
+ armor *= ARMOR_WEAKENED_MULTIPLIER
+
+ chance -= armor
+ if (chance < 0)
+ victim.visible_message(span_danger("[parent] bounces off [victim]'s armor, unable to embed!"),
+ span_notice("[parent] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
+ return FALSE
+
+ return prob(chance)
+
+/// We've tried to embed into something and failed
+/// Random being TRUE means we've lost the roulette, FALSE means we've either been blocked or the target is invalid
+/datum/embedding/proc/failed_embed(mob/living/carbon/victim, hit_zone, random = FALSE)
+ if (!istype(parent))
+ return
+ SEND_SIGNAL(parent, COMSIG_ITEM_FAILED_EMBED, victim, hit_zone)
+ if((parent.item_flags & DROPDEL) && !QDELETED(parent))
+ qdel(parent)
+
+/// Does this item deal any damage when embedding or jostling inside of someone?
+/datum/embedding/proc/is_harmless(consider_stamina = FALSE)
+ return pain_mult == 0 && jostle_pain_mult == 0 && (consider_stamina || pain_stam_pct < 1)
+
+//Handles actual embedding logic.
+/datum/embedding/proc/embed_into(mob/living/carbon/victim, obj/item/bodypart/target_limb)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ set_owner(victim, target_limb)
+
+ START_PROCESSING(SSprocessing, src)
+ owner_limb._embed_object(parent)
+ parent.forceMove(owner)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(weapon_disappeared))
+ RegisterSignal(parent, COMSIG_MAGIC_RECALL, PROC_REF(magic_pull))
+ owner.visible_message(span_danger("[parent] [is_harmless() ? "sticks itself to" : "embeds itself in"] [owner]'s [owner_limb.plaintext_zone]!"),
+ span_userdanger("[parent] [is_harmless() ? "sticks itself to" : "embeds itself in"] your [owner_limb.plaintext_zone]!"))
+
+ var/damage = parent.throwforce
+ if (!is_harmless(consider_stamina = TRUE))
+ owner.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject)
+ if (!is_harmless())
+ playsound(owner,'sound/weapons/bladeslice.ogg', 40)
+ if (owner_limb.can_bleed())
+ parent.add_mob_blood(owner) // it embedded itself in you, of course it's bloody!
+ owner.add_mood_event("embedded", /datum/mood_event/embedded)
+ damage += parent.w_class * impact_pain_mult
+
+ SEND_SIGNAL(parent, COMSIG_ITEM_EMBEDDED, victim, target_limb)
+ on_successful_embed(victim, target_limb)
+
+ if (damage <= 0)
+ return TRUE
+
+ var/armor = owner.run_armor_check(owner_limb.body_zone, MELEE, "Your armor has protected your [owner_limb.plaintext_zone].",
+ "Your armor has softened a hit to your [owner_limb.plaintext_zone].", parent.armour_penetration,
+ weak_against_armour = parent.weak_against_armour,
+ )
+
+ owner.apply_damage(
+ damage = (1 - pain_stam_pct) * damage,
+ damagetype = BRUTE,
+ def_zone = owner_limb.body_zone,
+ blocked = armor,
+ wound_bonus = parent.wound_bonus,
+ bare_wound_bonus = parent.bare_wound_bonus,
+ sharpness = parent.get_sharpness(),
+ attacking_item = parent,
+ )
+
+ owner.apply_damage(
+ damage = pain_stam_pct * damage,
+ damagetype = STAMINA,
+ )
+ return TRUE
+
+/// Proc which is called upon successfully embedding into someone/something, for children to override
+/datum/embedding/proc/on_successful_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb)
+ return
+
+/// Registers signals that our owner should have
+/// Handles jostling, tweezing embedded items out and grenade chain reactions
+/datum/embedding/proc/set_owner(mob/living/carbon/victim, obj/item/bodypart/target_limb)
+ owner = victim
+ owner_limb = target_limb
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(owner_moved))
+ RegisterSignal(owner, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
+ RegisterSignal(owner, COMSIG_ATOM_EX_ACT, PROC_REF(on_ex_act))
+ RegisterSignal(owner_limb, COMSIG_BODYPART_REMOVED, PROC_REF(on_removed))
+
+/// Avoid calling this directly as this doesn't move the object from its owner's contents
+/// Returns TRUE if the item got deleted due to DROPDEL flag
+/datum/embedding/proc/stop_embedding()
+ STOP_PROCESSING(SSprocessing, src)
+ if (owner_limb)
+ UnregisterSignal(owner_limb, COMSIG_BODYPART_REMOVED)
+ owner_limb._unembed_object(parent)
+ if (owner)
+ UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EX_ACT))
+ if (!owner.has_embedded_objects())
+ owner.clear_alert(ALERT_EMBEDDED_OBJECT)
+ owner.clear_mood_event("embedded")
+ UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MAGIC_RECALL))
+ SEND_SIGNAL(parent, COMSIG_ITEM_UNEMBEDDED, owner, owner_limb)
+ owner = null
+ owner_limb = null
+ if((parent.item_flags & DROPDEL) && !QDELETED(parent))
+ qdel(parent)
+ return TRUE
+ return FALSE
+
+/datum/embedding/proc/on_qdel(atom/movable/source)
+ SIGNAL_HANDLER
+ if (owner_limb)
+ weapon_disappeared()
+ qdel(src)
+
+/// Move self to owner's turf when our limb gets removed
+/datum/embedding/proc/on_removed(datum/source, mob/living/carbon/old_owner)
+ SIGNAL_HANDLER
+ if (!stop_embedding()) // Dropdel?
+ parent.forceMove(old_owner.drop_location())
+
+/// Someone attempted to pull us out! Either the owner by inspecting themselves, or someone else by examining the owner and clicking the link.
+/datum/embedding/proc/rip_out(mob/living/jack_the_ripper)
+ if (!jack_the_ripper.CanReach(owner))
+ return
+
+ if (!jack_the_ripper.can_perform_action(owner, FORBID_TELEKINESIS_REACH | NEED_HANDS | ALLOW_RESTING))
+ return
+
+ var/time_taken = rip_time * parent.w_class
+ var/damage_mult = 1
+ if (jack_the_ripper != owner)
+ time_taken *= RIPPING_OUT_HELP_TIME_MULTIPLIER
+ damage_mult *= RIPPING_OUT_HELP_DAMAGE_MULTIPLIER
+ owner.visible_message(span_warning("[jack_the_ripper] attempts to remove [parent] from [owner]'s [owner_limb.plaintext_zone]!"),
+ span_userdanger("[jack_the_ripper] attempt to remove [parent] from your [owner_limb.plaintext_zone]!"), ignored_mobs = jack_the_ripper)
+ to_chat(jack_the_ripper, span_notice("You attempt to remove [parent] from [owner]'s [owner_limb.plaintext_zone]..."))
+ else
+ owner.visible_message(span_warning("[owner] attempts to remove [parent] from [owner.p_their()] [owner_limb.plaintext_zone]."),
+ span_notice("You attempt to remove [parent] from your [owner_limb.plaintext_zone]..."))
+
+ if (!do_after(jack_the_ripper, time_taken, owner, extra_checks = CALLBACK(src, PROC_REF(still_in))))
+ return
+
+ if (parent.loc != owner || !(parent in owner_limb?.embedded_objects))
+ return
+
+ if (jack_the_ripper == owner)
+ owner.visible_message(span_notice("[owner] successfully rips [parent] [is_harmless() ? "off" : "out"] of [owner.p_their()] [owner_limb.plaintext_zone]!"),
+ span_notice("You successfully remove [parent] from your [owner_limb.plaintext_zone]."))
+ else
+ owner.visible_message(span_notice("[jack_the_ripper] successfully rips [parent] [is_harmless() ? "off" : "out"] of [owner]'s [owner_limb.plaintext_zone]!"),
+ span_userdanger("[jack_the_ripper] removes [parent] from your [owner_limb.plaintext_zone]!"), ignored_mobs = jack_the_ripper)
+ to_chat(jack_the_ripper, span_notice("You successfully remove [parent] from [owner]'s [owner_limb.plaintext_zone]."))
+
+ if (!is_harmless())
+ damaging_removal_effect(damage_mult)
+ remove_embedding(jack_the_ripper)
+
+/// Handles damage effects upon forceful removal
+/datum/embedding/proc/damaging_removal_effect(ouchies_multiplier)
+ var/damage = parent.w_class * remove_pain_mult * ouchies_multiplier
+ owner.apply_damage(
+ damage = (1 - pain_stam_pct) * damage,
+ damagetype = BRUTE,
+ def_zone = owner_limb,
+ wound_bonus = max(0, parent.wound_bonus), // It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow
+ sharpness = parent.get_sharpness() || SHARP_EDGED, // always sharp, even if the object isn't
+ attacking_item = parent,
+ )
+
+ owner.apply_damage(
+ damage = pain_stam_pct * damage,
+ damagetype = STAMINA,
+ )
+
+ owner.emote("scream")
+
+/// The proper proc to call when you want to remove something. If a mob is passed, the item will be put in its hands - otherwise its just dumped onto the ground
+/datum/embedding/proc/remove_embedding(mob/living/to_hands)
+ var/mob/living/carbon/stored_owner = owner
+ if (stop_embedding()) // Dropdel?
+ return
+ parent.forceMove(stored_owner.drop_location())
+
+/// When owner moves around, attempt to jostle the item
+/datum/embedding/proc/owner_moved(mob/living/carbon/source, atom/old_loc, dir, forced, list/old_locs)
+ SIGNAL_HANDLER
+
+ var/chance = jostle_chance
+ if(!forced && (owner.m_intent == MOVE_INTENT_WALK || owner.body_position == LYING_DOWN) && !CHECK_MOVE_LOOP_FLAGS(source, MOVEMENT_LOOP_OUTSIDE_CONTROL))
+ chance *= 0.5
+
+ if(is_harmless(consider_stamina = TRUE) || !prob(chance))
+ return
+
+ var/damage = parent.w_class * jostle_pain_mult
+ owner.apply_damage(
+ damage = (1 - pain_stam_pct) * damage,
+ damagetype = BRUTE,
+ def_zone = owner_limb,
+ wound_bonus = CANT_WOUND,
+ sharpness = parent.get_sharpness(),
+ attacking_item = parent,
+ )
+
+ owner.apply_damage(
+ damage = pain_stam_pct * damage,
+ damagetype = STAMINA,
+ )
+
+ to_chat(owner, span_userdanger("[parent] embedded in your [owner_limb.plaintext_zone] jostles and stings!"))
+ jostle_effects()
+
+/// Effects which should occur when the owner moves, sometimes
+/datum/embedding/proc/jostle_effects()
+ return
+
+/// When someone attempts to pluck us with tweezers or wirecutters
+/datum/embedding/proc/on_attackby(mob/living/carbon/victim, obj/item/tool, mob/user)
+ SIGNAL_HANDLER
+
+ if (user.zone_selected != owner_limb.body_zone || (tool.tool_behaviour != TOOL_HEMOSTAT && tool.tool_behaviour != TOOL_WIRECUTTER))
+ return
+
+ if (parent != owner_limb.embedded_objects[1]) // Don't pluck everything at the same time
+ return
+
+ // Ensure that we can actually
+ if (!owner.try_inject(user, owner_limb.body_zone, INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE))
+ return COMPONENT_NO_AFTERATTACK
+
+ INVOKE_ASYNC(src, PROC_REF(try_pluck), tool, user)
+ return COMPONENT_NO_AFTERATTACK
+
+/datum/embedding/process(seconds_per_tick)
+ if (!owner || !owner_limb || owner_limb.owner != owner)
+ stack_trace("Attempted to process embedding on [parent] ([parent.type]) without an owner, owner_limb or owner-less limb!")
+ parent.forceMove(get_turf(parent))
+ return
+
+ if (process_effect(seconds_per_tick))
+ return
+
+ if (owner.stat == DEAD)
+ return
+
+ var/fall_chance_current = SPT_PROB_RATE(fall_chance / 100, seconds_per_tick) * 100
+ if(owner.body_position == LYING_DOWN)
+ fall_chance_current *= 0.2
+
+ if(prob(fall_chance_current))
+ fall_out()
+ return
+
+ var/damage = parent.w_class * pain_mult
+ var/pain_chance_current = SPT_PROB_RATE(pain_chance / 100, seconds_per_tick) * 100
+ if(pain_stam_pct && HAS_TRAIT_FROM(owner, TRAIT_INCAPACITATED, STAMINA)) //if it's a less-lethal embed, give them a break if they're already stamcritted
+ pain_chance_current *= 0.2
+ damage *= 0.5
+ else if(owner.body_position == LYING_DOWN)
+ pain_chance_current *= 0.2
+
+ if (is_harmless(consider_stamina = TRUE) || !prob(pain_chance_current))
+ return
+
+ owner.apply_damage(
+ damage = (1 - pain_stam_pct) * damage,
+ damagetype = BRUTE,
+ def_zone = owner_limb,
+ wound_bonus = CANT_WOUND,
+ sharpness = parent.get_sharpness(),
+ attacking_item = parent,
+ )
+
+ owner.apply_damage(
+ damage = pain_stam_pct * damage,
+ damagetype = STAMINA,
+ )
+
+ to_chat(owner, span_userdanger("[parent] embedded in your [owner_limb.plaintext_zone] [pain_stam_pct < 1 ? "hurts!" : "weighs you down."]"))
+
+/// Called every process, return TRUE in order to abort further processing - if it falls out, etc
+/datum/embedding/proc/process_effect(seconds_per_tick)
+ return
+
+/// Attempt to pluck out the embedded item using tweezers of some kind
+/datum/embedding/proc/try_pluck(obj/item/tool, mob/user)
+ var/pluck_time = rip_time * (parent.w_class * 0.3) * tool.toolspeed
+ var/self_pluck = (user == owner)
+ var/safe_pluck = tool.tool_behaviour != TOOL_HEMOSTAT
+ // Don't harm ourselves if we're just stuck
+ if (is_harmless())
+ safe_pluck = TRUE
+ if (self_pluck)
+ pluck_time *= 1.5
+ // Wirecutters are harder to use for this
+ if (safe_pluck)
+ pluck_time *= 1.5
+
+ if (self_pluck)
+ owner.visible_message(span_danger("[owner] begins plucking [parent] from [owner.p_their()] [owner_limb.plaintext_zone] with [tool]..."),
+ span_notice("You start plucking [parent] from your [owner_limb.plaintext_zone] with [tool]..."))
+ else
+ user.visible_message(span_danger("[user] begins plucking [parent] from [owner]'s [owner_limb.plaintext_zone] with [tool]..."),
+ span_notice("You start plucking [parent] from [owner]'s [owner_limb.plaintext_zone] with [tool]..."), ignored_mobs = owner)
+ to_chat(owner, span_userdanger("[user] begins plucking [parent] from your [owner_limb.plaintext_zone] with [tool]... "))
+
+ if (!do_after(user, pluck_time, owner, extra_checks = CALLBACK(src, PROC_REF(still_in))))
+ if (self_pluck)
+ to_chat(user, span_danger("You fail to pluck [parent] from your [owner_limb.plaintext_zone]."))
+ else
+ to_chat(user, span_danger("You fail to pluck [parent] from [owner]'s [owner_limb.plaintext_zone]."))
+ to_chat(owner, span_danger("[user] fails to pluck [parent] from your [owner_limb.plaintext_zone]."))
+ return
+
+ if (self_pluck)
+ to_chat(span_notice("You pluck [parent] from your [owner_limb.plaintext_zone][safe_pluck ? "." : span_danger(", but it hurts like hell")]"))
+
+ if(!safe_pluck)
+ damaging_removal_effect(min(self_pluck ? 1 : RIPPING_OUT_HELP_DAMAGE_MULTIPLIER, 0.4 * tool.w_class))
+
+ remove_embedding(user)
+
+/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls remove_embedding()
+/datum/embedding/proc/fall_out()
+ if(is_harmless())
+ owner.visible_message(span_danger("[parent] falls off of [owner.name]'s [owner_limb.plaintext_zone]!"),
+ span_userdanger("[parent] falls off of your [owner_limb.plaintext_zone]!"))
+ remove_embedding()
+ return
+
+ var/damage = parent.w_class * remove_pain_mult
+ owner.apply_damage(
+ damage = (1 - pain_stam_pct) * damage,
+ damagetype = BRUTE,
+ def_zone = owner_limb,
+ wound_bonus = CANT_WOUND,
+ sharpness = parent.get_sharpness(),
+ attacking_item = parent,
+ )
+
+ owner.apply_damage(
+ damage = pain_stam_pct * damage,
+ damagetype = STAMINA,
+ )
+
+ owner.visible_message(span_danger("[parent] falls out of [owner.name]'s [owner_limb.plaintext_zone]!"),
+ span_userdanger("[parent] falls out of your [owner_limb.plaintext_zone]!"))
+ remove_embedding()
+
+/// Whenever the parent item is forcefully moved by some weird means
+/datum/embedding/proc/weapon_disappeared(atom/old_loc, dir, forced)
+ SIGNAL_HANDLER
+ // If something moved it to their limb, its not really *disappearing*, is it?
+ if (owner && parent.loc != owner_limb)
+ to_chat(owner, span_userdanger("[parent] that was embedded in your [owner_limb.plaintext_zone] disappears!"))
+ stop_embedding()
+
+/// So the sticky grenades chain-detonate, because mobs are very careful with which of their contents they blow up
+/datum/embedding/proc/on_ex_act(atom/source, severity)
+ SIGNAL_HANDLER
+ // In the process of owner's ex_act
+ if (QDELETED(parent))
+ return
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += parent
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += parent
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += parent
+
+/// Called when an object is ripped out of someone's body by magic or other abnormal means
+/datum/embedding/proc/magic_pull(obj/item/weapon, mob/living/caster)
+ SIGNAL_HANDLER
+
+ if(is_harmless())
+ owner.visible_message(span_danger("[parent] vanishes from [owner]'s [owner_limb.plaintext_zone]!"), span_userdanger("[parent] vanishes from [owner_limb.plaintext_zone]!"))
+ return
+
+ var/damage = parent.w_class * remove_pain_mult
+
+ owner.apply_damage(
+ damage = (1 - pain_stam_pct) * damage * 1.5,
+ damagetype = BRUTE,
+ def_zone = owner_limb,
+ wound_bonus = max(0, parent.wound_bonus), // Performs exit wounds and flings the user to the caster if nearby
+ sharpness = parent.get_sharpness() || SHARP_EDGED,
+ attacking_item = parent,
+ )
+
+ owner.apply_damage(
+ damage = pain_stam_pct * damage,
+ damagetype = STAMINA,
+ )
+
+ owner.cause_wound_of_type_and_severity(WOUND_PIERCE, owner_limb, WOUND_SEVERITY_MODERATE)
+ playsound(owner, 'sound/effects/wounds/blood2.ogg', 50, TRUE)
+
+ var/dist = get_dist(caster, owner) //Check if the caster is close enough to yank them in
+ if(dist >= 7)
+ owner.visible_message(span_danger("[parent] is violently torn from [owner]'s [owner_limb.plaintext_zone]!"), span_userdanger("[parent] is violently torn from your [owner_limb.plaintext_zone]!"))
+ return
+
+ owner.throw_at(caster, get_dist(owner, caster) - 1, 1, caster)
+ owner.Paralyze(1 SECONDS)
+ owner.visible_message(span_alert("[owner] is sent flying towards [caster] as the [parent] tears out of them!"), span_alert("You are launched at [caster] as the [parent] tears from your body and towards their hand!"))
+
+/datum/embedding/proc/still_in()
+ if (parent.loc != owner)
+ return FALSE
+ if (!(parent in owner_limb?.embedded_objects))
+ return FALSE
+ if (owner_limb?.owner != owner)
+ return FALSE
+ return TRUE
+
+#undef RIPPING_OUT_HELP_TIME_MULTIPLIER
+#undef RIPPING_OUT_HELP_DAMAGE_MULTIPLIER
diff --git a/code/datums/mutations/sight.dm b/code/datums/mutations/sight.dm
index abf78ea45f08a..0f6e0f326dd65 100644
--- a/code/datums/mutations/sight.dm
+++ b/code/datums/mutations/sight.dm
@@ -174,7 +174,7 @@
LE.firer = source
LE.damage *= GET_MUTATION_POWER(src) // MONKESTATION ADDITION
LE.def_zone = ran_zone(source.zone_selected)
- LE.preparePixelProjectile(target, source, modifiers)
+ LE.aim_projectile(target, source, modifiers)
INVOKE_ASYNC(LE, TYPE_PROC_REF(/obj/projectile, fire))
playsound(source, 'monkestation/sound/weapons/gun/energy/Laser2.ogg', 75, TRUE)
diff --git a/code/datums/mutations/tongue_spike.dm b/code/datums/mutations/tongue_spike.dm
index f1476a7c4a754..e73b26c3c1fae 100644
--- a/code/datums/mutations/tongue_spike.dm
+++ b/code/datums/mutations/tongue_spike.dm
@@ -62,12 +62,7 @@
force = 2
throwforce = 15 //15 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = i didnt do the math
throw_speed = 4
- embedding = list(
- "embedded_pain_multiplier" = 4,
- "embed_chance" = 100,
- "embedded_fall_chance" = 0,
- "embedded_ignore_throwspeed_threshold" = TRUE,
- )
+ embed_type = /datum/embedding/tongue_spike
w_class = WEIGHT_CLASS_SMALL
sharpness = SHARP_POINTY
custom_materials = list(/datum/material/biomass = SMALL_MATERIAL_AMOUNT * 5)
@@ -79,23 +74,32 @@
/obj/item/hardened_spike/Initialize(mapload, mob/living/carbon/source)
. = ..()
src.fired_by_ref = WEAKREF(source)
- addtimer(CALLBACK(src, PROC_REF(check_embedded)), 5 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(check_morph)), 5 SECONDS)
-/obj/item/hardened_spike/proc/check_embedded()
- if(missed)
- unembedded()
+/obj/item/hardened_spike/proc/check_morph()
+ // Failed to embed, morph back
+ if (!embed_data?.owner)
+ morph_back()
-/obj/item/hardened_spike/embedded(atom/target)
- if(isbodypart(target))
- missed = FALSE
-
-/obj/item/hardened_spike/unembedded()
+/obj/item/hardened_spike/proc/morph_back()
visible_message(span_warning("[src] cracks and twists, changing shape!"))
for(var/obj/tongue as anything in contents)
tongue.forceMove(get_turf(src))
-
qdel(src)
+/datum/embedding/tongue_spike
+ impact_pain_mult = 0
+ pain_mult = 15
+ embed_chance = 100
+ fall_chance = 0
+ ignore_throwspeed_threshold = TRUE
+
+/datum/embedding/tongue_spike/stop_embedding()
+ . = ..()
+ var/obj/item/hardened_spike/tongue_spike = parent
+ if (!QDELETED(tongue_spike)) // This can cause a qdel loop
+ tongue_spike.morph_back()
+
/datum/mutation/tongue_spike/chem
name = "Chem Spike"
desc = "Allows a creature to voluntary shoot their tongue out as biomass, allowing a long range transfer of chemicals."
@@ -121,41 +125,36 @@
name = "chem spike"
desc = "Hardened biomass, shaped into... something."
icon_state = "tonguespikechem"
- throwforce = 2 //2 + 2 (WEIGHT_CLASS_SMALL) * 0 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = i didnt do the math again but very low or smthin
- embedding = list(
- "embedded_pain_multiplier" = 0,
- "embed_chance" = 100,
- "embedded_fall_chance" = 0,
- "embedded_pain_chance" = 0,
- "embedded_ignore_throwspeed_threshold" = TRUE, //never hurts once it's in you
- )
- /// Whether the tongue's already embedded in a target once before
- var/embedded_once_alread = FALSE
-
-/obj/item/hardened_spike/chem/embedded(mob/living/carbon/human/embedded_mob)
- if(embedded_once_alread)
- return
- embedded_once_alread = TRUE
+ throwforce = 2
+ embed_type = /datum/embedding/tongue_spike/chem
+
+/datum/embedding/tongue_spike/chem
+ pain_mult = 0
+ pain_chance = 0
- var/mob/living/carbon/fired_by = fired_by_ref?.resolve()
- if(!fired_by)
+/datum/embedding/tongue_spike/chem/on_successful_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb)
+ var/obj/item/hardened_spike/chem/tongue_spike = parent
+ var/mob/living/carbon/fired_by = tongue_spike.fired_by_ref?.resolve()
+ if(!istype(fired_by))
return
- var/datum/action/send_chems/chem_action = new(src)
- chem_action.transfered_ref = WEAKREF(embedded_mob)
+ var/datum/action/send_chems/chem_action = new(tongue_spike)
+ chem_action.transfered_ref = WEAKREF(victim)
chem_action.Grant(fired_by)
to_chat(fired_by, span_notice("Link established! Use the \"Transfer Chemicals\" ability \
to send your chemicals to the linked target!"))
-/obj/item/hardened_spike/chem/unembedded()
- var/mob/living/carbon/fired_by = fired_by_ref?.resolve()
- if(fired_by)
- to_chat(fired_by, span_warning("Link lost!"))
- var/datum/action/send_chems/chem_action = locate() in fired_by.actions
- QDEL_NULL(chem_action)
+/datum/embedding/tongue_spike/chem/stop_embedding()
+ . = ..()
+ var/obj/item/hardened_spike/chem/tongue_spike = parent
+ var/mob/living/carbon/fired_by = tongue_spike.fired_by_ref?.resolve()
+ if(!istype(fired_by))
+ return
- return ..()
+ to_chat(fired_by, span_warning("Link lost!"))
+ var/datum/action/send_chems/chem_action = locate() in fired_by.actions
+ qdel(chem_action)
/datum/action/send_chems
name = "Transfer Chemicals"
@@ -188,9 +187,11 @@
transferer.reagents.trans_to(transfered, transferer.reagents.total_volume, 1, 1, 0, transfered_by = transferer)
var/obj/item/hardened_spike/chem/chem_spike = target
- var/obj/item/bodypart/spike_location = chem_spike.check_embedded()
- //this is where it would deal damage, if it transfers chems it removes itself so no damage
- chem_spike.forceMove(get_turf(spike_location))
- chem_spike.visible_message(span_notice("[chem_spike] falls out of [spike_location]!"))
+ // This is where it would deal damage, if it transfers chems it removes itself so no damage
+ var/mob/living/carbon/spike_owner = chem_spike.get_embed()?.owner
+ // Message first because it'll shift back into a tongue right after moving
+ if (istype(spike_owner))
+ spike_owner.visible_message(span_notice("[chem_spike] falls out of [spike_owner]!"))
+ chem_spike.forceMove(get_turf(chem_spike))
return TRUE
diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm
index c963d0ad76025..02281e2437a08 100644
--- a/code/datums/position_point_vector.dm
+++ b/code/datums/position_point_vector.dm
@@ -1,15 +1,11 @@
/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
- var/datum/point/P = new
- P.x = a.x + (b.x - a.x) * 0.5
- P.y = a.y + (b.y - a.y) * 0.5
- P.z = a.z
- return P
+ return new /datum/point(_z = a.z, _pixel_x = (a.return_px() + b.return_px()) * 0.5, _pixel_y = (a.return_py() + b.return_py()) * 0.5)
/proc/pixel_length_between_points(datum/point/a, datum/point/b)
- return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
+ return sqrt(((b.return_px() - a.return_px()) ** 2) + ((b.return_py() - a.return_py()) ** 2))
/proc/angle_between_points(datum/point/a, datum/point/b)
- return ATAN2((b.y - a.y), (b.x - a.x))
+ return ATAN2(b.return_py() - a.return_py(), b.return_px() - a.return_px())
/// For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
/datum/position
@@ -29,8 +25,8 @@
_x = T.x
_y = T.y
_z = T.z
- _pixel_x = P.return_px()
- _pixel_y = P.return_py()
+ _pixel_x = P.pixel_x
+ _pixel_y = P.pixel_y
else if(isatom(_x))
var/atom/A = _x
_x = A.x
@@ -61,6 +57,8 @@
var/x = 0
var/y = 0
var/z = 0
+ var/pixel_x = 0
+ var/pixel_y = 0
/datum/point/proc/valid()
return x && y && z
@@ -89,143 +87,88 @@
_pixel_y = A.pixel_y
initialize_location(_x, _y, _z, _pixel_x, _pixel_y)
-/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
+/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x, p_y)
if(!isnull(tile_x))
- x = ((tile_x - 1) * world.icon_size) + world.icon_size * 0.5 + p_x + 1
+ x = tile_x
if(!isnull(tile_y))
- y = ((tile_y - 1) * world.icon_size) + world.icon_size * 0.5 + p_y + 1
+ y = tile_y
if(!isnull(tile_z))
z = tile_z
+ if(!isnull(p_x))
+ var/x_offset = SIGNED_FLOOR_DIVISION(p_x, ICON_SIZE_X)
+ x += x_offset
+ pixel_x = p_x - x_offset * ICON_SIZE_X
+ if(!isnull(p_y))
+ var/y_offset = SIGNED_FLOOR_DIVISION(p_y, ICON_SIZE_Y)
+ y += y_offset
+ pixel_y = p_y - y_offset * ICON_SIZE_Y
+
+/datum/point/proc/increment(p_x, p_y)
+ var/x_offset = SIGNED_FLOOR_DIVISION(p_x, ICON_SIZE_X)
+ x += x_offset
+ pixel_x += p_x - x_offset * ICON_SIZE_X
+ var/y_offset = SIGNED_FLOOR_DIVISION(p_y, ICON_SIZE_Y)
+ y += y_offset
+ pixel_y += p_y - y_offset * ICON_SIZE_Y
/datum/point/proc/debug_out()
var/turf/T = return_turf()
- return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]"
+ return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [pixel_x] pY [pixel_y] mX [T.x] mY [T.y] mZ [T.z]"
/datum/point/proc/move_atom_to_src(atom/movable/AM)
AM.forceMove(return_turf())
- AM.pixel_x = return_px()
- AM.pixel_y = return_py()
+ AM.pixel_x = pixel_x
+ AM.pixel_y = pixel_y
/datum/point/proc/return_turf()
- return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z)
+ return locate(x + SIGNED_FLOOR_DIVISION(pixel_x, ICON_SIZE_X), y + SIGNED_FLOOR_DIVISION(pixel_y, ICON_SIZE_Y), z)
/datum/point/proc/return_coordinates() //[turf_x, turf_y, z]
- return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z)
+ return list(x + SIGNED_FLOOR_DIVISION(pixel_x, ICON_SIZE_X), y + SIGNED_FLOOR_DIVISION(pixel_y, ICON_SIZE_Y), z)
/datum/point/proc/return_position()
return new /datum/position(src)
/datum/point/proc/return_px()
- return MODULUS(x, world.icon_size) - 16 - 1
+ return x * ICON_SIZE_X + pixel_x
/datum/point/proc/return_py()
- return MODULUS(y, world.icon_size) - 16 - 1
+ return y * ICON_SIZE_Y + pixel_y
-/datum/point/vector
- /// Pixels per iteration
- var/speed = 32
- var/iteration = 0
+/datum/vector
+ var/magnitude = 1
var/angle = 0
- /// Calculated x movement amounts to prevent having to do trig every step.
- var/mpx = 0
- /// Calculated y movement amounts to prevent having to do trig every step.
- var/mpy = 0
- var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
- var/starting_y = 0
- var/starting_z = 0
-
-/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0)
- ..()
- initialize_trajectory(_speed, _angle)
- if(initial_increment)
- increment(initial_increment)
-
-/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
- . = ..()
- starting_x = x
- starting_y = y
- starting_z = z
+ // Calculated coordinate amounts to prevent having to do trig every step.
+ var/pixel_x = 0
+ var/pixel_y = 0
+ var/total_x = 0
+ var/total_y = 0
-/// Same effect as initiliaze_location, but without setting the starting_x/y/z
-/datum/point/vector/proc/set_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
- if(!isnull(tile_x))
- x = ((tile_x - 1) * world.icon_size) + world.icon_size * 0.5 + p_x + 1
- if(!isnull(tile_y))
- y = ((tile_y - 1) * world.icon_size) + world.icon_size * 0.5 + p_y + 1
- if(!isnull(tile_z))
- z = tile_z
+/datum/vector/New(new_magnitude, new_angle)
+ . = ..()
+ initialize_trajectory(new_magnitude, new_angle)
-/datum/point/vector/copy_to(datum/point/vector/v = new)
- ..(v)
- v.speed = speed
- v.iteration = iteration
- v.angle = angle
- v.mpx = mpx
- v.mpy = mpy
- v.starting_x = starting_x
- v.starting_y = starting_y
- v.starting_z = starting_z
- return v
-
-/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle)
- if(!isnull(pixel_speed))
- speed = pixel_speed
+/datum/vector/proc/initialize_trajectory(new_magnitude, new_angle)
+ if(!isnull(new_magnitude))
+ magnitude = new_magnitude
set_angle(new_angle)
/// Calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270.
-/datum/point/vector/proc/set_angle(new_angle)
+/datum/vector/proc/set_angle(new_angle)
if(isnull(angle))
return
angle = new_angle
update_offsets()
-/datum/point/vector/proc/update_offsets()
- mpx = sin(angle) * speed
- mpy = cos(angle) * speed
-
-/datum/point/vector/proc/set_speed(new_speed)
- if(isnull(new_speed) || speed == new_speed)
- return
- speed = new_speed
- update_offsets()
+/datum/vector/proc/update_offsets()
+ pixel_x = sin(angle)
+ pixel_y = cos(angle)
+ total_x = pixel_x * magnitude
+ total_y = pixel_y * magnitude
-/datum/point/vector/proc/increment(multiplier = 1)
- iteration++
- x += mpx * (multiplier)
- y += mpy * (multiplier)
-
-/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE)
- var/datum/point/vector/v = copy_to()
- if(force_simulate)
- for(var/i in 1 to amount)
- v.increment(multiplier)
- else
- v.increment(multiplier * amount)
- return v
-
-/datum/point/vector/proc/on_z_change()
- return
-
-/datum/point/vector/processed //pixel_speed is per decisecond.
- var/last_process = 0
- var/last_move = 0
- var/paused = FALSE
-
-/datum/point/vector/processed/Destroy()
- STOP_PROCESSING(SSprojectiles, src)
- return ..()
-
-/datum/point/vector/processed/proc/start()
- last_process = world.time
- last_move = world.time
- START_PROCESSING(SSprojectiles, src)
-
-/datum/point/vector/processed/process()
- if(paused)
- last_move += world.time - last_process
- last_process = world.time
+/datum/vector/proc/set_speed(new_magnitude)
+ if(isnull(new_magnitude) || magnitude == new_magnitude)
return
- var/needed_time = world.time - last_move
- last_process = world.time
- last_move = world.time
- increment(needed_time / SSprojectiles.wait)
+ magnitude = new_magnitude
+ total_x = pixel_x * magnitude
+ total_y = pixel_y * magnitude
diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm
index 733154661f50b..2d90b23892b84 100644
--- a/code/datums/proximity_monitor/fields/timestop.dm
+++ b/code/datums/proximity_monitor/fields/timestop.dm
@@ -203,11 +203,11 @@
freeze_atom(i)
freeze_turf(target)
-/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P)
- P.paused = TRUE
+/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/proj)
+ proj.paused = TRUE
-/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P)
- P.paused = FALSE
+/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/proj)
+ proj.paused = FALSE
/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/victim)
frozen_mobs += victim
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 566856f1408fc..46ace3fccd38b 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -432,11 +432,9 @@
/datum/status_effect/stacking/saw_bleed/threshold_cross_effect()
owner.adjustBruteLoss(bleed_damage)
- new /obj/effect/temp_visual/bleed/explode(owner.loc)
- for(var/d in GLOB.alldirs)
- if(QDELETED(owner))
- return
- owner.do_splatter_effect(d)
+ new /obj/effect/temp_visual/bleed/explode(get_turf(owner))
+ for(var/splatter_dir in GLOB.alldirs)
+ owner.create_splatter(splatter_dir)
playsound(owner, SFX_DESECRATION, 100, TRUE, -1)
/datum/status_effect/stacking/saw_bleed/bloodletting
@@ -560,7 +558,7 @@
new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir)
playsound(spawn_turf, 'sound/effects/curse2.ogg', 80, TRUE, -1)
var/obj/projectile/curse_hand/C = new (spawn_turf)
- C.preparePixelProjectile(owner, spawn_turf)
+ C.aim_projectile(owner, spawn_turf)
C.fire()
/obj/effect/temp_visual/curse
@@ -589,7 +587,7 @@
new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir)
playsound(spawn_turf, pick('sound/effects/curse1.ogg','sound/effects/curse2.ogg','sound/effects/curse3.ogg'), 80, 1, -1)
var/obj/projectile/curse_hand/progenitor/pro = new (spawn_turf)
- pro.preparePixelProjectile(owner, spawn_turf)
+ pro.aim_projectile(owner, spawn_turf)
pro.fire()
/datum/status_effect/gonbola_pacify
diff --git a/code/datums/storage/subtypes/bag_of_holding.dm b/code/datums/storage/subtypes/bag_of_holding.dm
index b60127efc7053..5ff260d521f9c 100644
--- a/code/datums/storage/subtypes/bag_of_holding.dm
+++ b/code/datums/storage/subtypes/bag_of_holding.dm
@@ -13,15 +13,17 @@
if(safety != "Proceed" || QDELETED(to_insert) || QDELETED(real_location) || QDELETED(user) || !iscarbon(user) || !user.can_perform_action(real_location, NEED_DEXTERITY))
return
- var/turf/loccheck = get_turf(real_location)
+ var/turf/rift_loc = get_turf(real_location)
to_chat(user, span_danger("The Bluespace interfaces of the two devices catastrophically malfunction!"))
qdel(to_insert)
- playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE)
+ playsound(rift_loc,'sound/effects/supermatter.ogg', 200, TRUE)
- message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
- user.log_message("detonated a bag of holding at [loc_name(loccheck)].", LOG_ATTACK, color="red")
+ message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(rift_loc)].")
+ user.log_message("detonated a bag of holding at [loc_name(rift_loc)].", LOG_ATTACK, color="red")
user.investigate_log("has been gibbed by a bag of holding recursive insertion.", INVESTIGATE_DEATHS)
- user.gib(TRUE, TRUE, TRUE)
- new/obj/boh_tear(loccheck)
- qdel(real_location)
+ user.gib()
+ var/obj/reality_tear/tear = new(rift_loc)
+ tear.start_disaster()
+ qdel(to_insert)
+ qdel(parent)
diff --git a/code/datums/storage/subtypes/pockets.dm b/code/datums/storage/subtypes/pockets.dm
index 8ceea477e78bb..907d61d6b1979 100644
--- a/code/datums/storage/subtypes/pockets.dm
+++ b/code/datums/storage/subtypes/pockets.dm
@@ -103,7 +103,7 @@
/obj/item/toy/crayon,
/obj/item/reagent_containers/cup/glass/flask),
list(/obj/item/screwdriver/power,
- /obj/item/ammo_casing/caseless/rocket,
+ /obj/item/ammo_casing/rocket,
/obj/item/clothing/mask/cigarette/pipe,
/obj/item/toy/crayon/spraycan)
)
@@ -138,7 +138,7 @@
/obj/item/bikehorn,
/obj/item/reagent_containers/cup/glass/flask),
list(/obj/item/screwdriver/power,
- /obj/item/ammo_casing/caseless/rocket,
+ /obj/item/ammo_casing/rocket,
/obj/item/clothing/mask/cigarette/pipe,
/obj/item/toy/crayon/spraycan)
)
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
index 7306fc3fddd14..c9c7329ff6c63 100644
--- a/code/datums/wounds/pierce.dm
+++ b/code/datums/wounds/pierce.dm
@@ -68,7 +68,8 @@
vision_distance = COMBAT_MESSAGE_RANGE,
)
-
+ victim.do_splatter_effect(victim.dir)
+ victim.bleed(blood_bled)
if(20 to INFINITY)
victim.visible_message(
span_danger("A spray of blood streams from the gash in [victim]'s [limb.plaintext_zone]!"),
@@ -76,6 +77,8 @@
vision_distance = COMBAT_MESSAGE_RANGE,
)
+ victim.bleed(blood_bled)
+ victim.do_splatter_effect(victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
victim.bleed(blood_bled, TRUE)
diff --git a/code/game/atoms/_atom.dm b/code/game/atoms/_atom.dm
index f4230d974ee03..d733e489aea39 100644
--- a/code/game/atoms/_atom.dm
+++ b/code/game/atoms/_atom.dm
@@ -329,7 +329,7 @@
var/turf/p_turf = get_turf(ricocheting_projectile)
var/face_direction = get_dir(src, p_turf) || get_dir(src, ricocheting_projectile)
var/face_angle = dir2angle(face_direction)
- var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.Angle + 180))
+ var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.angle + 180))
var/a_incidence_s = abs(incidence_s)
if(a_incidence_s > 90 && a_incidence_s < 270)
return FALSE
diff --git a/code/game/atoms/atom_act.dm b/code/game/atoms/atom_act.dm
index 52988e6c7bcc9..1d2674f2ba87b 100644
--- a/code/game/atoms/atom_act.dm
+++ b/code/game/atoms/atom_act.dm
@@ -62,6 +62,20 @@
wires.emp_pulse()
return protection // Pass the protection value collected here upwards
+/**
+ * Wrapper for bullet_act used for atom-specific calculations, i.e. armor
+ *
+ * @params
+ * * hitting_projectile - projectile
+ * * def_zone - zone hit
+ * * piercing_hit - is this hit piercing or normal?
+ */
+
+/atom/proc/projectile_hit(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE, blocked = null)
+ if (isnull(blocked))
+ blocked = check_projectile_armor(def_zone, hitting_projectile)
+ return bullet_act(hitting_projectile, def_zone, piercing_hit, blocked)
+
/**
* React to a hit by a projectile object
*
@@ -69,11 +83,12 @@
* * hitting_projectile - projectile
* * def_zone - zone hit
* * piercing_hit - is this hit piercing or normal?
+ * * blocked - total armor value to apply to this hit
*/
-/atom/proc/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
+/atom/proc/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE, blocked = 0)
SHOULD_CALL_PARENT(TRUE)
- var/sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_PRE_BULLET_ACT, hitting_projectile, def_zone)
+ var/sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_PRE_BULLET_ACT, hitting_projectile, def_zone, piercing_hit, blocked)
if(sigreturn & COMPONENT_BULLET_PIERCED)
return BULLET_ACT_FORCE_PIERCE
if(sigreturn & COMPONENT_BULLET_BLOCKED)
@@ -81,7 +96,7 @@
if(sigreturn & COMPONENT_BULLET_ACTED)
return BULLET_ACT_HIT
- SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, hitting_projectile, def_zone)
+ SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, hitting_projectile, def_zone, piercing_hit, blocked)
if(QDELETED(hitting_projectile)) // Signal deleted it?
return BULLET_ACT_BLOCK
@@ -89,7 +104,7 @@
target = src,
// This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since
// only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot.
- blocked = check_projectile_armor(def_zone, hitting_projectile),
+ blocked = blocked,
pierce_hit = piercing_hit,
)
diff --git a/code/game/atoms/atom_defense.dm b/code/game/atoms/atom_defense.dm
index f2a42d8358571..84a87d9dea515 100644
--- a/code/game/atoms/atom_defense.dm
+++ b/code/game/atoms/atom_defense.dm
@@ -143,10 +143,6 @@
/// A cut-out proc for [/atom/proc/bullet_act] so living mobs can have their own armor behavior checks without causing issues with needing their own on_hit call
/atom/proc/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent)
- if(!uses_integrity)
- return 0
-
- . = clamp(PENETRATE_ARMOUR(get_armor_rating(impacting_projectile.armor_flag), impacting_projectile.armour_penetration), 0, 100)
- if(impacting_projectile.grazing)
- . += 50
- return .
+ if(uses_integrity)
+ return clamp(PENETRATE_ARMOUR(get_armor_rating(impacting_projectile.armor_flag), impacting_projectile.armour_penetration), 0, 100)
+ return 0
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index 0e7a10c82fa89..d5c9c92b2a53c 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -667,7 +667,7 @@ DEFINE_BITFIELD(turret_flags, list(
//Shooting Code:
- A.preparePixelProjectile(target, T)
+ A.aim_projectile(target, T)
A.firer = src
A.fired_from = src
if(ignore_faction)
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index dad389b886a4f..16b28cebda9ed 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -130,29 +130,35 @@
linked = null
return ..()
-/obj/effect/portal/attack_ghost(mob/dead/observer/O)
- if(!teleport(O, TRUE))
+/obj/effect/portal/attack_ghost(mob/dead/observer/ghost)
+ if(!teleport(ghost, force = TRUE))
return ..()
+ return BULLET_ACT_FORCE_PIERCE
-/obj/effect/portal/proc/teleport(atom/movable/M, force = FALSE)
- if(!force && (!istype(M) || iseffect(M) || (ismecha(M) && !mech_sized) || (!isobj(M) && !ismob(M)))) //Things that shouldn't teleport.
+/obj/effect/portal/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
+ if (!teleport(hitting_projectile, force = TRUE))
+ return ..()
+ return BULLET_ACT_FORCE_PIERCE
+
+/obj/effect/portal/proc/teleport(atom/movable/moving, force = FALSE)
+ if(!force && (!istype(moving) || iseffect(moving) || (ismecha(moving) && !mech_sized) || (!isobj(moving) && !ismob(moving)))) //Things that shouldn't teleport.
return
var/turf/real_target = get_link_target_turf()
if(!istype(real_target))
return FALSE
- if(!force && (!ismecha(M) && !isprojectile(M) && M.anchored && !allow_anchored))
+ if(!force && (!ismecha(moving) && !isprojectile(moving) && moving.anchored && !allow_anchored))
return
var/no_effect = FALSE
- if(last_effect == world.time)
+ if(last_effect == world.time || sparkless)
no_effect = TRUE
else
last_effect = world.time
- var/turf/start_turf = get_turf(M)
- if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport))
- if(isprojectile(M))
- var/obj/projectile/P = M
+ var/turf/start_turf = get_turf(moving)
+ if(do_teleport(moving, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport))
+ if(isprojectile(moving))
+ var/obj/projectile/P = moving
P.ignore_source_check = TRUE
- new /obj/effect/temp_visual/portal_animation(start_turf, src, M)
+ new /obj/effect/temp_visual/portal_animation(start_turf, src, moving)
playsound(start_turf, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(real_target, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
return TRUE
@@ -189,7 +195,7 @@
linked = P
break
-/obj/effect/portal/permanent/teleport(atom/movable/M, force = FALSE)
+/obj/effect/portal/permanent/teleport(atom/movable/moving, force = FALSE)
set_linked() // update portal links
. = ..()
@@ -213,9 +219,9 @@
name = "one-use portal"
desc = "This is probably the worst decision you'll ever make in your life."
-/obj/effect/portal/permanent/one_way/one_use/teleport(atom/movable/M, force = FALSE)
+/obj/effect/portal/permanent/one_way/one_use/teleport(atom/movable/moving, force = FALSE)
. = ..()
- if (. && !isdead(M))
+ if (. && !isdead(moving))
expire()
/**
diff --git a/code/game/objects/effects/posters/poster.dm b/code/game/objects/effects/posters/poster.dm
index 3e1d7890d6bf8..733dafe811059 100644
--- a/code/game/objects/effects/posters/poster.dm
+++ b/code/game/objects/effects/posters/poster.dm
@@ -201,11 +201,9 @@
return
to_chat(user, span_warning("There's something sharp behind this! What the hell?"))
- if(!can_embed_trap(user) || !payload.tryEmbed(user.get_active_hand(), forced = TRUE))
+ if(!can_embed_trap(user) || !payload.force_embed(user, user.get_active_hand()))
visible_message(span_notice("A [payload.name] falls from behind the poster.") )
payload.forceMove(user.drop_location())
- else
- SEND_SIGNAL(src, COMSIG_POSTER_TRAP_SUCCEED, user)
/obj/structure/sign/poster/proc/can_embed_trap(mob/living/carbon/human/user)
if (!istype(user) || HAS_TRAIT(user, TRAIT_PIERCEIMMUNE))
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 03cffcecffae1..6367f07f3d929 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -99,6 +99,12 @@
icon_state = "firing_effect"
duration = 2
+/obj/effect/temp_visual/dir_setting/firing_effect/Initialize(mapload, set_dir)
+ . = ..()
+ if (ismovable(loc))
+ var/atom/movable/spawned_inside = loc
+ spawned_inside.vis_contents += src
+
/obj/effect/temp_visual/dir_setting/firing_effect/setDir(newdir)
switch(newdir)
if(NORTH)
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm
index 4305abb5341f7..ecaea51eb3d6a 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm
@@ -37,26 +37,19 @@
apply_vars(angle_override, p_x, p_y, color_override, scaling)
return ..()
-/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, atom/new_loc, increment = 0)
- var/mutable_appearance/look = new(src)
- SET_PLANE_EXPLICIT(look, plane, new_loc || src)
- look.pixel_x = p_x
- look.pixel_y = p_y
+/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, increment = 0)
+ pixel_x = p_x
+ pixel_y = p_y
if(color_override)
- look.color = color_override
- appearance = look
- scale_to(1,scaling, FALSE)
+ color = color_override
+ scale_to(1, scaling, FALSE)
turn_to(angle_override, FALSE)
- if(!isnull(new_loc)) //If you want to null it just delete it...
- forceMove(new_loc)
for(var/i in 1 to increment)
pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1)
pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1)
-/obj/effect/projectile_lighting
- var/owner
+/obj/effect/abstract/projectile_lighting
-/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key)
+/obj/effect/abstract/projectile_lighting/Initialize(mapload, color, range, intensity)
. = ..()
set_light(l_outer_range = range, l_power = intensity, l_color = color)
- owner = owner_key
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
index 9a3f4e36c20db..6c936c6a6299b 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
@@ -1,25 +1,3 @@
-/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported!
- if(!istype(starting) || !istype(ending) || !ispath(beam_type))
- return
- var/datum/point/midpoint = point_midpoint_points(starting, ending)
- var/obj/effect/projectile/tracer/PB = new beam_type
- if(isnull(light_color_override))
- light_color_override = color
- PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0)
- . = PB
- if(light_range > 0 && light_intensity > 0)
- var/list/turf/line = get_line(starting.return_turf(), ending.return_turf())
- tracing_line:
- for(var/i in line)
- var/turf/T = i
- for(var/obj/effect/projectile_lighting/PL in T)
- if(PL.owner == instance_key)
- continue tracing_line
- QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5)
- line = null
- if(qdel_in)
- QDEL_IN(PB, qdel_in)
-
/obj/effect/projectile/tracer
name = "beam"
icon = 'icons/obj/weapons/guns/projectiles_tracer.dmi'
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index dd5fad237e677..a5c33378b8b86 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -167,8 +167,10 @@
///the icon to indicate this object is being dragged
mouse_drag_pointer = MOUSE_ACTIVE_POINTER
- ///Does it embed and if yes, what kind of embed
- var/list/embedding
+ /// Does it embed and if yes, what kind of embed
+ var/embed_type
+ /// Stores embedding data
+ VAR_PROTECTED/datum/embedding/embed_data
///for flags such as [GLASSESCOVERSEYES]
var/flags_cover = 0
@@ -275,8 +277,6 @@
hitsound = SFX_SWING_HIT
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_ITEM, src)
- if(LAZYLEN(embedding))
- updateEmbedding()
setup_reskinning()
@@ -864,6 +864,7 @@
do_drop_animation(master_storage.parent)
/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ get_embed() // Ensure that embedding is lazyloaded before we impact the target, if we can have it
if(QDELETED(hit_atom))
return
if(SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) & COMPONENT_MOVABLE_IMPACT_NEVERMIND)
@@ -1205,14 +1206,6 @@
dropped(M, FALSE)
return ..()
-/obj/item/proc/embedded(atom/embedded_target, obj/item/bodypart/part)
- return
-
-/obj/item/proc/unembedded()
- if(item_flags & DROPDEL && !QDELETED(src))
- qdel(src)
- return TRUE
-
/obj/item/proc/canStrip(mob/stripper, mob/owner)
SHOULD_BE_PURE(TRUE)
return !HAS_TRAIT(src, TRAIT_NODROP) && !(item_flags & ABSTRACT)
@@ -1220,16 +1213,6 @@
/obj/item/proc/doStrip(mob/stripper, mob/owner)
return owner.dropItemToGround(src)
-///Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true.
-/obj/item/proc/isEmbedHarmless()
- if(embedding)
- return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0
-
-///In case we want to do something special (like self delete) upon failing to embed in something.
-/obj/item/proc/failedEmbed()
- if(item_flags & DROPDEL && !QDELETED(src))
- qdel(src)
-
///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else.
/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target)
if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP))
@@ -1240,55 +1223,6 @@
return
return src
-/**
- * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targeting the target.
- *
- * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements.
- *
- * Returns TRUE if it embedded successfully, nothing otherwise
- *
- * Arguments:
- * * target- Either a body part or a carbon. What are we hitting?
- * * forced- Do we want this to go through 100%?
- */
-/obj/item/proc/tryEmbed(atom/target, forced=FALSE)
- if(!isbodypart(target) && !iscarbon(target))
- return NONE
- if(!forced && !LAZYLEN(embedding))
- return NONE
-
- if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target = target, forced = forced))
- return COMPONENT_EMBED_SUCCESS
- failedEmbed()
-
-///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it.
-/obj/item/proc/disableEmbedding()
- SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED)
- return
-
-///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/var/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item.
-/obj/item/proc/updateEmbedding()
- SHOULD_CALL_PARENT(TRUE)
-
- SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDING_UPDATE)
- if(!LAZYLEN(embedding))
- disableEmbedding()
- return
-
- AddElement(/datum/element/embed,\
- embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\
- fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\
- pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\
- pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\
- remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\
- rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\
- ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\
- impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\
- jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\
- jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\
- pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT))
- return TRUE
-
/// How many different types of mats will be counted in a bite?
#define MAX_MATS_PER_BITE 2
@@ -1317,15 +1251,16 @@
victim.apply_damage(max(15, force), BRUTE, BODY_ZONE_HEAD, wound_bonus = 10, sharpness = TRUE)
victim.losebreath += 2
- if(tryEmbed(victim.get_bodypart(BODY_ZONE_CHEST), forced = TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
+ if(force_embed(victim, BODY_ZONE_CHEST)) //and if it embeds successfully in their chest, cause a lot of pain
victim.apply_damage(max(25, force*1.5), BRUTE, BODY_ZONE_CHEST, wound_bonus = 7, sharpness = TRUE)
victim.losebreath += 6
discover_after = FALSE
if(QDELETED(src)) // in case trying to embed it caused its deletion (say, if it's DROPDEL)
return
source_item?.reagents?.add_reagent(/datum/reagent/blood, 2)
+ return discover_after
- else if(custom_materials?.len) //if we've got materials, lets see whats in it
+ if(custom_materials?.len) //if we've got materials, let's see what's in it
/// How many mats have we found? You can only be affected by two material datums by default
var/found_mats = 0
/// How much of each material is in it? Used to determine if the glass should break
@@ -1358,25 +1293,25 @@
victim.adjust_disgust(33)
victim.visible_message(span_warning("[victim] looks like [victim.p_theyve()] just bitten into something hard."), \
span_warning("Eugh! Did I just bite into something?"))
+ return discover_after
- else if(w_class == WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums
- /// victim's chest (for cavity implanting the item)
- var/obj/item/bodypart/chest/victim_cavity = victim.get_bodypart(BODY_ZONE_CHEST)
- if(victim_cavity.cavity_item)
- victim.vomit(5, FALSE, FALSE, distance = 0)
- forceMove(drop_location())
- to_chat(victim, span_warning("You vomit up a [name]! [source_item? "Was that in \the [source_item]?" : ""]"))
- else
- victim.transferItemToLoc(src, victim, TRUE)
- victim.losebreath += 2
- victim_cavity.cavity_item = src
- to_chat(victim, span_warning("You swallow hard. [source_item? "Something small was in \the [source_item]..." : ""]"))
- discover_after = FALSE
-
- else
+ if(w_class > WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums
to_chat(victim, span_warning("[source_item? "Something strange was in the \the [source_item]..." : "I just bit something strange..."] "))
+ return discover_after
+
+ // victim's chest (for cavity implanting the item)
+ var/obj/item/bodypart/chest/victim_cavity = victim.get_bodypart(BODY_ZONE_CHEST)
+ if(victim_cavity.cavity_item)
+ victim.vomit(lost_nutrition = 5, distance = 0)
+ forceMove(drop_location())
+ to_chat(victim, span_warning("You vomit up a [name]! [source_item? "Was that in \the [source_item]?" : ""]"))
+ return FALSE
- return discover_after
+ victim.transferItemToLoc(src, victim, TRUE)
+ victim.losebreath += 2
+ victim_cavity.cavity_item = src
+ to_chat(victim, span_warning("You swallow hard. [source_item? "Something small was in \the [source_item]..." : ""]"))
+ return FALSE
#undef MAX_MATS_PER_BITE
@@ -1733,3 +1668,40 @@
var/list/wardrobe_stock = SSwardrobe?.preloaded_stock?[type]
if(wardrobe_stock && (src in wardrobe_stock[WARDROBE_STOCK_CONTENTS]))
return "Stocked in SSwardrobe"
+
+/// Fetches, or lazyloads, our embedding datum
+/obj/item/proc/get_embed()
+ RETURN_TYPE(/datum/embedding)
+ // Something may call this during qdeleting, which would cause a harddel
+ if (QDELETED(src))
+ return null
+ if (embed_data)
+ return embed_data
+ if (embed_type)
+ embed_data = new embed_type(src)
+ return embed_data
+
+/// Sets our embedding datum to a different one. Can also take types
+/obj/item/proc/set_embed(datum/embedding/new_embed)
+ if (new_embed == embed_data)
+ return
+
+ // Needs to be QDELETED as embed data uses this to clean itself up from its parent (us)
+ if (!QDELETED(embed_data))
+ qdel(embed_data)
+
+ if (ispath(new_embed))
+ new_embed = new new_embed(src)
+
+ embed_data = new_embed
+ SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDING_UPDATE)
+
+/// Embed ourselves into an object if we possess embedding data
+/obj/item/proc/force_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb)
+ if (!istype(victim))
+ return FALSE
+
+ if (!istype(target_limb))
+ target_limb = victim.get_bodypart(target_limb) || victim.bodyparts[1]
+
+ return get_embed()?.embed_into(victim, target_limb)
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index 958accd05ea08..6be8902248d09 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -568,7 +568,7 @@ effective or pretty fucking useless.
/obj/projectile/bullet/toolbox_turret
damage = 10
- speed = 0.6
+ speed = 1.6
/obj/machinery/porta_turret/syndicate/toolbox/nukie
name = "9mm turret"
diff --git a/code/game/objects/items/gift.dm b/code/game/objects/items/gift.dm
index 2965764352bfa..69b2bbd248b50 100644
--- a/code/game/objects/items/gift.dm
+++ b/code/game/objects/items/gift.dm
@@ -178,7 +178,6 @@ GLOBAL_LIST_EMPTY(possible_gifts)
//holy fuck why was this enabled
/obj/item/debug,
/obj/item/storage/box/debugbox,
- /obj/item/gun/energy/beam_rifle/debug,
/obj/item/multitool/field_debug,
/obj/item/bounty_cube/debug_cube,
/obj/item/organ/internal/cyberimp/brain/nif/debug,
diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm
index a03d345dea7b5..3b57d6b5c0cbc 100644
--- a/code/game/objects/items/grenades/plastic.dm
+++ b/code/game/objects/items/grenades/plastic.dm
@@ -135,9 +135,7 @@
var/obj/item/thrown_weapon = bomb_target
thrown_weapon.throw_speed = max(1, (thrown_weapon.throw_speed - 3))
thrown_weapon.throw_range = max(1, (thrown_weapon.throw_range - 3))
- if(thrown_weapon.embedding)
- thrown_weapon.embedding["embed_chance"] = 0
- thrown_weapon.updateEmbedding()
+ thrown_weapon.get_embed()?.embed_chance = 0
else if(isliving(bomb_target))
plastic_overlay.layer = FLOAT_LAYER
diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm
index bb75a01074b6d..c4f49234e362d 100644
--- a/code/game/objects/items/hand_items.dm
+++ b/code/game/objects/items/hand_items.dm
@@ -510,8 +510,8 @@
blown_kiss.original = target
blown_kiss.fired_from = user
blown_kiss.firer = user // don't hit ourself that would be really annoying
- blown_kiss.impacted = list(user = TRUE) // just to make sure we don't hit the wearer
- blown_kiss.preparePixelProjectile(target, user)
+ blown_kiss.impacted = list(WEAKREF(user) = TRUE) // just to make sure we don't hit the wearer
+ blown_kiss.aim_projectile(target, user)
blown_kiss.fire()
qdel(src)
return ITEM_INTERACT_SUCCESS
@@ -537,8 +537,8 @@
blown_kiss.original = taker
blown_kiss.fired_from = offerer
blown_kiss.firer = offerer // don't hit ourself that would be really annoying
- blown_kiss.impacted = list(offerer = TRUE) // just to make sure we don't hit the wearer
- blown_kiss.preparePixelProjectile(taker, offerer)
+ blown_kiss.impacted = list(WEAKREF(offerer) = TRUE) // just to make sure we don't hit the wearer
+ blown_kiss.aim_projectile(taker, offerer)
blown_kiss.suppressed = SUPPRESSED_VERY // this also means it's a direct offer
blown_kiss.fire()
qdel(src)
@@ -580,7 +580,7 @@
return ..()
-/obj/projectile/kiss/Impact(atom/A)
+/obj/projectile/kiss/impact(atom/A)
def_zone = BODY_ZONE_HEAD // let's keep it PG, people
if(damage > 0 || !isliving(A)) // if we do damage or we hit a nonliving thing, we don't have to worry about a harmless hit because we can't wrongly do damage anyway
diff --git a/code/game/objects/items/holotool/modes.dm b/code/game/objects/items/holotool/modes.dm
index a432878124068..3eec94fba1e87 100644
--- a/code/game/objects/items/holotool/modes.dm
+++ b/code/game/objects/items/holotool/modes.dm
@@ -77,13 +77,18 @@
holotool.force = 17
holotool.attack_verb_continuous = list("sliced", "torn", "cut")
holotool.armour_penetration = 45
- holotool.embedding = list("embed_chance" = 40, "embedded_fall_chance" = 0, "embedded_pain_multiplier" = 5)
+ holotool.embed_type = /datum/embedding/holotool_knife
holotool.hitsound = 'sound/weapons/blade1.ogg'
+/datum/embedding/holotool_knife
+ embed_chance = 40
+ fall_chance = 0
+ pain_mult = 5
+
/datum/holotool_mode/knife/on_unset(obj/item/holotool/holotool)
..()
holotool.force = initial(holotool.force)
holotool.attack_verb_continuous = initial(holotool.attack_verb_continuous)
holotool.armour_penetration = initial(holotool.armour_penetration)
- holotool.embedding = initial(holotool.embedding)
+ holotool.set_embed(holotool.embed_type)
holotool.hitsound = initial(holotool.hitsound)
diff --git a/code/game/objects/items/implants/hardlight.dm b/code/game/objects/items/implants/hardlight.dm
index 42ec4e05b1545..2a4b63f400c06 100644
--- a/code/game/objects/items/implants/hardlight.dm
+++ b/code/game/objects/items/implants/hardlight.dm
@@ -274,20 +274,30 @@
wound_bonus = 5
bare_wound_bonus = 20 //Why was this fifty before wtf
wound_falloff_tile = -1
- speed = 0.4 //lower = faster
+ speed = 1.7
shrapnel_type = /obj/item/shrapnel/bullet/spear
light_outer_range = 1
light_power = 1
hitsound = 'sound/weapons/bladeslice.ogg'
hitsound_wall = 'sound/weapons/parry.ogg'
- embedding = list(embed_chance=100, fall_chance=2, jostle_chance=8, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.5, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embedding/hardlight_spear
+
+/datum/embedding/hardlight_spear
+ embed_chance = 100
+ fall_chance = 2
+ jostle_chance = 8
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.5
+ pain_mult = 5
+ jostle_pain_mult = 6
+ rip_time = 10
/obj/item/shrapnel/bullet/spear
name = "hardlight spear"
icon = 'monkestation/icons/obj/items_and_weapons.dmi'
icon_state = "lightspear"
-/obj/item/shrapnel/bullet/spear/unembedded()
+/datum/embedding/hardlight_spear/stop_embedding()
. = ..()
QDEL_NULL(src) //Deletes itself when unembedded
return TRUE
diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm
index f0c2599fac258..e82af70936540 100644
--- a/code/game/objects/items/knives.dm
+++ b/code/game/objects/items/knives.dm
@@ -21,7 +21,6 @@
attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
sharpness = SHARP_EDGED
armor_type = /datum/armor/item_knife
- var/bayonet = FALSE //Can this be attached to a gun?
wound_bonus = 5
bare_wound_bonus = 15
tool_behaviour = TOOL_KNIFE
@@ -118,21 +117,25 @@
name = "combat knife"
icon_state = "buckknife"
desc = "A military combat utility survival knife."
- embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE)
+ embed_type = /datum/embedding/combat_knife
force = 17 // MONKESTATION EDIT ORG: 20
throwforce = 20
attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "cuts")
attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "cut")
- bayonet = TRUE
+
+/datum/embedding/combat_knife
+ pain_mult = 4
+ embed_chance = 65
+ fall_chance = 10
+ ignore_throwspeed_threshold = TRUE
/obj/item/knife/combat/survival
name = "survival knife"
icon_state = "survivalknife"
- embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
+ embed_type = /datum/embedding/combat_knife/weak
desc = "A hunting grade survival knife."
force = 15
throwforce = 15
- bayonet = TRUE
/obj/item/knife/combat/bone
name = "bone dagger"
@@ -142,11 +145,14 @@
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
desc = "A sharpened bone. The bare minimum in survival."
- embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
+ embed_type = /datum/embedding/combat_knife/weak
force = 15
throwforce = 15
custom_materials = null
+/datum/embedding/combat_knife/weak
+ embed_chance = 35
+
/obj/item/knife/combat/bone/Initialize(mapload)
flags_1 &= ~CONDUCT_1
return ..()
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index 922a0898d3493..cddc722cf947e 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -113,13 +113,9 @@
SIGNAL_HANDLER
if(active)
- if(embedding)
- updateEmbedding()
heat = active_heat
START_PROCESSING(SSobj, src)
else
- if(embedding)
- disableEmbedding()
heat = initial(heat)
STOP_PROCESSING(SSobj, src)
@@ -173,6 +169,10 @@
return (BRUTELOSS|FIRELOSS)
/// Energy swords.
+/datum/embedding/esword
+ embed_chance = 75
+ impact_pain_mult = 10
+
/obj/item/melee/energy/sword
name = "energy sword"
desc = "May the force be within you."
@@ -190,7 +190,7 @@
armour_ignorance = 5
block_chance = 50
block_sound = 'sound/weapons/block_blade.ogg'
- embedding = list("embed_chance" = 75, "impact_pain_mult" = 10)
+ embed_type = /datum/embedding/esword
/obj/item/melee/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm
index 6b79b4ff6904a..3f6fff2080482 100644
--- a/code/game/objects/items/mop.dm
+++ b/code/game/objects/items/mop.dm
@@ -140,7 +140,7 @@
throwforce = 18
throw_speed = 4
demolition_mod = 0.75
- embedding = list("impact_pain_mult" = 2, "remove_pain_mult" = 4, "jostle_chance" = 2.5)
+ embed_type = /datum/embedding/mop
armour_penetration = 20
armour_ignorance = 10
attack_verb_continuous = list("mops", "stabs", "shanks", "jousts")
@@ -148,3 +148,8 @@
sharpness = SHARP_EDGED //spears aren't pointy either. Just assume it's carved into a naginata-style blade
wound_bonus = -15
bare_wound_bonus = 15
+
+/datum/embedding/mop
+ impact_pain_mult = 2
+ remove_pain_mult = 4
+ jostle_chance = 2.5
diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm
index c9ad081e4c014..67403a8412936 100644
--- a/code/game/objects/items/robot/items/food.dm
+++ b/code/game/objects/items/robot/items/food.dm
@@ -86,12 +86,12 @@
return FALSE
candy--
- var/obj/item/ammo_casing/caseless/lollipop/lollipop
+ var/obj/item/ammo_casing/lollipop/lollipop
var/mob/living/silicon/robot/robot_user = user
if(istype(robot_user) && robot_user.emagged)
- lollipop = new /obj/item/ammo_casing/caseless/lollipop/harmful(src)
+ lollipop = new /obj/item/ammo_casing/lollipop/harmful(src)
else
- lollipop = new /obj/item/ammo_casing/caseless/lollipop(src)
+ lollipop = new /obj/item/ammo_casing/lollipop(src)
playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
lollipop.fire_casing(target, user, params, 0, 0, null, 0, src)
@@ -104,12 +104,12 @@
to_chat(user, span_warning("Not enough gumballs left!"))
return FALSE
candy--
- var/obj/item/ammo_casing/caseless/gumball/gumball
+ var/obj/item/ammo_casing/gumball/gumball
var/mob/living/silicon/robot/robot_user = user
if(istype(robot_user) && robot_user.emagged)
- gumball = new /obj/item/ammo_casing/caseless/gumball/harmful(src)
+ gumball = new /obj/item/ammo_casing/gumball/harmful(src)
else
- gumball = new /obj/item/ammo_casing/caseless/gumball(src)
+ gumball = new /obj/item/ammo_casing/gumball(src)
gumball.loaded_projectile.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255))
playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, TRUE)
@@ -167,78 +167,84 @@
to_chat(user, span_notice("Module is now dispensing lollipops."))
..()
-/obj/item/ammo_casing/caseless/gumball
+/obj/item/ammo_casing/gumball
name = "Gumball"
desc = "Why are you seeing this?!"
- projectile_type = /obj/projectile/bullet/reusable/gumball
+ projectile_type = /obj/projectile/bullet/gumball
click_cooldown_override = 2
-/obj/item/ammo_casing/caseless/gumball/harmful
- projectile_type = /obj/projectile/bullet/reusable/gumball/harmful
+/obj/item/ammo_casing/gumball/harmful
+ projectile_type = /obj/projectile/bullet/gumball/harmful
-/obj/projectile/bullet/reusable/gumball
+/obj/projectile/bullet/gumball
name = "gumball"
desc = "Oh noes! A fast-moving gumball!"
icon_state = "gumball"
- ammo_type = /obj/item/food/gumball
damage = 0
- speed = 0.5
+ speed = 2
+ embed_type = null
-/obj/projectile/bullet/reusable/gumball/harmful
+/obj/projectile/bullet/gumball/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/projectile_drop, /obj/item/food/gumball)
+ RegisterSignal(src, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(handle_drop))
+
+/obj/projectile/bullet/gumball/harmful
damage = 10
-/obj/projectile/bullet/reusable/gumball/handle_drop()
- if(!dropped)
- var/turf/turf = get_turf(src)
- var/obj/item/food/gumball/gumball = new ammo_type(turf)
- gumball.color = color
- dropped = TRUE
+/obj/projectile/bullet/gumball/proc/handle_drop(datum/source, obj/item/food/gumball/gumball)
+ SIGNAL_HANDLER
+ gumball.color = color
-/obj/item/ammo_casing/caseless/lollipop //NEEDS RANDOMIZED COLOR LOGIC.
+/obj/item/ammo_casing/lollipop //NEEDS RANDOMIZED COLOR LOGIC.
name = "Lollipop"
desc = "Why are you seeing this?!"
- projectile_type = /obj/projectile/bullet/reusable/lollipop
+ projectile_type = /obj/projectile/bullet/lollipop
click_cooldown_override = 2
-/obj/item/ammo_casing/caseless/lollipop/harmful
- projectile_type = /obj/projectile/bullet/reusable/lollipop/harmful
+/obj/item/ammo_casing/lollipop/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless)
+
+/obj/item/ammo_casing/lollipop/harmful
+ projectile_type = /obj/projectile/bullet/lollipop/harmful
-/obj/projectile/bullet/reusable/lollipop
+/obj/projectile/bullet/lollipop
name = "lollipop"
desc = "Oh noes! A fast-moving lollipop!"
icon_state = "lollipop_1"
- ammo_type = /obj/item/food/lollipop/cyborg
damage = 0
- speed = 0.5
- var/color2 = rgb(0, 0, 0)
-
-/obj/projectile/bullet/reusable/lollipop/harmful
- embedding = list(
- embed_chance = 35,
- fall_chance = 2,
- jostle_chance = 0,
- ignore_throwspeed_threshold = TRUE,
- pain_stam_pct = 0.5,
- pain_mult = 3,
- rip_time = 10,
- )
+ speed = 2
+ embed_type = null
+ var/head_color
+
+/obj/projectile/bullet/lollipop/harmful
+ embed_type = /datum/embedding/lollipop
damage = 10
+ shrapnel_type = /obj/item/food/lollipop/cyborg
embed_falloff_tile = 0
-/obj/projectile/bullet/reusable/lollipop/Initialize(mapload)
- var/obj/item/food/lollipop/lollipop = new ammo_type(src)
- color2 = lollipop.head_color
+/datum/embedding/lollipop
+ embed_chance = 35
+ fall_chance = 2
+ jostle_chance = 0
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.5
+ pain_mult = 3
+ rip_time = 10
+
+/obj/projectile/bullet/lollipop/Initialize(mapload)
+ . = ..()
var/mutable_appearance/head = mutable_appearance('icons/obj/weapons/guns/projectiles.dmi', "lollipop_2")
- head.color = color2
+ head.color = head_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255))
add_overlay(head)
- return ..()
+ if(!embed_type)
+ AddElement(/datum/element/projectile_drop, /obj/item/food/lollipop/cyborg)
+ RegisterSignals(src, list(COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED), PROC_REF(handle_drop))
-/obj/projectile/bullet/reusable/lollipop/handle_drop()
- if(!dropped)
- var/turf/turf = get_turf(src)
- var/obj/item/food/lollipop/lollipop = new ammo_type(turf)
- lollipop.change_head_color(color2)
- dropped = TRUE
+/obj/projectile/bullet/lollipop/proc/handle_drop(datum/source, obj/item/food/lollipop/lollipop)
+ SIGNAL_HANDLER
+ lollipop.change_head_color(head_color)
#undef DISPENSE_LOLLIPOP_MODE
#undef THROW_LOLLIPOP_MODE
diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm
index 3a7bba5ef28d1..dad4f3d37850a 100644
--- a/code/game/objects/items/robot/items/tools.dm
+++ b/code/game/objects/items/robot/items/tools.dm
@@ -33,6 +33,11 @@
var/datum/proximity_monitor/advanced/bubble/projectile_dampener/peaceborg/dampening_field
/// Energy cost per tracked projectile damage amount per second
var/projectile_damage_tick_ecost_coefficient = 10
+ /**
+ * Speed coefficient
+ * Higher the coefficient faster the projectile.
+ */
+ var/projectile_speed_coefficient = 0.66
/// Energy cost per tracked projectile per second
var/projectile_tick_speed_ecost = 75
/// Projectiles dampened by our dampener
diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm
index e6d7eb18af87c..4b905094c8ea4 100644
--- a/code/game/objects/items/shooting_range.dm
+++ b/code/game/objects/items/shooting_range.dm
@@ -81,6 +81,6 @@
desc = "A shooting target that looks like a useless clown."
max_integrity = 2000
-/obj/item/target/clown/bullet_act(obj/projectile/P)
+/obj/item/target/clown/bullet_act(obj/projectile/proj)
. = ..()
playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE)
diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm
index 05bd3e71eb017..a612d22702481 100644
--- a/code/game/objects/items/shrapnel.dm
+++ b/code/game/objects/items/shrapnel.dm
@@ -17,7 +17,7 @@
name = "bullet"
icon = 'icons/obj/weapons/guns/ammo.dmi'
icon_state = "s-casing"
- embedding = null // embedding vars are taken from the projectile itself
+ embed_type = null // embedding vars are taken from the projectile itself
/obj/projectile/bullet/shrapnel
@@ -33,7 +33,12 @@
hit_prone_targets = TRUE
sharpness = SHARP_EDGED
wound_bonus = 30
- embedding = list(embed_chance=70, ignore_throwspeed_threshold=TRUE, fall_chance=1)
+ embed_type = /datum/embedding/shrapnel
+
+/datum/embedding/shrapnel
+ embed_chance = 70
+ ignore_throwspeed_threshold = TRUE
+ fall_chance = 1
/obj/projectile/bullet/shrapnel/short_range
range = 5
@@ -68,7 +73,17 @@
ricochet_incidence_leeway = 0
embed_falloff_tile = -2
shrapnel_type = /obj/item/shrapnel/stingball
- embedding = list(embed_chance=55, fall_chance=2, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.7, pain_mult=3, jostle_pain_mult=3, rip_time=15)
+ embed_type = /datum/embedding/stingball
+
+/datum/embedding/stingball
+ embed_chance = 55
+ fall_chance = 2
+ jostle_chance = 7
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.7
+ pain_mult = 3
+ jostle_pain_mult = 3
+ rip_time = 15
/obj/projectile/bullet/pellet/stingball/on_ricochet(atom/A)
hit_prone_targets = TRUE // ducking will save you from the first wave, but not the rebounds
@@ -89,10 +104,20 @@
ricochets_max = 2
ricochet_chance = 140
shrapnel_type = /obj/item/shrapnel/capmine
- embedding = list(embed_chance=90, fall_chance=3, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.7, pain_mult=5, jostle_pain_mult=6, rip_time=15)
+ embed_type = /datum/embedding/capmine
wound_falloff_tile = 0
embed_falloff_tile = 0
+/datum/embedding/capmine
+ embed_chance = 90
+ fall_chance = 3
+ jostle_chance = 7
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.7
+ pain_mult = 5
+ jostle_pain_mult = 6
+ rip_time = 15
+
/obj/item/shrapnel/capmine
name = "\improper AP shrapnel shard"
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 0.5)
diff --git a/code/game/objects/items/soulscythe.dm b/code/game/objects/items/soulscythe.dm
index 929a2038bfdfc..97044a402ddda 100644
--- a/code/game/objects/items/soulscythe.dm
+++ b/code/game/objects/items/soulscythe.dm
@@ -202,7 +202,7 @@
return
COOLDOWN_START(src, attack_cooldown, 3 SECONDS)
var/obj/projectile/projectile = new /obj/projectile/soulscythe(get_turf(src))
- projectile.preparePixelProjectile(attacked_atom, src)
+ projectile.aim_projectile(attacked_atom, src)
projectile.firer = src
projectile.fire(null, attacked_atom)
visible_message(span_danger("[src] fires at [attacked_atom]!"), span_notice("You fire at [attacked_atom]!"))
diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm
index 7eb94016b1bcc..1d48859857a7a 100644
--- a/code/game/objects/items/spear.dm
+++ b/code/game/objects/items/spear.dm
@@ -12,7 +12,7 @@
throwforce = 20
throw_speed = 4
demolition_mod = 0.75
- embedding = list("impact_pain_mult" = 2, "remove_pain_mult" = 4, "jostle_chance" = 2.5)
+ embed_type = /datum/embedding/spear
armour_penetration = 30
custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass= HALF_SHEET_MATERIAL_AMOUNT * 2)
hitsound = 'sound/weapons/bladeslice.ogg'
@@ -32,6 +32,11 @@
/// How much damage to do wielded
var/force_wielded = 18
+/datum/embedding/spear
+ impact_pain_mult = 2
+ remove_pain_mult = 4
+ jostle_chance = 2.5
+
/datum/armor/item_spear
fire = 50
acid = 30
diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm
index 1563a4d4161d7..0f92cee2b18d1 100644
--- a/code/game/objects/items/stacks/rods.dm
+++ b/code/game/objects/items/stacks/rods.dm
@@ -33,13 +33,16 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \
attack_verb_continuous = list("hits", "bludgeons", "whacks")
attack_verb_simple = list("hit", "bludgeon", "whack")
hitsound = 'sound/weapons/gun/general/grenade_launch.ogg'
- embedding = list(embed_chance = 50)
+ embed_type = /datum/embedding/rods
novariants = TRUE
matter_amount = 2
cost = 250
source = /datum/robot_energy_storage/iron
merge_type = /obj/item/stack/rods
+/datum/embedding/rods
+ embed_chance = 50
+
/obj/item/stack/rods/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] begins to stuff \the [src] down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide!"))//it looks like theyre ur mum
return BRUTELOSS
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index 7446c3da0b822..b408d28712f39 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -291,7 +291,16 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
var/shiv_type = /obj/item/knife/shiv
var/craft_time = 3.5 SECONDS
var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass
- embedding = list("embed_chance" = 65)
+ embed_type = /datum/embedding/shard
+
+/datum/embedding/shard
+ embed_chance = 65
+
+/datum/embedding/glass_candy
+ embed_chance = 100
+ ignore_throwspeed_threshold = TRUE
+ impact_pain_mult = 1
+ pain_chance = 5
/datum/armor/item_shard
melee = 100
diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm
index 57aa666c046ab..7f7a9431f4d38 100644
--- a/code/game/objects/items/stacks/tape.dm
+++ b/code/game/objects/items/stacks/tape.dm
@@ -14,12 +14,18 @@
grind_results = list(/datum/reagent/cellulose = 5)
splint_factor = 0.65
merge_type = /obj/item/stack/sticky_tape
- var/list/conferred_embed = EMBED_HARMLESS
+ var/conferred_embed = /datum/embedding/sticky_tape
///The tape type you get when ripping off a piece of tape.
var/obj/tape_gag = /obj/item/clothing/mask/muzzle/tape
greyscale_config = /datum/greyscale_config/tape
greyscale_colors = "#B2B2B2#BD6A62"
+/datum/embedding/sticky_tape
+ pain_mult = 0
+ jostle_pain_mult = 0
+ ignore_throwspeed_threshold = 0
+ immune_traits = null
+
/obj/item/stack/sticky_tape/attack_hand(mob/user, list/modifiers)
if(user.get_inactive_held_item() == src)
if(is_zero_amount(delete_if_zero = TRUE))
@@ -43,35 +49,36 @@
if(!isitem(target))
return NONE
- if(target.embedding && target.embedding == conferred_embed)
+ if(target.get_embed()?.type == conferred_embed)
to_chat(user, span_warning("[target] is already coated in [src]!"))
return ITEM_INTERACT_BLOCKING
user.visible_message(span_notice("[user] begins wrapping [target] with [src]."), span_notice("You begin wrapping [target] with [src]."))
playsound(user, 'sound/items/duct_tape_rip.ogg', 50, TRUE)
- if(do_after(user, 3 SECONDS, target=target))
- playsound(user, 'sound/items/duct_tape_snap.ogg', 50, TRUE)
- use(1)
- if(istype(target, /obj/item/clothing/gloves/fingerless))
- var/obj/item/clothing/gloves/tackler/offbrand/O = new /obj/item/clothing/gloves/tackler/offbrand
- to_chat(user, span_notice("You turn [target] into [O] with [src]."))
- QDEL_NULL(target)
- user.put_in_hands(O)
- return ITEM_INTERACT_SUCCESS
-
- if(target.embedding && target.embedding == conferred_embed)
- to_chat(user, span_warning("[target] is already coated in [src]!"))
- return ITEM_INTERACT_BLOCKING
-
- target.embedding = conferred_embed
- target.updateEmbedding()
- to_chat(user, span_notice("You finish wrapping [target] with [src]."))
- target.name = "[prefix] [target.name]"
-
- if(isgrenade(target))
- var/obj/item/grenade/sticky_bomb = target
- sticky_bomb.sticky = TRUE
+ if(!do_after(user, 3 SECONDS, target=target))
+ return ITEM_INTERACT_BLOCKING
+
+ playsound(user, 'sound/items/duct_tape_snap.ogg', 50, TRUE)
+ use(1)
+ if(istype(target, /obj/item/clothing/gloves/fingerless))
+ var/obj/item/clothing/gloves/tackler/offbrand/O = new /obj/item/clothing/gloves/tackler/offbrand
+ to_chat(user, span_notice("You turn [target] into [O] with [src]."))
+ QDEL_NULL(target)
+ user.put_in_hands(O)
+ return ITEM_INTERACT_SUCCESS
+
+ if(target.get_embed()?.type == conferred_embed)
+ to_chat(user, span_warning("[target] is already coated in [src]!"))
+ return ITEM_INTERACT_BLOCKING
+
+ target.set_embed(conferred_embed)
+ to_chat(user, span_notice("You finish wrapping [target] with [src]."))
+ target.name = "[prefix] [target.name]"
+
+ if(isgrenade(target))
+ var/obj/item/grenade/sticky_bomb = target
+ sticky_bomb.sticky = TRUE
return ITEM_INTERACT_SUCCESS
@@ -80,45 +87,58 @@
singular_name = "super sticky tape"
desc = "Quite possibly the most mischevious substance in the galaxy. Use with extreme lack of caution."
prefix = "super sticky"
- conferred_embed = EMBED_HARMLESS_SUPERIOR
+ conferred_embed = /datum/embedding/sticky_tape/super
splint_factor = 0.4
merge_type = /obj/item/stack/sticky_tape/super
greyscale_colors = "#4D4D4D#75433F"
tape_gag = /obj/item/clothing/mask/muzzle/tape/super
+/datum/embedding/sticky_tape/super
+ embed_chance = 100
+ fall_chance = 0.1
+
/obj/item/stack/sticky_tape/pointy
name = "pointy tape"
singular_name = "pointy tape"
desc = "Used for sticking to things for sticking said things inside people."
icon_state = "tape_spikes"
prefix = "pointy"
- conferred_embed = EMBED_POINTY
+ conferred_embed = /datum/embedding/pointy_tape
merge_type = /obj/item/stack/sticky_tape/pointy
greyscale_config = /datum/greyscale_config/tape/spikes
greyscale_colors = "#E64539#808080#AD2F45"
tape_gag = /obj/item/clothing/mask/muzzle/tape/pointy
+/datum/embedding/pointy_tape
+ ignore_throwspeed_threshold = TRUE
+
/obj/item/stack/sticky_tape/pointy/super
name = "super pointy tape"
singular_name = "super pointy tape"
desc = "You didn't know tape could look so sinister. Welcome to Space Station 13."
prefix = "super pointy"
- conferred_embed = EMBED_POINTY_SUPERIOR
+ conferred_embed = /datum/embedding/pointy_tape/super
merge_type = /obj/item/stack/sticky_tape/pointy/super
greyscale_colors = "#8C0A00#4F4F4F#300008"
tape_gag = /obj/item/clothing/mask/muzzle/tape/pointy/super
+/datum/embedding/pointy_tape/super
+ embed_chance = 100
+
/obj/item/stack/sticky_tape/surgical
name = "surgical tape"
singular_name = "surgical tape"
desc = "Made for patching broken bones back together alongside bone gel, not for playing pranks."
prefix = "surgical"
- conferred_embed = list("embed_chance" = 30, "pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE)
+ conferred_embed = /datum/embedding/sticky_tape/surgical
splint_factor = 0.5
custom_price = PAYCHECK_CREW
merge_type = /obj/item/stack/sticky_tape/surgical
greyscale_colors = "#70BAE7#BD6A62"
tape_gag = /obj/item/clothing/mask/muzzle/tape/surgical
+/datum/embedding/sticky_tape/surgical
+ embed_chance = 30
+
/obj/item/stack/sticky_tape/surgical/get_surgery_tool_overlay(tray_extended)
return "tape" + (tray_extended ? "" : "_out")
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index 0a2b97ef563a5..fccb7d3f652f2 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -566,12 +566,12 @@
atom_storage.max_slots = 40
atom_storage.max_total_storage = 100
atom_storage.set_holdable(list(
- /obj/item/ammo_casing/caseless/harpoon
+ /obj/item/ammo_casing/harpoon
))
/obj/item/storage/bag/harpoon_quiver/PopulateContents()
for(var/i in 1 to 40)
- new /obj/item/ammo_casing/caseless/harpoon(src)
+ new /obj/item/ammo_casing/harpoon(src)
/obj/item/storage/bag/rebar_quiver
name = "rebar quiver"
diff --git a/code/game/objects/items/tail_pin.dm b/code/game/objects/items/tail_pin.dm
index 3b93888e76d3f..9f347b4cf714c 100644
--- a/code/game/objects/items/tail_pin.dm
+++ b/code/game/objects/items/tail_pin.dm
@@ -7,7 +7,6 @@
w_class = WEIGHT_CLASS_SMALL
throwforce = 0
throw_speed = 1
- embedding = EMBED_HARMLESS
custom_materials = list(/datum/material/iron= HALF_SHEET_MATERIAL_AMOUNT)
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb_continuous = list("pokes", "jabs", "pins the tail on")
@@ -15,6 +14,12 @@
sharpness = SHARP_POINTY
max_integrity = 200
layer = CORGI_ASS_PIN_LAYER
+ embed_type = /datum/embedding/corgi_pin
+
+/datum/embedding/corgi_pin
+ pain_chance = 0
+ jostle_pain_mult = 0
+ ignore_throwspeed_threshold = TRUE
/obj/item/poster/tail_board
name = "party game poster"
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 62f1f6b877183..9bcd3d5343380 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -180,22 +180,22 @@
return TRUE
/obj/item/toy/balloon/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers)
- if(istype(attacking_item, /obj/item/ammo_casing/caseless/foam_dart) && ismonkey(user))
+ if(istype(attacking_item, /obj/item/ammo_casing/foam_dart) && ismonkey(user))
pop_balloon(monkey_pop = TRUE)
else
return ..()
/obj/item/toy/balloon/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- if(ismonkey(throwingdatum.thrower) && istype(AM, /obj/item/ammo_casing/caseless/foam_dart))
+ if(ismonkey(throwingdatum.thrower) && istype(AM, /obj/item/ammo_casing/foam_dart))
pop_balloon(monkey_pop = TRUE)
else
return ..()
-/obj/item/toy/balloon/bullet_act(obj/projectile/P)
- if((istype(P,/obj/projectile/bullet/p50) || istype(P,/obj/projectile/bullet/reusable/foam_dart)) && ismonkey(P.firer))
+/obj/item/toy/balloon/bullet_act(obj/projectile/proj)
+ if((istype(proj, /obj/projectile/bullet/p50) || istype(proj,/obj/projectile/bullet/foam_dart)) && ismonkey(proj.firer))
pop_balloon(monkey_pop = TRUE)
- else
- return ..()
+ return BULLET_ACT_HIT
+ return ..()
/obj/item/toy/balloon/proc/pop_balloon(monkey_pop = FALSE)
playsound(src, 'sound/effects/cartoon_pop.ogg', 50, vary = TRUE)
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 71324df946295..20fb9568cf7b2 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -426,7 +426,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
force = 2
throwforce = 10 //10 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 18 damage on hit due to guaranteed embedding
throw_speed = 4
- embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0)
+ embed_type = /datum/embedding/throwing_star
armour_penetration = 75
w_class = WEIGHT_CLASS_SMALL
@@ -434,11 +434,22 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass= SMALL_MATERIAL_AMOUNT * 5)
resistance_flags = FIRE_PROOF
+/datum/embedding/throwing_star
+ pain_mult = 4
+ embed_chance = 100
+ fall_chance = 0
+
/obj/item/throwing_star/stamina
name = "shock throwing star"
desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm."
throwforce = 5
- embedding = list("pain_chance" = 5, "embed_chance" = 100, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3)
+ embed_type = /datum/embedding/throwing_star/stamina
+
+/datum/embedding/throwing_star/stamina
+ pain_mult = 5
+ jostle_chance = 10
+ pain_stam_pct = 0.8
+ jostle_pain_mult = 3
/obj/item/throwing_star/toy
name = "toy throwing star"
@@ -446,7 +457,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
sharpness = NONE
force = 0
throwforce = 0
- embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0)
+ embed_type = /datum/embedding/throwing_star/toy
+
+/datum/embedding/throwing_star/toy
+ pain_mult = 0
+ jostle_pain_mult = 0
/obj/item/switchblade
name = "switchblade"
@@ -1067,7 +1082,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
throwforce = 25
throw_speed = 4
attack_speed = CLICK_CD_HYPER_RAPID
- embedding = list("embed_chance" = 100)
+ embed_type = /datum/embedding/hfr_blade
block_chance = 25
block_sound = 'sound/weapons/parry.ogg'
sharpness = SHARP_EDGED
@@ -1082,6 +1097,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/// The previous target we attacked
var/datum/weakref/previous_target
+/datum/embedding/hfr_blade
+ embed_chance = 100
+
/obj/item/highfrequencyblade/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, \
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 636bebec9671b..a44da54728373 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -56,7 +56,7 @@
continue
defenestrated.apply_damage(10, BRUTE, part, blocked = min(90, defenestrated.getarmor(part, MELEE)), sharpness = SHARP_POINTY, wound_bonus = 4, bare_wound_bonus = 8, attacking_item = (length(shards) ? shards[1] : null))
- if(prob(25 * length(shards)) && shards[1].tryEmbed(part, TRUE))
+ if(prob(25 * length(shards)) && shards[1].force_embed(defenestrated, part))
shards -= shards[1]
if(has_grille)
@@ -109,7 +109,6 @@
if(. != BULLET_ACT_HIT)
return .
- playsound(src, hitting_projectile.hitsound, 50, TRUE)
var/damage_sustained = 0
if(!QDELETED(src)) //Bullet on_hit effect might have already destroyed this object
damage_sustained = take_damage(
@@ -122,7 +121,7 @@
)
if(hitting_projectile.suppressed != SUPPRESSED_VERY)
visible_message(
- span_danger("[src] is hit by \a [hitting_projectile.generic_name || hitting_projectile][damage_sustained ? "" : ", without leaving a mark"]!"),
+ span_danger("[src] is hit by \a [hitting_projectile][damage_sustained ? "" : ", without leaving a mark"]!"),
vision_distance = COMBAT_MESSAGE_RANGE,
)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 68d5775d848f5..b48a35735186a 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -281,11 +281,10 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag)
/obj/proc/dump_contents()
CRASH("Unimplemented.")
-/obj/handle_ricochet(obj/projectile/ricocheting)
+/obj/handle_ricochet(obj/projectile/proj)
. = ..()
if(. && receive_ricochet_damage_coeff)
- // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet
- take_damage(ricocheting.damage * receive_ricochet_damage_coeff, ricocheting.damage_type, ricocheting.armor_flag, 0, turn(ricocheting.dir, 180), ricocheting.armour_penetration)
+ take_damage(proj.damage * receive_ricochet_damage_coeff, proj.damage_type, proj.armor_flag, 0, REVERSE_DIR(proj.dir), proj.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet
/// Handles exposing an object to reagents.
/obj/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE)
diff --git a/code/game/objects/structures/deployable_turret.dm b/code/game/objects/structures/deployable_turret.dm
index 4aee113515db2..00735a9eab3c1 100644
--- a/code/game/objects/structures/deployable_turret.dm
+++ b/code/game/objects/structures/deployable_turret.dm
@@ -204,7 +204,7 @@
target = target_turf
var/obj/projectile/projectile_to_fire = new projectile_type
playsound(src, firesound, 75, TRUE)
- projectile_to_fire.preparePixelProjectile(target, targets_from)
+ projectile_to_fire.aim_projectile(target, targets_from)
projectile_to_fire.firer = user
projectile_to_fire.fired_from = src
projectile_to_fire.fire()
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 0323286c3041c..753bdd500fc5c 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -229,13 +229,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
to_chat(user, span_warning("A chill runs down your spine as [src] shatters..."))
user.AddComponent(/datum/component/omen, incidents_left = 7)
-/obj/structure/mirror/bullet_act(obj/projectile/P)
- if(broken || !isliving(P.firer) || !P.damage)
+/obj/structure/mirror/bullet_act(obj/projectile/proj)
+ if(broken || !isliving(proj.firer) || !proj.damage)
return ..()
. = ..()
if(broken) // breaking a mirror truly gets you bad luck!
- var/mob/living/unlucky_dude = P.firer
+ var/mob/living/unlucky_dude = proj.firer
to_chat(unlucky_dude, span_warning("A chill runs down your spine as [src] shatters..."))
unlucky_dude.AddComponent(/datum/component/omen, incidents_left = 7)
diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm
index a6713cf366adc..477f162517d7d 100644
--- a/code/game/objects/structures/reflector.dm
+++ b/code/game/objects/structures/reflector.dm
@@ -63,20 +63,20 @@
/obj/structure/reflector/setDir(new_dir)
return ..(NORTH)
-/obj/structure/reflector/bullet_act(obj/projectile/P)
- var/pdir = P.dir
- var/pangle = P.Angle
- var/ploc = get_turf(P)
- if(!finished || !allowed_projectile_typecache[P.type] || !(P.dir in GLOB.cardinals))
+/obj/structure/reflector/bullet_act(obj/projectile/proj)
+ var/pdir = proj.dir
+ var/pangle = proj.angle
+ var/ploc = get_turf(proj)
+ if(!finished || !allowed_projectile_typecache[proj.type] || !(proj.dir in GLOB.cardinals))
return ..()
- if(auto_reflect(P, pdir, ploc, pangle) != BULLET_ACT_FORCE_PIERCE)
+ if(auto_reflect(proj, pdir, ploc, pangle) != BULLET_ACT_FORCE_PIERCE)
return ..()
return BULLET_ACT_FORCE_PIERCE
-/obj/structure/reflector/proc/auto_reflect(obj/projectile/P, pdir, turf/ploc, pangle)
- P.ignore_source_check = TRUE
- P.range = P.decayedRange
- P.decayedRange = max(P.decayedRange--, 0)
+/obj/structure/reflector/proc/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle)
+ proj.ignore_source_check = TRUE
+ proj.range = proj.maximum_range
+ proj.maximum_range = max(proj.maximum_range--, 0)
return BULLET_ACT_FORCE_PIERCE
/obj/structure/reflector/tool_act(mob/living/user, obj/item/tool, list/modifiers)
@@ -191,12 +191,12 @@
admin = TRUE
anchored = TRUE
-/obj/structure/reflector/single/auto_reflect(obj/projectile/P, pdir, turf/ploc, pangle)
- var/incidence = GET_ANGLE_OF_INCIDENCE(rotation_angle, (P.Angle + 180))
+/obj/structure/reflector/single/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle)
+ var/incidence = GET_ANGLE_OF_INCIDENCE(rotation_angle, (proj.angle + 180))
if(abs(incidence) > 90 && abs(incidence) < 270)
return FALSE
var/new_angle = SIMPLIFY_DEGREES(rotation_angle + incidence)
- P.set_angle_centered(new_angle)
+ proj.set_angle_centered(loc, new_angle)
return ..()
//DOUBLE
@@ -217,10 +217,11 @@
admin = TRUE
anchored = TRUE
-/obj/structure/reflector/double/auto_reflect(obj/projectile/P, pdir, turf/ploc, pangle)
- var/incidence = GET_ANGLE_OF_INCIDENCE(rotation_angle, (P.Angle + 180))
+/obj/structure/reflector/double/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle)
+ var/incidence = GET_ANGLE_OF_INCIDENCE(rotation_angle, (proj.angle + 180))
var/new_angle = SIMPLIFY_DEGREES(rotation_angle + incidence)
- P.set_angle_centered(new_angle)
+ proj.forceMove(loc)
+ proj.set_angle_centered(loc, new_angle)
return ..()
//BOX
@@ -241,8 +242,8 @@
admin = TRUE
anchored = TRUE
-/obj/structure/reflector/box/auto_reflect(obj/projectile/P)
- P.set_angle_centered(rotation_angle)
+/obj/structure/reflector/box/auto_reflect(obj/projectile/proj)
+ proj.set_angle_centered(loc, rotation_angle)
return ..()
/obj/structure/reflector/ex_act()
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index a0f12419a3889..d03c957ce3afc 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -100,6 +100,8 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
var/list/old_baseturfs = baseturfs
var/old_type = type
+ var/datum/weakref/old_ref = weak_reference
+ weak_reference = null
var/list/post_change_callbacks = list()
SEND_SIGNAL(src, COMSIG_TURF_CHANGE, path, new_baseturfs, flags, post_change_callbacks)
@@ -149,6 +151,8 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
liquids = old_liquids
+ new_turf.weak_reference = old_ref
+
if(SSlighting.initialized)
if(!space_lit)
// Should have a lighting object if we never had one
diff --git a/code/modules/admin/verbs/adminfun.dm b/code/modules/admin/verbs/adminfun.dm
index 9c93e0d6ab3ce..cb71e24e0003f 100644
--- a/code/modules/admin/verbs/adminfun.dm
+++ b/code/modules/admin/verbs/adminfun.dm
@@ -192,7 +192,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(admin_smite, R_ADMIN | R_FUN, TRUE, "Smite", "Smite
divine_wrath.original = target
divine_wrath.def_zone = body_zone
divine_wrath.spread = 0
- divine_wrath.preparePixelProjectile(target, source_turf)
+ divine_wrath.aim_projectile(target, source_turf)
divine_wrath.fire()
/client/proc/punish_log(whom, punishment)
diff --git a/code/modules/antagonists/clown_ops/clown_weapons.dm b/code/modules/antagonists/clown_ops/clown_weapons.dm
index 2977f688f1efd..7925b7bab9745 100644
--- a/code/modules/antagonists/clown_ops/clown_weapons.dm
+++ b/code/modules/antagonists/clown_ops/clown_weapons.dm
@@ -90,7 +90,7 @@
force = 0
throwforce = 0
hitsound = null
- embedding = null
+ embed_type = null
light_color = COLOR_YELLOW
sword_color_icon = "bananium"
active_heat = 0
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 5f4eb17ef3c1e..d8e3eba17eb8d 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -997,7 +997,8 @@ Striking a noncultist, however, will tear their flesh."}
owner.Paralyze(25)
qdel(src)
return FALSE
- if(P.reflectable & REFLECT_NORMAL)
+ var/obj/projectile/projectile = hitby
+ if(projectile.reflectable)
return FALSE //To avoid reflection chance double-dipping with block chance
. = ..()
if(.)
diff --git a/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm b/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm
index d98034ca20374..b1a19eda54121 100644
--- a/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm
+++ b/code/modules/antagonists/darkspawn/darkspawn_abilities/thrall_spells.dm
@@ -214,7 +214,7 @@
/datum/action/cooldown/spell/pointed/mindblast/proc/ready_projectile(obj/projectile/to_fire, atom/target, mob/shooter)
to_fire.firer = owner
to_fire.fired_from = shooter
- to_fire.preparePixelProjectile(target, shooter)
+ to_fire.aim_projectile(target, shooter)
if(istype(to_fire, /obj/projectile/magic))
var/obj/projectile/magic/magic_to_fire = to_fire
diff --git a/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm b/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm
index 8654d8ce6b50f..f641a46ccbf2a 100644
--- a/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm
+++ b/code/modules/antagonists/darkspawn/darkspawn_objects/shadow_caster.dm
@@ -41,7 +41,7 @@
/// Recharges a bolt, done after the delay in shoot_live_shot
/obj/item/gun/ballistic/bow/shadow_caster/proc/recharge_bolt()
- var/obj/item/ammo_casing/caseless/arrow/shadow/bolt = new
+ var/obj/item/ammo_casing/arrow/shadow/bolt = new
magazine.give_round(bolt)
chambered = bolt
update_icon()
@@ -52,17 +52,17 @@
// the thing that holds the ammo inside the bow
/obj/item/ammo_box/magazine/internal/bow/shadow
- ammo_type = /obj/item/ammo_casing/caseless/arrow/shadow
+ ammo_type = /obj/item/ammo_casing/arrow/shadow
start_empty = FALSE
//the object that appears when the arrow finishes flying
-/obj/item/ammo_casing/caseless/arrow/shadow
+/obj/item/ammo_casing/arrow/shadow
name = "shadow arrow"
desc = "it seems to suck light out of the surroundings."
icon = 'icons/obj/darkspawn_projectiles.dmi'
icon_state = "caster_arrow"
inhand_icon_state = null
- embedding = list("embed_chance" = 20, "embedded_fall_chance" = 0)
+ embed_type = /datum/embedding/shadow_arrow
projectile_type = /obj/projectile/bullet/shadow_arrow
//the projectile being shot from the bow
@@ -73,7 +73,11 @@
damage = 25 //reduced damage per arrow compared to regular ones
damage_type = BURN
wound_bonus = -100
- embedding = list("embed_chance" = 20, "embedded_fall_chance" = 0)
+ embed_type = /datum/embedding/shadow_arrow
+
+/datum/embedding/shadow_arrow
+ embed_chance = 20
+ fall_chance = 0
/obj/projectile/bullet/shadow_arrow/Initialize(mapload)
. = ..()
diff --git a/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm b/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm
index ba647ca590ccf..8a45bde1f48c0 100644
--- a/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm
+++ b/code/modules/antagonists/darkspawn/darkspawn_objects/umbral_tendrils.dm
@@ -79,7 +79,7 @@
span_velvet("opehhjaoo
You swing your tendrils towards [target]!"))
playsound(user, 'sound/magic/tail_swing.ogg', 50, TRUE)
var/obj/projectile/umbral_tendrils/T = new(get_turf(user))
- T.preparePixelProjectile(target, user)
+ T.aim_projectile(target, user)
T.twinned = twin
T.firer = user
T.fire()
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm
index 90e45b0ce3766..54bb9e22da0ab 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm
@@ -111,7 +111,7 @@
new /obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, victim.dir)
playsound(spawn_turf, 'sound/effects/curse2.ogg', 80, TRUE, -1)
var/obj/projectile/curse_hand/hel/hand = new (spawn_turf)
- hand.preparePixelProjectile(victim, spawn_turf)
+ hand.aim_projectile(victim, spawn_turf)
if (QDELETED(hand)) // safety check if above fails - above has a stack trace if it does fail
return
hand.fire()
diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm
index bcbd260c82256..ecf73afbcd987 100644
--- a/code/modules/antagonists/heretic/magic/furious_steel.dm
+++ b/code/modules/antagonists/heretic/magic/furious_steel.dm
@@ -116,7 +116,7 @@
name = "blade"
icon = 'icons/effects/eldritch.dmi'
icon_state = "dio_knife"
- speed = 2
+ speed = 0.5
damage = 25
armour_penetration = 100
sharpness = SHARP_EDGED
diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm
index b2bfa13d48a2f..478b9e8d27e80 100644
--- a/code/modules/antagonists/heretic/magic/moon_parade.dm
+++ b/code/modules/antagonists/heretic/magic/moon_parade.dm
@@ -26,12 +26,11 @@
icon_state = "lunar_parade"
damage = 0
damage_type = BURN
- speed = 1
+ speed = 0.2
range = 75
ricochets_max = 40
ricochet_chance = 500
ricochet_incidence_leeway = 0
- pixel_speed_multiplier = 0.2
projectile_piercing = PASSMOB|PASSVEHICLE
///looping sound datum for our projectile.
var/datum/looping_sound/moon_parade/soundloop
diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm
index 5ca4b7da07e4b..e1b1bc6e408ed 100644
--- a/code/modules/antagonists/heretic/magic/rust_wave.dm
+++ b/code/modules/antagonists/heretic/magic/rust_wave.dm
@@ -116,4 +116,4 @@
/obj/projectile/magic/aoe/rust_wave/short
range = 7
- speed = 2
+ speed = 0.5
diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm
index 212e90535d6c7..98c953e4c2fb8 100644
--- a/code/modules/antagonists/heretic/magic/star_blast.dm
+++ b/code/modules/antagonists/heretic/magic/star_blast.dm
@@ -24,10 +24,9 @@
icon_state = "star_ball"
damage = 20
damage_type = BURN
- speed = 1
+ speed = 0.2
range = 100
knockdown = 4 SECONDS
- pixel_speed_multiplier = 0.2
/// Effect for when the ball hits something
var/obj/effect/explosion_effect = /obj/effect/temp_visual/cosmic_explosion
/// The range at which people will get marked with a star mark.
diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm
index 8af58d4d83244..3ca95c6018f17 100644
--- a/code/modules/antagonists/heretic/structures/carving_knife.dm
+++ b/code/modules/antagonists/heretic/structures/carving_knife.dm
@@ -15,15 +15,7 @@
attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends")
attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend")
actions_types = list(/datum/action/item_action/rune_shatter)
- embedding = list(
- ignore_throwspeed_threshold = TRUE,
- embed_chance = 75,
- jostle_chance = 2,
- jostle_pain_mult = 5,
- pain_stam_pct = 0.4,
- pain_mult = 3,
- rip_time = 15,
- )
+ embed_type = /datum/embedding/rune_carver
/// Whether we're currently drawing a rune
var/drawing = FALSE
@@ -34,6 +26,15 @@
/// Turfs that you cannot draw carvings on
var/static/list/blacklisted_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/lava))
+/datum/embedding/rune_carver
+ ignore_throwspeed_threshold = TRUE
+ embed_chance = 75
+ jostle_chance = 2
+ jostle_pain_mult = 5
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ rip_time = 15
+
/obj/item/melee/rune_carver/examine(mob/user)
. = ..()
if(!IS_HERETIC_OR_MONSTER(user) && !isobserver(user))
diff --git a/code/modules/antagonists/monster_hunters/weapons/darkmoon.dm b/code/modules/antagonists/monster_hunters/weapons/darkmoon.dm
index 95eaab5ad15b8..fdb9ec8ab4ea4 100644
--- a/code/modules/antagonists/monster_hunters/weapons/darkmoon.dm
+++ b/code/modules/antagonists/monster_hunters/weapons/darkmoon.dm
@@ -57,7 +57,7 @@
if(!isturf(proj_turf))
return
var/obj/projectile/moonbeam/moon = new(proj_turf)
- moon.preparePixelProjectile(target, user, modifiers)
+ moon.aim_projectile(target, user, modifiers)
moon.firer = user
playsound(src, 'monkestation/sound/weapons/moonlightbeam.ogg', vol = 50)
moon.fire()
diff --git a/code/modules/capture_the_flag/ctf_equipment.dm b/code/modules/capture_the_flag/ctf_equipment.dm
index 8fcea14b9d58b..c8f5b4ca61a98 100644
--- a/code/modules/capture_the_flag/ctf_equipment.dm
+++ b/code/modules/capture_the_flag/ctf_equipment.dm
@@ -20,17 +20,17 @@
return BULLET_ACT_HIT
/obj/item/ammo_box/magazine/recharge/ctf
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf
+ ammo_type = /obj/item/ammo_casing/laser/ctf
/obj/item/ammo_box/magazine/recharge/ctf/Initialize(mapload)
. = ..()
AddElement(/datum/element/delete_on_drop)
-/obj/item/ammo_casing/caseless/laser/ctf
+/obj/item/ammo_casing/laser/ctf
projectile_type = /obj/projectile/beam/ctf/
-/obj/item/ammo_casing/caseless/laser/ctf/Initialize(mapload)
+/obj/item/ammo_casing/laser/ctf/Initialize(mapload)
. = ..()
AddElement(/datum/element/delete_on_drop)
@@ -49,10 +49,10 @@
/obj/item/ammo_box/magazine/recharge/ctf/rifle
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/rifle
+ ammo_type = /obj/item/ammo_casing/laser/ctf/rifle
-/obj/item/ammo_casing/caseless/laser/ctf/rifle
+/obj/item/ammo_casing/laser/ctf/rifle
projectile_type = /obj/projectile/beam/ctf/rifle
@@ -82,11 +82,11 @@
AddElement(/datum/element/delete_on_drop)
/obj/item/ammo_box/magazine/recharge/ctf/shotgun
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/shotgun
+ ammo_type = /obj/item/ammo_casing/laser/ctf/shotgun
max_ammo = 6
-/obj/item/ammo_casing/caseless/laser/ctf/shotgun
+/obj/item/ammo_casing/laser/ctf/shotgun
projectile_type = /obj/projectile/beam/ctf/shotgun
pellets = 6
variance = 25
@@ -107,14 +107,15 @@
fire_delay = 1 SECONDS
/obj/item/ammo_box/magazine/recharge/ctf/marksman
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/marksman
+ ammo_type = /obj/item/ammo_casing/laser/ctf/marksman
max_ammo = 10
-/obj/item/ammo_casing/caseless/laser/ctf/marksman
+/obj/item/ammo_casing/laser/ctf/marksman
projectile_type = /obj/projectile/beam/ctf/marksman
/obj/projectile/beam/ctf/marksman
damage = 30
+ icon_state = null
hitscan = TRUE
tracer_type = /obj/effect/projectile/tracer/laser/blue
muzzle_type = /obj/effect/projectile/muzzle/laser/blue
@@ -133,11 +134,11 @@
/obj/item/ammo_box/magazine/recharge/ctf/deagle
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/deagle
+ ammo_type = /obj/item/ammo_casing/laser/ctf/deagle
max_ammo = 7
-/obj/item/ammo_casing/caseless/laser/ctf/deagle
+/obj/item/ammo_casing/laser/ctf/deagle
projectile_type = /obj/projectile/beam/ctf/deagle
@@ -242,9 +243,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/rifle/red
/obj/item/ammo_box/magazine/recharge/ctf/rifle/red
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/rifle/red
+ ammo_type = /obj/item/ammo_casing/laser/ctf/rifle/red
-/obj/item/ammo_casing/caseless/laser/ctf/rifle/red
+/obj/item/ammo_casing/laser/ctf/rifle/red
projectile_type = /obj/projectile/beam/ctf/rifle/red
/obj/projectile/beam/ctf/rifle/red
@@ -258,9 +259,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/shotgun/red
/obj/item/ammo_box/magazine/recharge/ctf/shotgun/red
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/shotgun/red
+ ammo_type = /obj/item/ammo_casing/laser/ctf/shotgun/red
-/obj/item/ammo_casing/caseless/laser/ctf/shotgun/red
+/obj/item/ammo_casing/laser/ctf/shotgun/red
projectile_type = /obj/projectile/beam/ctf/shotgun/red
/obj/projectile/beam/ctf/shotgun/red
@@ -274,9 +275,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/marksman/red
/obj/item/ammo_box/magazine/recharge/ctf/marksman/red
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/marksman/red
+ ammo_type = /obj/item/ammo_casing/laser/ctf/marksman/red
-/obj/item/ammo_casing/caseless/laser/ctf/marksman/red
+/obj/item/ammo_casing/laser/ctf/marksman/red
projectile_type = /obj/projectile/beam/ctf/marksman/red
/obj/projectile/beam/ctf/marksman/red
@@ -308,9 +309,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/rifle/blue
/obj/item/ammo_box/magazine/recharge/ctf/rifle/blue
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/rifle/blue
+ ammo_type = /obj/item/ammo_casing/laser/ctf/rifle/blue
-/obj/item/ammo_casing/caseless/laser/ctf/rifle/blue
+/obj/item/ammo_casing/laser/ctf/rifle/blue
projectile_type = /obj/projectile/beam/ctf/rifle/blue
/obj/projectile/beam/ctf/rifle/blue
@@ -322,9 +323,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/shotgun/blue
/obj/item/ammo_box/magazine/recharge/ctf/shotgun/blue
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/shotgun/blue
+ ammo_type = /obj/item/ammo_casing/laser/ctf/shotgun/blue
-/obj/item/ammo_casing/caseless/laser/ctf/shotgun/blue
+/obj/item/ammo_casing/laser/ctf/shotgun/blue
projectile_type = /obj/projectile/beam/ctf/shotgun/blue
/obj/projectile/beam/ctf/shotgun/blue
@@ -337,9 +338,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/marksman/blue
/obj/item/ammo_box/magazine/recharge/ctf/marksman/blue
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/marksman/blue
+ ammo_type = /obj/item/ammo_casing/laser/ctf/marksman/blue
-/obj/item/ammo_casing/caseless/laser/ctf/marksman/blue
+/obj/item/ammo_casing/laser/ctf/marksman/blue
projectile_type = /obj/projectile/beam/ctf/marksman/blue
/obj/projectile/beam/ctf/marksman/blue
@@ -367,9 +368,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/rifle/green
/obj/item/ammo_box/magazine/recharge/ctf/rifle/green
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/rifle/green
+ ammo_type = /obj/item/ammo_casing/laser/ctf/rifle/green
-/obj/item/ammo_casing/caseless/laser/ctf/rifle/green
+/obj/item/ammo_casing/laser/ctf/rifle/green
projectile_type = /obj/projectile/beam/ctf/rifle/green
/obj/projectile/beam/ctf/rifle/green
@@ -383,9 +384,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/shotgun/green
/obj/item/ammo_box/magazine/recharge/ctf/shotgun/green
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/shotgun/green
+ ammo_type = /obj/item/ammo_casing/laser/ctf/shotgun/green
-/obj/item/ammo_casing/caseless/laser/ctf/shotgun/green
+/obj/item/ammo_casing/laser/ctf/shotgun/green
projectile_type = /obj/projectile/beam/ctf/shotgun/green
/obj/projectile/beam/ctf/shotgun/green
@@ -399,9 +400,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/marksman/green
/obj/item/ammo_box/magazine/recharge/ctf/marksman/green
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/marksman/green
+ ammo_type = /obj/item/ammo_casing/laser/ctf/marksman/green
-/obj/item/ammo_casing/caseless/laser/ctf/marksman/green
+/obj/item/ammo_casing/laser/ctf/marksman/green
projectile_type = /obj/projectile/beam/ctf/marksman/green
/obj/projectile/beam/ctf/marksman/green
@@ -433,9 +434,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/rifle/yellow
/obj/item/ammo_box/magazine/recharge/ctf/rifle/yellow
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/rifle/yellow
+ ammo_type = /obj/item/ammo_casing/laser/ctf/rifle/yellow
-/obj/item/ammo_casing/caseless/laser/ctf/rifle/yellow
+/obj/item/ammo_casing/laser/ctf/rifle/yellow
projectile_type = /obj/projectile/beam/ctf/rifle/yellow
/obj/projectile/beam/ctf/rifle/yellow
@@ -449,9 +450,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/shotgun/yellow
/obj/item/ammo_box/magazine/recharge/ctf/shotgun/yellow
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/shotgun/yellow
+ ammo_type = /obj/item/ammo_casing/laser/ctf/shotgun/yellow
-/obj/item/ammo_casing/caseless/laser/ctf/shotgun/yellow
+/obj/item/ammo_casing/laser/ctf/shotgun/yellow
projectile_type = /obj/projectile/beam/ctf/shotgun/yellow
/obj/projectile/beam/ctf/shotgun/yellow
@@ -465,9 +466,9 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/recharge/ctf/marksman/yellow
/obj/item/ammo_box/magazine/recharge/ctf/marksman/yellow
- ammo_type = /obj/item/ammo_casing/caseless/laser/ctf/marksman/yellow
+ ammo_type = /obj/item/ammo_casing/laser/ctf/marksman/yellow
-/obj/item/ammo_casing/caseless/laser/ctf/marksman/yellow
+/obj/item/ammo_casing/laser/ctf/marksman/yellow
projectile_type = /obj/projectile/beam/ctf/marksman/yellow
/obj/projectile/beam/ctf/marksman/yellow
diff --git a/code/modules/cassettes/machines/dj_station.dm b/code/modules/cassettes/machines/dj_station.dm
index 02ab9d92aae7d..aa648b5d71a5f 100644
--- a/code/modules/cassettes/machines/dj_station.dm
+++ b/code/modules/cassettes/machines/dj_station.dm
@@ -411,13 +411,12 @@ GLOBAL_DATUM(dj_booth, /obj/machinery/dj_station)
// Funny.
/obj/machinery/dj_station/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
- SHOULD_CALL_PARENT(FALSE)
+ . = ..()
visible_message(span_warning("[hitting_projectile] bounces harmlessly off of [src]!"))
// doesn't actually do any damage, this is meant to annoy people when they try to shoot it bc someone played pickle rick
hitting_projectile.damage = 0
hitting_projectile.stamina = 0
hitting_projectile.reflect(src)
- return BULLET_ACT_FORCE_PIERCE
// TODO: clean all of this shit up
/obj/machinery/dj_station/proc/play_to_all_listeners()
diff --git a/code/modules/clothing/shoes/gunboots.dm b/code/modules/clothing/shoes/gunboots.dm
index db24b2338cf3a..fd9a30e1ad9dd 100644
--- a/code/modules/clothing/shoes/gunboots.dm
+++ b/code/modules/clothing/shoes/gunboots.dm
@@ -61,7 +61,7 @@
shot.firer = wearer // don't hit ourself that would be really annoying
shot.impacted = list(wearer = TRUE)
shot.def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) // they're fired from boots after all
- shot.preparePixelProjectile(target, wearer)
+ shot.aim_projectile(target, wearer)
if(!shot.suppressed)
wearer.visible_message(span_danger("[wearer]'s [name] fires \a [shot]!"), "", blind_message = span_hear("You hear a gunshot!"), vision_distance=COMBAT_MESSAGE_RANGE)
shot.fire()
diff --git a/code/modules/deathmatch/deathmatch_loadouts.dm b/code/modules/deathmatch/deathmatch_loadouts.dm
index dbd5d50cf679e..4bb7a488e39c9 100644
--- a/code/modules/deathmatch/deathmatch_loadouts.dm
+++ b/code/modules/deathmatch/deathmatch_loadouts.dm
@@ -175,7 +175,7 @@
desc = "How can plants help you?"
species_override = /datum/species/pod
l_hand = /obj/item/gun/ballistic/bow
- r_hand = /obj/item/ammo_casing/caseless/arrow
+ r_hand = /obj/item/ammo_casing/arrow
l_pocket = /obj/item/knife/shiv/carrot
r_pocket = /obj/item/flashlight/lantern
head = /obj/item/food/grown/ambrosia/gaia
@@ -189,7 +189,7 @@
backpack_contents = list(
/obj/item/reagent_containers/syringe/crude/mushroom = 1,
/obj/item/reagent_containers/syringe/crude/blastoff = 1,
- /obj/item/ammo_casing/caseless/arrow = 2,
+ /obj/item/ammo_casing/arrow = 2,
/obj/item/food/grown/nettle/death = 2,
/obj/item/food/grown/banana = 2,
/obj/item/food/grown/cherry_bomb = 2,
diff --git a/code/modules/events/wizard/embeddies.dm b/code/modules/events/wizard/embeddies.dm
index 1ae6769cb8c1b..2196b35134a78 100644
--- a/code/modules/events/wizard/embeddies.dm
+++ b/code/modules/events/wizard/embeddies.dm
@@ -43,9 +43,12 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding)
* Makes every item in the world embed when thrown, but also hooks into global signals for new items created to also bless them with embed-ability(??).
*/
/datum/global_funny_embedding
- var/embed_type = EMBED_POINTY
+ var/embed_type = /datum/embedding/global_funny
var/prefix = "error"
+/datum/embedding/global_funny
+ ignore_throwspeed_threshold = TRUE
+
/datum/global_funny_embedding/New()
. = ..()
//second operation takes MUCH longer, so lets set up signals first.
@@ -61,11 +64,10 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding)
SIGNAL_HANDLER
// this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:)
- if(LAZYLEN(created_item.embedding))
- return //already embeds to some degree, so whatever 🐀
- created_item.embedding = embed_type
+ if(created_item.get_embed())
+ return //already embeds to some degree, so whatever // No rat allowed
created_item.name = "[prefix] [created_item.name]"
- created_item.updateEmbedding()
+ created_item.set_embed(embed_type)
/**
* ### handle_current_items
@@ -77,17 +79,20 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding)
CHECK_TICK
if(QDELETED(embed_item) || !(embed_item.flags_1 & INITIALIZED_1))
continue
- if(!embed_item.embedding)
- embed_item.embedding = embed_type
- embed_item.updateEmbedding()
- embed_item.name = "[prefix] [embed_item.name]"
+ if(embed_item.get_embed())
+ continue
+ embed_item.set_embed(embed_type)
+ embed_item.name = "[prefix] [embed_item.name]"
///everything will be... POINTY!!!!
/datum/global_funny_embedding/pointy
- embed_type = EMBED_POINTY
prefix = "pointy"
///everything will be... sticky? sure, why not
/datum/global_funny_embedding/sticky
- embed_type = EMBED_HARMLESS
+ embed_type = /datum/embedding/global_funny/sticky
prefix = "sticky"
+
+/datum/embedding/global_funny/sticky
+ pain_mult = 0
+ jostle_pain_mult = 0
diff --git a/code/modules/events/wormholes.dm b/code/modules/events/wormholes.dm
index 502cd1cdeba06..288001d50772e 100644
--- a/code/modules/events/wormholes.dm
+++ b/code/modules/events/wormholes.dm
@@ -69,7 +69,7 @@ GLOBAL_LIST_EMPTY(all_wormholes) // So we can pick wormholes to teleport to
. = ..()
GLOB.all_wormholes -= src
-/obj/effect/portal/wormhole/teleport(atom/movable/M)
+/obj/effect/portal/wormhole/teleport(atom/movable/M, force = FALSE)
if(iseffect(M)) //sparks don't teleport
return
if(M.anchored)
diff --git a/code/modules/experisci/experiment/physical_experiments.dm b/code/modules/experisci/experiment/physical_experiments.dm
index 6b88e19e4a3b6..60303814e44ef 100644
--- a/code/modules/experisci/experiment/physical_experiments.dm
+++ b/code/modules/experisci/experiment/physical_experiments.dm
@@ -21,9 +21,9 @@
/datum/experiment/physical/meat_wall_explosion/check_progress()
. += EXPERIMENT_PROG_BOOL("Fire an emitter at a tracked meat wall", is_complete())
-/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/Proj)
+/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/proj)
SIGNAL_HANDLER
- if(istype(Proj, /obj/projectile/beam/emitter))
+ if(istype(proj, /obj/projectile/beam/emitter))
finish_experiment(linked_experiment_handler)
/datum/experiment/physical/meat_wall_explosion/finish_experiment(datum/component/experiment_handler/experiment_handler)
diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm
index ccff495a7c884..39eaae2474cf6 100644
--- a/code/modules/fishing/fishing_equipment.dm
+++ b/code/modules/fishing/fishing_equipment.dm
@@ -15,6 +15,8 @@
var/list/fishing_line_traits
/// Color of the fishing line
var/line_color = "#808080"
+ ///The amount of range this fishing line adds to casting
+ var/cast_range = 2
/obj/item/fishing_line/reinforced
name = "reinforced fishing line reel"
@@ -36,6 +38,7 @@
icon_state = "reel_red"
fishing_line_traits = FISHING_LINE_BOUNCY
line_color = "#99313f"
+ cast_range = 3
/obj/item/fishing_line/sinew
name = "fishing sinew"
diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm
index 67ac6779c0a8e..cf73b44927860 100644
--- a/code/modules/fishing/fishing_rod.dm
+++ b/code/modules/fishing/fishing_rod.dm
@@ -165,6 +165,14 @@
fishing_line = null
currently_hooked = null
+/obj/item/fishing_rod/proc/get_cast_range(mob/living/user)
+ . = max(cast_range + line?.cast_range, 1)
+ user = user || loc
+ if (!isliving(user) || !user.mind || !user.is_holding(src))
+ return
+ . += round(user.mind.get_skill_level(/datum/skill/fishing) * 0.3)
+ return max(., 1)
+
/obj/item/fishing_rod/dropped(mob/user, silent)
. = ..()
QDEL_NULL(fishing_line)
@@ -228,13 +236,14 @@
COOLDOWN_START(src, casting_cd, 1 SECONDS)
casting = TRUE
var/obj/projectile/fishing_cast/cast_projectile = new(get_turf(src))
- cast_projectile.range = cast_range
+ cast_projectile.range = get_cast_range(user)
+ cast_projectile.maximum_range = get_cast_range(user)
cast_projectile.owner = src
cast_projectile.original = target
cast_projectile.fired_from = src
cast_projectile.firer = user
- cast_projectile.impacted = list(user = TRUE)
- cast_projectile.preparePixelProjectile(target, user)
+ cast_projectile.impacted = list(WEAKREF(user) = TRUE)
+ cast_projectile.aim_projectile(target, user)
cast_projectile.fire()
/// Called by hook projectile when hitting things
@@ -566,7 +575,7 @@
var/obj/item/fishing_rod/owner
var/datum/beam/our_line
-/obj/projectile/fishing_cast/Impact(atom/hit_atom)
+/obj/projectile/fishing_cast/impact(atom/hit_atom)
. = ..()
owner.hook_hit(hit_atom)
qdel(src)
diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm
index b5009230d7556..532b12f286add 100644
--- a/code/modules/hallucination/stray_bullet.dm
+++ b/code/modules/hallucination/stray_bullet.dm
@@ -16,7 +16,7 @@
var/obj/projectile/hallucination/fake_projectile = new fake_type(start, src)
- fake_projectile.preparePixelProjectile(hallucinator, start)
+ fake_projectile.aim_projectile(hallucinator, start)
fake_projectile.fire()
QDEL_IN(src, 10 SECONDS) // Should clean up the projectile if it somehow gets stuck.
@@ -32,7 +32,6 @@
ricochets_max = 0
ricochet_chance = 0
damage = 0
- projectile_type = /obj/projectile/hallucination
log_override = TRUE
/// Our parent hallucination that's created us
var/datum/hallucination/parent
@@ -213,7 +212,7 @@
ricochets_max = 50
ricochet_chance = 80
- reflectable = REFLECT_NORMAL // No idea if this works
+ reflectable = TRUE
/obj/projectile/hallucination/laser/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.stamina.adjust(-10)
@@ -259,7 +258,7 @@
ricochets_max = 50
ricochet_chance = 80
- reflectable = REFLECT_NORMAL // No idea if this works
+ reflectable = TRUE
/obj/projectile/hallucination/disabler/apply_effect_to_hallucinator(mob/living/afflicted)
afflicted.stamina.adjust(-15)
diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm
index 696668b010669..a1cf08fbda589 100644
--- a/code/modules/holodeck/items.dm
+++ b/code/modules/holodeck/items.dm
@@ -14,7 +14,7 @@
throw_speed = 2
block_chance = 0
throwforce = 0
- embedding = null
+ embed_type = null
sword_color_icon = null
active_throwforce = 0
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index c055d396cc8d5..21b225da72aa0 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -489,7 +489,7 @@
throwforce = 15
throw_speed = 4
throw_range = 7
- embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
+ embed_type = /datum/embedding/hatchet
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5)
attack_verb_continuous = list("chops", "tears", "lacerates", "cuts")
attack_verb_simple = list("chop", "tear", "lacerate", "cut")
@@ -497,6 +497,11 @@
sharpness = SHARP_EDGED
tool_behaviour = TOOL_SAW
+/datum/embedding/hatchet
+ pain_mult = 4
+ embed_chance = 35
+ fall_chance = 10
+
/obj/item/hatchet/Initialize(mapload)
. = ..()
AddComponent(/datum/component/butchering, \
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index 486496b9ccacf..cef071e5c112a 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -229,20 +229,21 @@
return ..()
//Works with the Somatoray to modify plant variables.
-/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj)
+/obj/machinery/hydroponics/bullet_act(obj/projectile/proj)
if(!myseed)
return ..()
- if(istype(Proj , /obj/projectile/energy/flora/mut))
+ if(istype(proj, /obj/projectile/energy/flora/mut))
mutate()
- else if(istype(Proj , /obj/projectile/energy/flora/yield))
- return myseed.bullet_act(Proj)
- else if(istype(Proj , /obj/projectile/energy/flora/evolution))
+ return BULLET_ACT_HIT
+ if(istype(proj, /obj/projectile/energy/flora/yield))
+ return myseed.projectile_hit(proj)
+ if(istype(proj, /obj/projectile/energy/flora/evolution))
if(myseed)
if(LAZYLEN(myseed.mutatelist))
myseed.mutate()
mutatespecie()
- else
- return ..()
+ return BULLET_ACT_HIT
+ return ..()
/obj/machinery/hydroponics/process(delta_time)
var/needs_update = FALSE // Checks if the icon needs updating so we don't redraw empty trays every time
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index cec6b6531c8ed..57e92f81f0c27 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -972,12 +972,29 @@
return
var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
- if(our_seed.get_gene(/datum/plant_gene/trait/stinging))
- our_plant.embedding = EMBED_POINTY
- else
- our_plant.embedding = EMBED_HARMLESS
- our_plant.updateEmbedding()
our_plant.throwforce = qp_sigmoid(1000, 50, our_seed.potency)
+ var/datum/embedding/plant_embed = our_plant.get_embed()
+ if (!plant_embed)
+ if(our_seed.get_gene(/datum/plant_gene/trait/stinging))
+ our_plant.set_embed(/datum/embedding/spiky_plant)
+ else
+ our_plant.set_embed(/datum/embedding/sticky_plant)
+ return
+
+ plant_embed.ignore_throwspeed_threshold = TRUE
+ if(our_seed.get_gene(/datum/plant_gene/trait/stinging))
+ return
+
+ plant_embed.pain_mult = 0
+ plant_embed.jostle_pain_mult = 0
+
+/datum/embedding/sticky_plant
+ pain_mult = 0
+ jostle_pain_mult = 0
+ ignore_throwspeed_threshold = TRUE
+
+/datum/embedding/spiky_plant
+ ignore_throwspeed_threshold = TRUE
/**
* This trait automatically heats up the plant's chemical contents when harvested.
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 92205eaf8a8a6..78e4982584c3f 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -228,16 +228,18 @@
-/obj/item/seeds/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables.
- if(istype(Proj, /obj/projectile/energy/flora/yield))
- var/rating = 1
- if(yield == 0)//Oh god don't divide by zero you'll doom us all.
- adjust_yield(1 * rating)
- else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2...
- adjust_yield(1 * rating)
- else
+/obj/item/seeds/bullet_act(obj/projectile/proj) //Works with the Somatoray to modify plant variables.
+ if(!istype(proj, /obj/projectile/energy/flora/yield))
return ..()
-
+ var/rating = 1
+ if(istype(loc, /obj/machinery/hydroponics))
+ var/obj/machinery/hydroponics/H = loc
+ rating = H.rating
+ if(yield == 0)//Oh god don't divide by zero you'll doom us all.
+ adjust_yield(1 * rating)
+ else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2...
+ adjust_yield(1 * rating)
+ return BULLET_ACT_HIT
// Harvest procs
/obj/item/seeds/proc/getYield()
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index 4958b0d9ae0f5..394e76c0d6817 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -109,8 +109,8 @@
var/obj/item/stack/sheet/animalhide/goliath_hide/plating = new()
explorer_suit.hood.attackby(plating)
for(var/obj/item/gun/energy/recharge/kinetic_accelerator/accelerator in miner_contents)
- var/obj/item/knife/combat/survival/knife = new(accelerator)
- accelerator.bayonet = knife
+ var/datum/component/bayonet_attachable/bayonet = accelerator.GetComponent(/datum/component/bayonet_attachable)
+ bayonet.add_bayonet(new /obj/item/knife/combat/survival(accelerator))
var/obj/item/flashlight/seclite/flashlight = new()
var/datum/component/seclite_attachable/light_component = accelerator.GetComponent(/datum/component/seclite_attachable)
light_component.add_light(flashlight)
diff --git a/code/modules/mining/equipment/kinetic_crusher/crusher_variants/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher/crusher_variants/kinetic_crusher.dm
index 0fc5aafe6f77b..a6b95d17aa2b0 100644
--- a/code/modules/mining/equipment/kinetic_crusher/crusher_variants/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher/crusher_variants/kinetic_crusher.dm
@@ -225,7 +225,7 @@
var/obj/projectile/destabilizer/destabilizer = new crusher_destabilizer(proj_turf)
for(var/obj/item/crusher_trophy/attached_trophy as anything in trophies)
attached_trophy.on_projectile_fire(destabilizer, user)
- destabilizer.preparePixelProjectile(target, user, modifiers)
+ destabilizer.aim_projectile(target, user, modifiers)
destabilizer.firer = user
destabilizer.fired_from = src
playsound(user, 'sound/weapons/plasma_cutter.ogg', 100, TRUE)
diff --git a/code/modules/mining/equipment/kinetic_crusher/crusher_variants/knives.dm b/code/modules/mining/equipment/kinetic_crusher/crusher_variants/knives.dm
index da8f15ee28284..5f581e5a0e7d9 100644
--- a/code/modules/mining/equipment/kinetic_crusher/crusher_variants/knives.dm
+++ b/code/modules/mining/equipment/kinetic_crusher/crusher_variants/knives.dm
@@ -84,7 +84,7 @@
if(!isturf(proj_turf))
return
var/obj/projectile/knives/knife = new(proj_turf)
- knife.preparePixelProjectile(target, user, modifiers)
+ knife.aim_projectile(target, user, modifiers)
knife.firer = user
knife.hammer_synced = src
playsound(user, 'sound/weapons/fwoosh.ogg', 100, TRUE)
diff --git a/code/modules/mining/equipment/wormhole_jaunter.dm b/code/modules/mining/equipment/wormhole_jaunter.dm
index 384502db18772..035f4221c3407 100644
--- a/code/modules/mining/equipment/wormhole_jaunter.dm
+++ b/code/modules/mining/equipment/wormhole_jaunter.dm
@@ -115,7 +115,7 @@
light_on = FALSE
wibbles = FALSE
-/obj/effect/portal/jaunt_tunnel/teleport(atom/movable/M)
+/obj/effect/portal/jaunt_tunnel/teleport(atom/movable/M, force = FALSE)
. = ..()
if(.)
// KERPLUNK
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 6e75b9624c7e6..03c0fbc9badc7 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -108,8 +108,8 @@
/obj/structure/closet/crate/necropolis/colossus
name = "colossus chest"
-/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/P)
- if(istype(P, /obj/projectile/colossus))
+/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/proj)
+ if(istype(proj, /obj/projectile/colossus))
return BULLET_ACT_FORCE_PIERCE
return ..()
diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm
index c473f504654fa..4c45e338eaa51 100644
--- a/code/modules/mob/living/basic/basic_defense.dm
+++ b/code/modules/mob/living/basic/basic_defense.dm
@@ -122,9 +122,6 @@
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
return ..()
-/mob/living/basic/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent)
- return impacting_projectile.grazing ? 50 : 0
-
/mob/living/basic/ex_act(severity, target, origin)
. = ..()
if(!. || QDELETED(src))
diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm
index 049c180d0b698..266dc6a18dd6d 100644
--- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm
+++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm
@@ -4,8 +4,7 @@
damage = 5
damage_type = BURN
armor_flag = ENERGY
- speed = 1
- pixel_speed_multiplier = 0.25
+ speed = 0.25
temperature = -75
/datum/action/cooldown/mob_cooldown/ice_demon_teleport
diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm
index e502f3ec09fb2..c86805ee41436 100644
--- a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm
+++ b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm
@@ -6,7 +6,7 @@
light_outer_range = 2
armor_flag = ENERGY
light_color = LIGHT_COLOR_DIM_YELLOW
- speed = 1.6
+ speed = 0.66
hitsound = 'sound/weapons/sear.ogg'
hitsound_wall = 'sound/weapons/effects/searwall.ogg'
nondirectional_sprite = TRUE
diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm
index 0892f77aa5056..f039d23243c1b 100644
--- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm
+++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm
@@ -72,7 +72,7 @@
icon_state = "neurotoxin"
hitsound = 'sound/weapons/sear.ogg'
damage = 20
- speed = 2
+ speed = 0.5
range = 20
jitter = 3 SECONDS
stutter = 3 SECONDS
diff --git a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
index b46497f21fdc5..7fccb41c113df 100644
--- a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
+++ b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
@@ -291,7 +291,7 @@ Doesn't work on other aliens/AI.*/
span_alertalien("You spit neurotoxin."),
)
var/obj/projectile/neurotoxin/neurotoxin = new /obj/projectile/neurotoxin(user.loc)
- neurotoxin.preparePixelProjectile(target, user, modifiers)
+ neurotoxin.aim_projectile(target, user, modifiers)
neurotoxin.firer = user
neurotoxin.fire()
user.newtonian_move(get_dir(target, user))
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index d56fb884d14a8..22d849244e2d8 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -114,13 +114,13 @@
/mob/living/carbon/Topic(href, href_list)
..()
if(href_list["embedded_object"] && usr.can_perform_action(src, NEED_DEXTERITY))
- var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts
- if(!L)
+ var/obj/item/bodypart/limb = locate(href_list["embedded_limb"]) in bodyparts
+ if(!limb)
return
- var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects
- if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore
+ var/obj/item/weapon = locate(href_list["embedded_object"]) in limb.embedded_objects
+ if(!weapon || weapon.loc != src) //no item, no limb, or item is not in limb or in the person anymore
return
- SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L)
+ weapon.get_embed().rip_out(usr)
return
if(href_list["gauze_limb"])
@@ -1374,6 +1374,12 @@
head.adjustBleedStacks(5)
visible_message(span_notice("[src] gets a nosebleed."), span_warning("You get a nosebleed."))
+/mob/living/carbon/check_hit_limb_zone_name(hit_zone)
+ if(get_bodypart(hit_zone))
+ return hit_zone
+ // When a limb is missing the damage is actually passed to the chest
+ return BODY_ZONE_CHEST
+
/mob/living/carbon/death(gibbed)
if (stat == DEAD)
return ..()
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 88c5670d05a9c..793aa4a29cd43 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -59,12 +59,12 @@
return eyes
return check_equipment_cover_flags(PEPPERPROOF)
-/mob/living/carbon/check_projectile_dismemberment(obj/projectile/P, def_zone)
+/mob/living/carbon/check_projectile_dismemberment(obj/projectile/proj, def_zone)
var/obj/item/bodypart/affecting = get_bodypart(def_zone)
- if(affecting && !(affecting.bodypart_flags & BODYPART_UNREMOVABLE) && affecting.get_damage() >= (affecting.max_damage - P.dismemberment))
- affecting.dismember(P.damtype)
- if(P.catastropic_dismemberment)
- apply_damage(P.damage, P.damtype, BODY_ZONE_CHEST, wound_bonus = P.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles.
+ if(affecting && affecting.can_dismember() && !(affecting.bodypart_flags & BODYPART_UNREMOVABLE) && affecting.get_damage() >= (affecting.max_damage - proj.dismemberment))
+ affecting.dismember(proj.damtype)
+ if(proj.catastropic_dismemberment)
+ apply_damage(proj.damage, proj.damtype, BODY_ZONE_CHEST, wound_bonus = proj.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles.
/mob/living/carbon/proc/can_catch_item(skip_throw_mode_check)
. = FALSE
@@ -595,22 +595,22 @@
return
var/embeds = FALSE
- for(var/X in bodyparts)
- var/obj/item/bodypart/LB = X
- for(var/obj/item/I in LB.embedded_objects)
+ for(var/obj/item/bodypart/limb as anything in bodyparts)
+ for(var/obj/item/weapon as anything in limb.embedded_objects)
if(!embeds)
embeds = TRUE
// this way, we only visibly try to examine ourselves if we have something embedded, otherwise we'll still hug ourselves :)
visible_message(span_notice("[src] examines [p_them()]self."), \
span_notice("You check yourself for shrapnel."))
- if(I.isEmbedHarmless())
- to_chat(src, "\t There is \a [I] stuck to your [LB.name]!")
+ var/harmless = weapon.get_embed().is_harmless()
+ var/stuck_wordage = harmless ? "stuck to" : "embedded in"
+ var/embed_text = "\t There is [icon2html(weapon, src)] \a [weapon] [stuck_wordage] your [limb.plaintext_zone]!"
+ if (harmless)
+ to_chat(src, span_italics(span_notice(embed_text)))
else
- to_chat(src, "\t There is \a [I] embedded in your [LB.name]!")
-
+ to_chat(src, span_boldwarning(embed_text))
return embeds
-
/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25)
var/obj/item/organ/internal/eyes/eyes = get_organ_slot(ORGAN_SLOT_EYES)
if(!eyes) //can't flash what can't see!
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 09257d60d01ed..7f07704c6be9a 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -1,3 +1,5 @@
+#define CARBON_EXAMINE_EMBEDDING_MAX_DIST 4
+
/mob/living/carbon/examine(mob/user)
var/t_He = p_they(TRUE)
var/t_His = p_their(TRUE)
@@ -41,11 +43,17 @@
if(body_part.bodypart_disabled)
disabled += body_part
missing -= body_part.body_zone
- for(var/obj/item/leftover in body_part.embedded_objects)
- var/stuck_or_embedded = "embedded in"
- if(leftover.isEmbedHarmless())
- stuck_or_embedded = "stuck to"
- msg += "[t_He] [t_has] [ma2html(leftover, user)] \a [leftover] [stuck_or_embedded] [t_his] [body_part.plaintext_zone]!\n"
+ for(var/obj/item/embedded as anything in body_part.embedded_objects)
+ var/harmless = embedded.get_embed().is_harmless()
+ var/stuck_wordage = harmless ? "stuck to" : "embedded in"
+ var/embed_line = "\a [embedded]"
+ if (get_dist(src, user) <= CARBON_EXAMINE_EMBEDDING_MAX_DIST)
+ embed_line = "\a [embedded]"
+ var/embed_text = "[t_He] [t_has] [icon2html(embedded, user)] [embed_line] [stuck_wordage] [t_his] [body_part.plaintext_zone]!"
+ if (harmless)
+ msg += span_italics(span_notice(embed_text))
+ else
+ msg += span_boldwarning(embed_text)
if(body_part.current_gauze)
var/gauze_href = body_part.current_gauze.name
@@ -187,3 +195,5 @@
. += "[scar_text]"
return .
+
+#undef CARBON_EXAMINE_EMBEDDING_MAX_DIST
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index c69b03542eadb..4ea6b8f9fe912 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -153,7 +153,8 @@
disabled += body_part
missing -= body_part.body_zone
for(var/obj/item/I in body_part.embedded_objects)
- if(I.isEmbedHarmless())
+ var/harmless = I.get_embed().is_harmless()
+ if(harmless)
msg += "[t_He] [t_has] [icon2html(I, user)] \a [I] stuck to [t_his] [body_part.name]!\n"
else
msg += "[t_He] [t_has] [icon2html(I, user)] \a [I] embedded in [t_his] [body_part.name]!\n"
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 61add27331593..11a0cdf345f0b 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -50,42 +50,24 @@
covering_part += worn
return covering_part
-/mob/living/carbon/human/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
-
- if(P.firer == src && P.original == src) //can't block or reflect when shooting yourself
+/mob/living/carbon/human/bullet_act(obj/projectile/bullet, def_zone, piercing_hit = FALSE)
+ if(bullet.firer == src && bullet.original == src) //can't block or reflect when shooting yourself
return ..()
- if(P.reflectable & REFLECT_NORMAL)
+ if(bullet.reflectable)
if(check_reflect(def_zone)) // Checks if you've passed a reflection% check
visible_message(
- span_danger("The [P.name] gets reflected by [src]!"),
- span_userdanger("The [P.name] gets reflected by [src]!"),
+ span_danger("The [bullet.name] gets reflected by [src]!"),
+ span_userdanger("The [bullet.name] gets reflected by [src]!"),
)
// Find a turf near or on the original location to bounce to
if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit
- P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage.
- loc.bullet_act(P, def_zone, piercing_hit)
- return BULLET_ACT_HIT
- if(P.starting)
- 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(src)
-
- // redirect the projectile
- P.original = locate(new_x, new_y, P.z)
- P.starting = curloc
- P.firer = src
- P.yo = new_y - curloc.y
- P.xo = new_x - curloc.x
- var/new_angle_s = P.Angle + rand(120,240)
- while(new_angle_s > 180) // Translate to regular projectile degrees
- new_angle_s -= 360
- P.set_angle(new_angle_s)
-
+ return loc.projectile_hit(bullet, def_zone, piercing_hit)
+ bullet.reflect(src)
return BULLET_ACT_FORCE_PIERCE // complete projectile permutation
- if(check_block(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration, P.damage_type))
- P.on_hit(src, 100, def_zone, piercing_hit)
+ if(check_block(bullet, bullet.damage, "the [bullet.name]", PROJECTILE_ATTACK, bullet.armour_penetration, bullet.damage_type))
+ bullet.on_hit(src, 100, def_zone, piercing_hit)
return BULLET_ACT_HIT
return ..()
@@ -115,9 +97,8 @@
if(worn_thing in held_items)
continue
// Things that are supposed to be held, being worn = cannot block
- else
- if(!(worn_thing in held_items))
- continue
+ else if(!(worn_thing in held_items))
+ continue
var/final_block_chance = worn_thing.block_chance - (clamp((armour_penetration - worn_thing.armour_penetration) / 2, 0, 100)) + block_chance_modifier
if(worn_thing.hit_reaction(src, hit_by, attack_text, final_block_chance, damage, attack_type, damage_type))
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index f1bfbb46d77bd..40711eea2a0f0 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -150,8 +150,7 @@
for(var/i in missing_bodyparts)
var/datum/scar/scaries = new
scars += "[scaries.format_amputated(i)]"
- for(var/i in all_scars)
- var/datum/scar/iter_scar = i
+ for(var/datum/scar/iter_scar as anything in all_scars)
if(!iter_scar.fake)
scars += "[iter_scar.format()];"
return scars
diff --git a/code/modules/mob/living/carbon/human/species_types/golem/glass_golem.dm b/code/modules/mob/living/carbon/human/species_types/golem/glass_golem.dm
index f46747a9b7c97..565861a2f8bef 100644
--- a/code/modules/mob/living/carbon/human/species_types/golem/glass_golem.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golem/glass_golem.dm
@@ -32,7 +32,7 @@
var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
// redirect the projectile
P.firer = H
- P.preparePixelProjectile(locate(clamp(new_x, 1, world.maxx), clamp(new_y, 1, world.maxy), H.z), H)
+ P.aim_projectile(locate(clamp(new_x, 1, world.maxx), clamp(new_y, 1, world.maxy), H.z), H)
return BULLET_ACT_FORCE_PIERCE
return ..()
*/
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index e22734256cc0c..6a93221e75c7e 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -230,6 +230,7 @@
eyeblur = 0 SECONDS,
drowsy = 0 SECONDS,
blocked = 0, // This one's not an effect, don't be confused - it's the % of the other effects to be blocked by armor
+ stamina = 0, // This one's a damage type, and not an effect
jitter = 0 SECONDS,
paralyze = 0,
immobilize = 0,
@@ -248,6 +249,10 @@
apply_effect(paralyze, EFFECT_PARALYZE, blocked)
if(immobilize)
apply_effect(immobilize, EFFECT_IMMOBILIZE, blocked)
+
+ if(stamina)
+ apply_damage(stamina, STAMINA, null, blocked)
+
if(drowsy)
adjust_drowsiness(drowsy)
if(eyeblur)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 2987a249c3ffd..a9e28ad03297a 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2666,3 +2666,8 @@ GLOBAL_LIST_EMPTY(fire_appearances)
return
STOP_PROCESSING(SSclient_mobs, src)
START_PROCESSING(clientless_subsystem, src)
+
+/// Returns the string form of the def_zone we have hit.
+/mob/living/proc/check_hit_limb_zone_name(hit_zone)
+ if(has_limbs)
+ return hit_zone
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index ac7e3ce135fb1..467f8e59aaf24 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -91,58 +91,84 @@
/mob/living/proc/is_ears_covered()
return null
-/mob/living/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
+/mob/living/bullet_act(obj/projectile/proj, def_zone, piercing_hit = FALSE, blocked = 0)
. = ..()
- if(. != BULLET_ACT_HIT)
+ if (. != BULLET_ACT_HIT)
return .
- if(!hitting_projectile.is_hostile_projectile())
- return BULLET_ACT_HIT
- // we need a second, silent armor check to actually know how much to reduce damage taken, as opposed to
- // on [/atom/proc/bullet_act] where it's just to pass it to the projectile's on_hit().
- var/armor_check = min(ARMOR_MAX_BLOCK, check_projectile_armor(def_zone, hitting_projectile, is_silent = TRUE))
+ if(blocked >= 100)
+ if(proj.is_hostile_projectile())
+ apply_projectile_effects(proj, def_zone, blocked)
+ return .
+
+ var/hit_limb_zone = check_hit_limb_zone_name(def_zone)
+ var/organ_hit_text = ""
+ if (hit_limb_zone)
+ organ_hit_text = "in \the [parse_zone_with_bodypart(hit_limb_zone)]"
- var/stamina_armor_check = armor_check
+ switch (proj.suppressed)
+ if (SUPPRESSED_QUIET)
+ to_chat(src, span_userdanger("You're shot by \a [proj] [organ_hit_text]!"))
+ if (SUPPRESSED_NONE)
+ visible_message(span_danger("[src] is hit by \a [proj] [organ_hit_text]!"), \
+ span_userdanger("You're hit by \a [proj] [organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE)
+ if(is_blind())
+ to_chat(src, span_userdanger("You feel something hit you [organ_hit_text]!"))
- //If we hit a limb with stamina damage, we check the armor on the chest instead, to prevent cheesing armor by targeting limbs to stamcrit.
- if (def_zone != BODY_ZONE_CHEST && def_zone != BODY_ZONE_HEAD)
- stamina_armor_check = min(ARMOR_MAX_BLOCK, check_projectile_armor(BODY_ZONE_CHEST, hitting_projectile, is_silent = TRUE))
+ if(proj.is_hostile_projectile())
+ apply_projectile_effects(proj, def_zone, blocked)
+/mob/living/proc/apply_projectile_effects(obj/projectile/proj, def_zone, armor_check)
apply_damage(
- damage = hitting_projectile.damage,
- damagetype = hitting_projectile.damage_type,
+ damage = proj.damage,
+ damagetype = proj.damage_type,
def_zone = def_zone,
- blocked = armor_check,
- wound_bonus = hitting_projectile.wound_bonus,
- bare_wound_bonus = hitting_projectile.bare_wound_bonus,
- sharpness = hitting_projectile.sharpness,
- attack_direction = hitting_projectile.dir,
+ blocked = min(ARMOR_MAX_BLOCK, armor_check), //cap damage reduction at 90%
+ wound_bonus = proj.wound_bonus,
+ bare_wound_bonus = proj.bare_wound_bonus,
+ sharpness = proj.sharpness,
+ attack_direction = get_dir(proj.starting, src),
)
- if(hitting_projectile.stamina)
- apply_damage(
- damage = hitting_projectile.stamina,
- damagetype = STAMINA,
- def_zone = def_zone,
- blocked = stamina_armor_check,
- attack_direction = hitting_projectile.dir,
- )
apply_effects(
- stun = hitting_projectile.stun,
- knockdown = hitting_projectile.knockdown,
- unconscious = hitting_projectile.unconscious,
- slur = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : hitting_projectile.slur, // Don't want your cyborgs to slur from being ebow'd
- stutter = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : hitting_projectile.stutter, // Don't want your cyborgs to stutter from being tazed
- eyeblur = hitting_projectile.eyeblur,
- drowsy = hitting_projectile.drowsy,
+ stun = proj.stun,
+ knockdown = proj.knockdown,
+ unconscious = proj.unconscious,
+ slur = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : proj.slur, // Don't want your cyborgs to slur from being ebow'd
+ stutter = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : proj.stutter, // Don't want your cyborgs to stutter from being tazed
+ eyeblur = proj.eyeblur,
+ drowsy = proj.drowsy,
blocked = armor_check,
- jitter = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : hitting_projectile.jitter, // Cyborgs can jitter but not from being shot
- paralyze = hitting_projectile.paralyze,
- immobilize = hitting_projectile.immobilize,
+ stamina = proj.stamina,
+ jitter = (mob_biotypes & MOB_ROBOTIC) ? 0 SECONDS : proj.jitter, // Cyborgs can jitter but not from being shot
+ paralyze = proj.paralyze,
+ immobilize = proj.immobilize,
)
- if(hitting_projectile.dismemberment)
- check_projectile_dismemberment(hitting_projectile, def_zone)
- return BULLET_ACT_HIT
+
+ if(proj.dismemberment)
+ check_projectile_dismemberment(proj, def_zone)
+
+ if (proj.damage && armor_check < 100)
+ create_projectile_hit_effects(proj, def_zone, armor_check)
+
+/mob/living/proc/create_projectile_hit_effects(obj/projectile/proj, def_zone, blocked)
+ if (proj.damage_type != BRUTE)
+ return
+
+ var/obj/item/bodypart/hit_bodypart = get_bodypart(check_hit_limb_zone_name(def_zone))
+ if (blood_volume && (isnull(hit_bodypart) || hit_bodypart.can_bleed()))
+ do_splatter_effect(angle2dir(proj.angle))
+ if(prob(33))
+ add_splatter_floor(get_turf(src))
+ return
+
+ if (hit_bodypart?.biological_state & (BIO_METAL|BIO_WIRED))
+ var/random_damage_mult = RANDOM_DECIMAL(0.85, 1.15) // SOMETIMES you can get more or less sparks
+ var/damage_dealt = ((proj.damage / (1 - (blocked / 100))) * random_damage_mult)
+
+ var/spark_amount = round((damage_dealt / PROJECTILE_DAMAGE_PER_ROBOTIC_SPARK))
+ if (spark_amount > 0)
+ do_sparks(spark_amount, FALSE, src)
/mob/living/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent)
. = run_armor_check(
@@ -152,20 +178,17 @@
silent = is_silent,
weak_against_armour = impacting_projectile.weak_against_armour,
)
- if(impacting_projectile.grazing)
- . += 50
return .
-/mob/living/proc/check_projectile_dismemberment(obj/projectile/P, def_zone)
- return 0
+/mob/living/proc/check_projectile_dismemberment(obj/projectile/proj, def_zone)
+ return
/obj/item/proc/get_volume_by_throwforce_and_or_w_class()
- if(throwforce && w_class)
- return clamp((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100
- else if(w_class)
- return clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100
- else
- return 0
+ if(throwforce && w_class)
+ return clamp((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100
+ if(w_class)
+ return clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100
+ return 0
///LEGACY HELPER
/mob/living/proc/set_combat_mode(new_mode, silent = TRUE)
@@ -194,7 +217,7 @@
blocked = TRUE
var/zone = get_random_valid_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
- var/nosell_hit = SEND_SIGNAL(thrown_item, COMSIG_MOVABLE_IMPACT_ZONE, src, zone, blocked, throwingdatum) // TODO: find a better way to handle hitpush and skipcatch for humans
+ var/nosell_hit = (SEND_SIGNAL(thrown_item, COMSIG_MOVABLE_IMPACT_ZONE, src, zone, blocked, throwingdatum) & MOVABLE_IMPACT_ZONE_OVERRIDE) // TODO: find a better way to handle hitpush and skipcatch for humans
if(nosell_hit)
skipcatch = TRUE
hitpush = FALSE
@@ -231,6 +254,9 @@
emote("scream", intentional=FALSE)
return ..()
+/mob/living/proc/create_splatter(splatter_dir)
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(get_turf(src), splatter_dir)
+
/mob/living/fire_act()
. = ..()
adjust_fire_stacks(3)
@@ -656,7 +682,7 @@
/// Simplified ricochet angle calculation for mobs (also the base version doesn't work on mobs)
/mob/living/handle_ricochet(obj/projectile/ricocheting_projectile)
var/face_angle = get_angle_raw(ricocheting_projectile.x, ricocheting_projectile.pixel_x, ricocheting_projectile.pixel_y, ricocheting_projectile.p_y, x, y, pixel_x, pixel_y)
- var/new_angle_s = SIMPLIFY_DEGREES(face_angle + GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.Angle + 180)))
+ var/new_angle_s = SIMPLIFY_DEGREES(face_angle + GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.angle + 180)))
ricocheting_projectile.set_angle(new_angle_s)
return TRUE
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index e1c586371ee04..d5668153297ae 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -76,7 +76,7 @@
var/obj/projectile/fired_bullet = new projectile(loc)
playsound(src, shoot_sound, 50, TRUE)
- fired_bullet.preparePixelProjectile(target, src)
+ fired_bullet.aim_projectile(target, src)
fired_bullet.fire()
/mob/living/simple_animal/bot/secbot/ed209/emp_act(severity)
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index ba7250b19a2fd..ee6fcc267b1c8 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -228,14 +228,14 @@
wires.cut_random()
-/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/Proj)
+/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/proj)
. = ..()
if(. && !QDELETED(src)) //Got hit and not blown up yet.
if(prob(50) && !isnull(load))
unload(0)
if(prob(25))
visible_message(span_danger("Something shorts out inside [src]!"))
- wires.cut_random(source = Proj.firer)
+ wires.cut_random(source = proj.firer)
/mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index f6af3c0d9750c..6dde08e91fd8a 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -269,15 +269,15 @@
update_appearance()
return TRUE
-/mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/Proj)
+/mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/proj)
. = ..()
if(. != BULLET_ACT_HIT)
return
- if(istype(Proj, /obj/projectile/beam) || istype(Proj, /obj/projectile/bullet))
- if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))
- if(Proj.is_hostile_projectile() && Proj.damage < src.health && ishuman(Proj.firer))
- retaliate(Proj.firer)
+ if(istype(proj, /obj/projectile/beam) || istype(proj, /obj/projectile/bullet))
+ if((proj.damage_type == BURN) || (proj.damage_type == BRUTE))
+ if(proj.is_hostile_projectile() && proj.damage < src.health && ishuman(proj.firer))
+ retaliate(proj.firer)
/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/attack_target, proximity_flag)
if(!(bot_mode_flags & BOT_MODE_ON))
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index d61e764b5c615..4fe3b505ddc6c 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -165,11 +165,11 @@
FindTarget(list(source))
return ..()
-/mob/living/simple_animal/hostile/bullet_act(obj/projectile/P)
+/mob/living/simple_animal/hostile/bullet_act(obj/projectile/proj)
if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client)
- if(P.firer && get_dist(src, P.firer) <= aggro_vision_range)
- FindTarget(list(P.firer))
- Goto(P.starting, move_to_delay, 3)
+ if(proj.firer && get_dist(src, proj.firer) <= aggro_vision_range)
+ FindTarget(list(proj.firer))
+ Goto(proj.starting, move_to_delay, 3)
return ..()
//////////////HOSTILE MOB TARGETING AND AGGRESSION////////////
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
index 48c06b7a8b9ff..1f25804f52bbc 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
@@ -20,6 +20,10 @@
bodytemp_heat_damage_limit = INFINITY
vision_range = 5
aggro_vision_range = 18
+ // Pale purple, should be red enough to see stuff on lavaland
+ lighting_cutoff_red = 25
+ lighting_cutoff_green = 15
+ lighting_cutoff_blue = 35
move_force = MOVE_FORCE_OVERPOWERING
move_resist = MOVE_FORCE_OVERPOWERING
pull_force = MOVE_FORCE_OVERPOWERING
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 1fdd30fc32d40..288e2b0f99fbb 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -104,7 +104,7 @@ Difficulty: Medium
/obj/projectile/kinetic/miner
damage = 20
- speed = 0.9
+ speed = 1.1
icon_state = "ka_tracer"
range = 4
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index f9057eb70e3c7..06de0288ac958 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -296,10 +296,10 @@ Difficulty: Hard
if(.)
recovery_time = world.time + 20 // can only attack melee once every 2 seconds but rapid_melee gives higher priority
-/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/projectile/P)
+/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/projectile/proj)
if(BUBBLEGUM_IS_ENRAGED)
- visible_message(span_danger("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons while enraged!"), span_userdanger("You deflect the projectile!"))
- playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 300, TRUE)
+ visible_message(span_danger("[src] deflects the [proj]! [p_They()] can't be hit with ranged weapons while enraged!"), span_userdanger("You deflect the projectile!"))
+ playsound(src, SFX_BULLET_MISS, 300, TRUE)
return BULLET_ACT_BLOCK
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 1954b79382e8f..319124566bce4 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -179,8 +179,8 @@
name = "death bolt"
icon_state = "chronobolt"
damage = 25
- armour_penetration = 85
- speed = 2
+ armour_penetration = 100
+ speed = 0.5
damage_type = BRUTE
pass_flags = PASSTABLE
plane = GAME_PLANE
@@ -191,7 +191,7 @@
AddComponent(/datum/component/parriable_projectile)
/obj/projectile/colossus/can_hit_target(atom/target, direct_target = FALSE, ignore_loc = FALSE, cross_failed = FALSE)
- if(isliving(target))
+ if(isliving(target) && target != firer)
direct_target = TRUE
return ..(target, direct_target, ignore_loc, cross_failed)
@@ -277,12 +277,12 @@
ActivationReaction(user, ACTIVATE_WEAPON)
..()
-/obj/machinery/anomalous_crystal/bullet_act(obj/projectile/P, def_zone)
+/obj/machinery/anomalous_crystal/bullet_act(obj/projectile/proj, def_zone)
. = ..()
- if(istype(P, /obj/projectile/magic))
- ActivationReaction(P.firer, ACTIVATE_MAGIC, P.damage_type)
+ if(istype(proj, /obj/projectile/magic))
+ ActivationReaction(proj.firer, ACTIVATE_MAGIC, proj.damage_type)
return
- ActivationReaction(P.firer, P.armor_flag, P.damage_type)
+ ActivationReaction(proj.firer, proj.armor_flag, proj.damage_type)
/obj/machinery/anomalous_crystal/proc/ActivationReaction(mob/user, method, damtype)
if(!COOLDOWN_FINISHED(src, cooldown_timer))
@@ -473,9 +473,9 @@
/obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method)
if(..())
- var/obj/projectile/P = new generated_projectile(get_turf(src))
- P.firer = src
- P.fire(dir2angle(dir))
+ var/obj/projectile/proj = new generated_projectile(get_turf(src))
+ proj.firer = src
+ proj.fire(dir2angle(dir))
/obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down.
observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again."
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
index 57ec690597598..bb3934c020772 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
@@ -124,7 +124,7 @@ Difficulty: Extremely Hard
if(FROST_MINER_SHOULD_ENRAGE)
INVOKE_ASYNC(src, PROC_REF(check_enraged))
return COMPONENT_BLOCK_ABILITY_START
- var/projectile_speed_multiplier = 1 - enraged * 0.5
+ var/projectile_speed_multiplier = 1 + enraged
frost_orbs.projectile_speed_multiplier = projectile_speed_multiplier
snowball_machine_gun.projectile_speed_multiplier = projectile_speed_multiplier
ice_shotgun.projectile_speed_multiplier = projectile_speed_multiplier
@@ -194,9 +194,8 @@ Difficulty: Extremely Hard
name = "frost orb"
icon_state = "ice_1"
damage = 20
- armour_penetration = 50
- speed = 1
- pixel_speed_multiplier = 0.1
+ armour_penetration = 100
+ speed = 0.1
range = 500
homing_turn_speed = 3
damage_type = BURN
@@ -210,9 +209,8 @@ Difficulty: Extremely Hard
name = "machine-gun snowball"
icon_state = "nuclear_particle"
damage = 5
- armour_penetration = 50
- speed = 1
- pixel_speed_multiplier = 0.333
+ armour_penetration = 100
+ speed = 0.33
range = 150
damage_type = BRUTE
explode_hit_objects = FALSE
@@ -221,9 +219,8 @@ Difficulty: Extremely Hard
name = "ice blast"
icon_state = "ice_2"
damage = 15
- armour_penetration = 50
- speed = 1
- pixel_speed_multiplier = 0.333
+ armour_penetration = 100
+ speed = 0.33
range = 150
damage_type = BRUTE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index d795e0511bed0..2e72c93437aaf 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -554,8 +554,8 @@ Difficulty: Hard
if(mover == caster.pulledby)
return
if(isprojectile(mover))
- var/obj/projectile/P = mover
- if(P.firer == caster)
+ var/obj/projectile/proj = mover
+ if(proj.firer == caster)
return
if(mover != caster)
return FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
index dc065e43ab039..9c7ed12996456 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
@@ -1,10 +1,4 @@
#define WENDIGO_ENRAGED (health <= maxHealth*0.5)
-#define WENDIGO_CIRCLE_SHOTCOUNT 24
-#define WENDIGO_CIRCLE_REPEATCOUNT 8
-#define WENDIGO_SPIRAL_SHOTCOUNT 40
-#define WENDIGO_WAVE_SHOTCOUNT 7
-#define WENDIGO_WAVE_REPEATCOUNT 32
-#define WENDIGO_SHOTGUN_SHOTCOUNT 5
/*
@@ -53,48 +47,49 @@ Warning the icebox version is being overridden in monkestation/code/modules/mob/
death_message = "falls to the ground in a bloody heap, shaking the arena"
death_sound = 'sound/effects/gravhit.ogg'
footstep_type = FOOTSTEP_MOB_HEAVY
- attack_action_types = list(/datum/action/innate/megafauna_attack/heavy_stomp,
- /datum/action/innate/megafauna_attack/teleport,
- /datum/action/innate/megafauna_attack/shockwave_scream)
/// Saves the turf the megafauna was created at (spawns exit portal here)
var/turf/starting
/// Range for wendigo stomping when it moves
var/stomp_range = 1
/// Stores directions the mob is moving, then calls that a move has fully ended when these directions are removed in moved
var/stored_move_dirs = 0
- /// If the wendigo is allowed to move
- var/can_move = TRUE
/// Time before the wendigo can scream again
var/scream_cooldown_time = 10 SECONDS
/// Should it create the portal? Special cases use this like ore_vents.
var/make_portal = TRUE //MONKESTATION EDIT
+ /// If true, will remove wave ability from wendigo
+ var/weakened = FALSE
+ /// Teleport Ability
+ var/datum/action/cooldown/mob_cooldown/teleport/teleport
+ /// Shotgun Ability
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/wendigo/shotgun_blast
+ /// Ground Slam Ability
+ var/datum/action/cooldown/mob_cooldown/ground_slam/ground_slam
+ /// Alternating Projectiles Ability
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/alternating_circle/alternating_circle
+ /// Spiral Projectiles Ability
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/wendigo/spiral
+ /// Wave Projectiles Ability
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/wave/wave
/// Stores the last scream time so it doesn't spam it
COOLDOWN_DECLARE(scream_cooldown)
/mob/living/simple_animal/hostile/megafauna/wendigo/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT)
-
-/datum/action/innate/megafauna_attack/heavy_stomp
- name = "Heavy Stomp"
- button_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "sniper_zoom"
- chosen_message = "You are now stomping the ground around you."
- chosen_attack_num = 1
-
-/datum/action/innate/megafauna_attack/teleport
- name = "Teleport"
- button_icon = 'icons/effects/bubblegum.dmi'
- button_icon_state = "smack ya one"
- chosen_message = "You are now teleporting at the target you click on."
- chosen_attack_num = 2
-
-/datum/action/innate/megafauna_attack/shockwave_scream
- name = "Shockwave Scream"
- button_icon = 'icons/turf/walls/wall.dmi'
- button_icon_state = "wall-0"
- chosen_message = "You are now screeching, disorienting targets around you."
- chosen_attack_num = 3
+ teleport = new(src)
+ shotgun_blast = new(src)
+ ground_slam = new(src)
+ alternating_circle = new(src)
+ spiral = new(src)
+ teleport.Grant(src)
+ shotgun_blast.Grant(src)
+ ground_slam.Grant(src)
+ alternating_circle.Grant(src)
+ spiral.Grant(src)
+ if(!weakened)
+ wave = new(src)
+ wave.Grant(src)
/mob/living/simple_animal/hostile/megafauna/wendigo/Initialize(mapload)
. = ..()
@@ -111,13 +106,6 @@ Warning the icebox version is being overridden in monkestation/code/modules/mob/
move_to_delay = initial(move_to_delay)
if(client)
- switch(chosen_attack)
- if(1)
- heavy_stomp()
- if(2)
- try_teleport()
- if(3)
- shockwave_scream()
return
if(COOLDOWN_FINISHED(src, scream_cooldown))
@@ -126,28 +114,54 @@ Warning the icebox version is being overridden in monkestation/code/modules/mob/
chosen_attack = rand(1, 2)
switch(chosen_attack)
if(1)
- heavy_stomp()
+ ground_slam.Activate(target)
if(2)
- try_teleport()
+ teleport.Activate(target)
+ if(WENDIGO_ENRAGED)
+ shotgun_blast.Activate(target)
if(3)
do_teleport(src, starting, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE)
- shockwave_scream()
+ var/shockwave_attack
+ if(WENDIGO_ENRAGED)
+ shockwave_attack = rand(1, 3)
+ else
+ shockwave_attack = rand(1, 2)
+ switch(shockwave_attack)
+ if(1)
+ alternating_circle.enraged = WENDIGO_ENRAGED
+ alternating_circle.Activate(target)
+ if(2)
+ spiral.enraged = WENDIGO_ENRAGED
+ spiral.Activate(target)
+ if(3)
+ wave.Activate(target)
+ update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 3 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 3 SECONDS))
/mob/living/simple_animal/hostile/megafauna/wendigo/Move(atom/newloc, direct)
- if(!can_move)
- return
stored_move_dirs |= direct
- return ..()
+ . = ..()
+ // Remove after anyways in case the movement was prevented
+ stored_move_dirs &= ~direct
/mob/living/simple_animal/hostile/megafauna/wendigo/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
stored_move_dirs &= ~movement_dir
if(!stored_move_dirs)
- INVOKE_ASYNC(src, PROC_REF(wendigo_slam), stomp_range, 1, 8)
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(wendigo_slam), src, stomp_range, 1, 8)
-/// Slams the ground around the source throwing back enemies caught nearby, delay is for the radius increase
-/mob/living/simple_animal/hostile/megafauna/wendigo/proc/wendigo_slam(range, delay, throw_range)
- var/turf/origin = get_turf(src)
+/proc/wendigo_scream(mob/owner)
+ SLEEP_CHECK_DEATH(5, owner)
+ playsound(owner.loc, 'sound/magic/demon_dies.ogg', 600, FALSE, 10)
+ var/pixel_shift = rand(5, 15)
+ animate(owner, pixel_z = pixel_shift, time = 1, loop = 20, flags = ANIMATION_RELATIVE)
+ animate(pixel_z = -pixel_shift, time = 1, flags = ANIMATION_RELATIVE)
+ for(var/mob/living/dizzy_target in get_hearers_in_view(7, owner) - owner)
+ dizzy_target.set_dizzy_if_lower(12 SECONDS)
+ to_chat(dizzy_target, span_danger("[owner] screams loudly!"))
+ SLEEP_CHECK_DEATH(1 SECONDS, owner)
+
+/proc/wendigo_slam(mob/owner, range, delay, throw_range)
+ var/turf/origin = get_turf(owner)
if(!origin)
return
var/list/all_turfs = RANGE_TURFS(range, origin)
@@ -157,117 +171,16 @@ Warning the icebox version is being overridden in monkestation/code/modules/mob/
if(get_dist(origin, stomp_turf) > sound_range)
continue
new /obj/effect/temp_visual/small_smoke/halfsecond(stomp_turf)
- for(var/mob/living/target in stomp_turf)
- if(target == src || target.throwing)
+ for(var/mob/living/hit_mob in stomp_turf)
+ if(hit_mob == owner || hit_mob.throwing)
continue
- to_chat(target, span_userdanger("[src]'s ground slam shockwave sends you flying!"))
- var/turf/thrownat = get_ranged_target_turf_direct(src, target, throw_range, rand(-10, 10))
- target.throw_at(thrownat, 8, 2, null, TRUE, force = MOVE_FORCE_OVERPOWERING, gentle = TRUE)
- target.apply_damage(20, BRUTE, wound_bonus=CANT_WOUND)
- shake_camera(target, 2, 1)
+ to_chat(hit_mob, span_userdanger("[owner]'s ground slam shockwave sends you flying!"))
+ var/turf/thrownat = get_ranged_target_turf_direct(owner, hit_mob, throw_range, rand(-10, 10))
+ hit_mob.throw_at(thrownat, 8, 2, null, TRUE, force = MOVE_FORCE_OVERPOWERING, gentle = TRUE)
+ hit_mob.apply_damage(20, BRUTE, wound_bonus=CANT_WOUND)
+ shake_camera(hit_mob, 2, 1)
all_turfs -= stomp_turf
- sleep(delay)
-
-/// Larger but slower ground stomp
-/mob/living/simple_animal/hostile/megafauna/wendigo/proc/heavy_stomp()
- can_move = FALSE
- wendigo_slam(5, 3 - WENDIGO_ENRAGED, 8)
- update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 0 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 0 SECONDS))
- can_move = TRUE
-
-/// Teleports to a location 4 turfs away from the enemy in view
-/mob/living/simple_animal/hostile/megafauna/wendigo/proc/try_teleport()
- teleport(6)
- if(WENDIGO_ENRAGED)
- playsound(loc, 'sound/magic/clockwork/invoke_general.ogg', 100, TRUE)
- for(var/shots in 1 to WENDIGO_SHOTGUN_SHOTCOUNT)
- var/spread = shots * 10 - 30
- var/turf/startloc = get_step(get_turf(src), get_dir(src, target))
- var/turf/endloc = get_turf(target)
- if(!endloc)
- break
- var/obj/projectile/colossus/wendigo_shockwave/shockwave = new /obj/projectile/colossus/wendigo_shockwave(loc)
- shockwave.speed = 8
- shockwave.preparePixelProjectile(endloc, startloc, null, spread)
- shockwave.firer = src
- if(target)
- shockwave.original = target
- shockwave.fire()
- update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 0 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 0 SECONDS))
-
-/mob/living/simple_animal/hostile/megafauna/wendigo/proc/teleport(range = 6)
- var/list/possible_ends = view(range, target.loc) - view(range - 1, target.loc)
- for(var/turf/closed/cant_teleport_turf in possible_ends)
- possible_ends -= cant_teleport_turf
- if(!possible_ends.len)
- return
- var/turf/end = pick(possible_ends)
- do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE)
-
-/// Applies dizziness to all nearby enemies that can hear the scream and animates the wendigo shaking up and down as shockwave projectiles shoot outward
-/mob/living/simple_animal/hostile/megafauna/wendigo/proc/shockwave_scream()
- can_move = FALSE
- COOLDOWN_START(src, scream_cooldown, scream_cooldown_time)
- SLEEP_CHECK_DEATH(5, src)
- playsound(loc, 'sound/magic/demon_dies.ogg', 600, FALSE, 10)
- var/pixel_shift = rand(5, 15)
- animate(src, pixel_z = pixel_shift, time = 1, loop = 20, flags = ANIMATION_RELATIVE)
- animate(pixel_z = -pixel_shift, time = 1, flags = ANIMATION_RELATIVE)
- for(var/mob/living/dizzy_target in get_hearers_in_view(7, src) - src)
- dizzy_target.set_dizzy_if_lower(12 SECONDS)
- to_chat(dizzy_target, span_danger("The wendigo screams loudly!"))
- SLEEP_CHECK_DEATH(1 SECONDS, src)
- spiral_attack()
- update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 3 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 3 SECONDS))
- SLEEP_CHECK_DEATH(3 SECONDS, src)
- can_move = TRUE
-
-/// Shoots shockwave projectiles in a random preset pattern
-/mob/living/simple_animal/hostile/megafauna/wendigo/proc/spiral_attack()
- var/list/choices = list("Alternating Circle", "Spiral")
- if(WENDIGO_ENRAGED)
- choices += "Wave"
- var/spiral_type = pick(choices)
- switch(spiral_type)
- if("Alternating Circle")
- var/shots_per = WENDIGO_CIRCLE_SHOTCOUNT
- for(var/shoot_times in 1 to WENDIGO_CIRCLE_REPEATCOUNT)
- var/offset = shoot_times % 2
- for(var/shot in 1 to shots_per)
- var/angle = shot * 360 / shots_per + (offset * 360 / shots_per) * 0.5
- var/obj/projectile/colossus/wendigo_shockwave/shockwave = new /obj/projectile/colossus/wendigo_shockwave(loc)
- shockwave.firer = src
- shockwave.speed = 3 - WENDIGO_ENRAGED
- shockwave.fire(angle)
- SLEEP_CHECK_DEATH(6 - WENDIGO_ENRAGED * 2, src)
- if("Spiral")
- var/shots_spiral = WENDIGO_SPIRAL_SHOTCOUNT
- var/angle_to_target = get_angle(src, target)
- var/spiral_direction = pick(-1, 1)
- for(var/shot in 1 to shots_spiral)
- var/shots_per_tick = 5 - WENDIGO_ENRAGED * 3
- var/angle_change = (5 + WENDIGO_ENRAGED * shot / 6) * spiral_direction
- for(var/count in 1 to shots_per_tick)
- var/angle = angle_to_target + shot * angle_change + count * 360 / shots_per_tick
- var/obj/projectile/colossus/wendigo_shockwave/shockwave = new /obj/projectile/colossus/wendigo_shockwave(loc)
- shockwave.firer = src
- shockwave.damage = 15
- shockwave.fire(angle)
- SLEEP_CHECK_DEATH(1, src)
- if("Wave")
- var/shots_per = WENDIGO_WAVE_SHOTCOUNT
- var/difference = 360 / shots_per
- var/wave_direction = pick(-1, 1)
- for(var/shoot_times in 1 to WENDIGO_WAVE_REPEATCOUNT)
- for(var/shot in 1 to shots_per)
- var/angle = shot * difference + shoot_times * 5 * wave_direction * -1
- var/obj/projectile/colossus/wendigo_shockwave/shockwave = new /obj/projectile/colossus/wendigo_shockwave(loc)
- shockwave.firer = src
- shockwave.wave_movement = TRUE
- shockwave.speed = 8
- shockwave.wave_speed = 10 * wave_direction
- shockwave.fire(angle)
- SLEEP_CHECK_DEATH(2, src)
+ SLEEP_CHECK_DEATH(delay, owner)
/mob/living/simple_animal/hostile/megafauna/wendigo/death(gibbed, list/force_grant)
if(health > 0)
@@ -287,18 +200,28 @@ Warning the icebox version is being overridden in monkestation/code/modules/mob/
/obj/projectile/colossus/wendigo_shockwave
name = "wendigo shockwave"
- /// If wave movement is enabled
- var/wave_movement = FALSE
+ speed = 0.5
/// Amount the angle changes every pixel move
- var/wave_speed = 15
+ var/wave_speed = 0.5
/// Amount of movements this projectile has made
var/pixel_moves = 0
-/obj/projectile/colossus/wendigo_shockwave/pixel_move(trajectory_multiplier, hitscanning = FALSE)
+/obj/projectile/colossus/wendigo_shockwave/spiral
+ damage = 15
+
+/obj/projectile/colossus/wendigo_shockwave/wave
+ speed = 0.125
+ wave_speed = 0.3
+
+/obj/projectile/colossus/wendigo_shockwave/wave/alternate
+ wave_speed = -0.3
+
+/obj/projectile/colossus/wendigo_shockwave/process_movement(pixels_to_move, hitscan, tile_limit)
. = ..()
- if(wave_movement)
- pixel_moves++
- set_angle(original_angle + pixel_moves * wave_speed)
+ if (QDELETED(src))
+ return
+ pixel_moves += .
+ set_angle(original_angle + pixel_moves * wave_speed)
/obj/item/wendigo_blood
name = "bottle of wendigo blood"
@@ -330,9 +253,3 @@ Warning the icebox version is being overridden in monkestation/code/modules/mob/
return
#undef WENDIGO_ENRAGED
-#undef WENDIGO_CIRCLE_SHOTCOUNT
-#undef WENDIGO_CIRCLE_REPEATCOUNT
-#undef WENDIGO_SPIRAL_SHOTCOUNT
-#undef WENDIGO_WAVE_SHOTCOUNT
-#undef WENDIGO_WAVE_REPEATCOUNT
-#undef WENDIGO_SHOTGUN_SHOTCOUNT
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
index de95e59dca903..5268c543ee602 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
@@ -102,8 +102,8 @@
if(mover == set_target)
return FALSE
if(isprojectile(mover))
- var/obj/projectile/P = mover
- if(P.firer == set_target)
+ var/obj/projectile/proj = mover
+ if(proj.firer == set_target)
return FALSE
#define IGNORE_PROC_IF_NOT_TARGET(X) /mob/living/simple_animal/hostile/asteroid/curseblob/##X(AM) { if (AM == set_target) return ..(); }
@@ -120,10 +120,9 @@ IGNORE_PROC_IF_NOT_TARGET(attack_larva)
IGNORE_PROC_IF_NOT_TARGET(attack_animal)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/Proj)
- if(Proj.firer != set_target)
- return
+/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/proj)
+ if(proj.firer != set_target)
+ return BULLET_ACT_BLOCK
return ..()
/mob/living/simple_animal/hostile/asteroid/curseblob/attacked_by(obj/item/I, mob/living/L)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
index c30651ef8eb3c..989d64a458fc4 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -134,7 +134,7 @@
H = new /obj/projectile/herald(startloc)
else
H = new /obj/projectile/herald/teleshot(startloc)
- H.preparePixelProjectile(marker, startloc)
+ H.aim_projectile(marker, startloc)
H.firer = src
if(target)
H.original = target
@@ -224,8 +224,8 @@
name ="death bolt"
icon_state= "chronobolt"
damage = 20
- armour_penetration = 25 //was 50 changed because 50 was waay too much monkestation 20 edit
- speed = 2
+ armour_penetration = 25
+ speed = 0.5
damage_type = BRUTE
pass_flags = PASSTABLE
@@ -273,7 +273,7 @@
var/turf/startloc = get_turf(owner)
var/obj/projectile/herald/H = null
H = new /obj/projectile/herald(startloc)
- H.preparePixelProjectile(marker, startloc)
+ H.aim_projectile(marker, startloc)
H.firer = owner
H.fire(set_angle)
diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm
index c3a536644f694..556c0c339379e 100644
--- a/code/modules/mob/living/simple_animal/hostile/ooze.dm
+++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm
@@ -344,7 +344,7 @@
// Why is this in InterceptClickOn() and not Activate()?
// Well, we need to use the params of the click intercept
- // for passing into preparePixelProjectile, so we'll handle it here instead.
+ // for passing into aim_projectile, so we'll handle it here instead.
// We just need to make sure Pre-activate and Activate return TRUE so we make it this far
user.visible_message(
span_nicegreen("[user] launches a mending globule!"),
@@ -357,7 +357,7 @@
var/modifiers = params2list(params)
var/obj/projectile/globule/globule = new(user.loc)
- globule.preparePixelProjectile(target, user, modifiers)
+ globule.aim_projectile(target, user, modifiers)
globule.def_zone = user.zone_selected
globule.fire()
@@ -374,43 +374,32 @@
name = "mending globule"
icon_state = "glob_projectile"
shrapnel_type = /obj/item/mending_globule
- embedding = list("embed_chance" = 100, ignore_throwspeed_threshold = TRUE, "pain_mult" = 0, "jostle_pain_mult" = 0, "fall_chance" = 0.5)
+ embed_type = /datum/embedding/mending_globule
damage = 0
-///This item is what is embedded into the mob, and actually handles healing of mending globules
+///This item is what is embedded into the mob
/obj/item/mending_globule
name = "mending globule"
desc = "It somehow heals those who touch it."
icon = 'icons/obj/xenobiology/vatgrowing.dmi'
icon_state = "globule"
- embedding = list("embed_chance" = 100, ignore_throwspeed_threshold = TRUE, "pain_mult" = 0, "jostle_pain_mult" = 0, "fall_chance" = 0.5)
- var/obj/item/bodypart/bodypart
var/heals_left = 35
-/obj/item/mending_globule/Destroy()
- . = ..()
- bodypart = null
-
-/obj/item/mending_globule/embedded(mob/living/carbon/human/embedded_mob, obj/item/bodypart/part)
- . = ..()
- if(!istype(part))
- return
- bodypart = part
- START_PROCESSING(SSobj, src)
+/datum/embedding/mending_globule
+ embed_chance = 100
+ ignore_throwspeed_threshold = TRUE
+ pain_mult = 0
+ jostle_pain_mult = 0
+ fall_chance = 0.5
-/obj/item/mending_globule/unembedded()
+// This already processes, zero logic to add additional tracking to the item
+/datum/embedding/mending_globule/process(seconds_per_tick)
. = ..()
- bodypart = null
- STOP_PROCESSING(SSobj, src)
-
-///Handles the healing of the mending globule
-/obj/item/mending_globule/process()
- if(!bodypart) //this is fucked
- return FALSE
- bodypart.heal_damage(1,1)
- heals_left--
- if(heals_left <= 0)
- qdel(src)
+ var/obj/item/mending_globule/globule = parent
+ owner_limb.heal_damage(0.5 * seconds_per_tick, 0.5 * seconds_per_tick)
+ globule.heals_left--
+ if(globule.heals_left <= 0)
+ qdel(globule)
///This action lets you put a mob inside of a cacoon that will inject it with some chemicals.
/datum/action/cooldown/gel_cocoon
diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm
index b74975a7b7f31..fbae15cec18e2 100644
--- a/code/modules/mod/modules/modules_antag.dm
+++ b/code/modules/mod/modules/modules_antag.dm
@@ -290,7 +290,7 @@
if(!.)
return
var/obj/projectile/flame = new /obj/projectile/bullet/incendiary/fire(mod.wearer.loc)
- flame.preparePixelProjectile(target, mod.wearer)
+ flame.aim_projectile(target, mod.wearer)
flame.firer = mod.wearer
playsound(src, 'sound/items/modsuit/flamethrower.ogg', 75, TRUE)
INVOKE_ASYNC(flame, TYPE_PROC_REF(/obj/projectile, fire))
diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm
index cb1bc85cc6ef4..7e6c02f97398d 100644
--- a/code/modules/mod/modules/modules_engineering.dm
+++ b/code/modules/mod/modules/modules_engineering.dm
@@ -91,8 +91,8 @@
cooldown_time = 1.5 SECONDS
/obj/item/mod/module/tether/on_use()
- if(mod.wearer.has_gravity(get_turf(src)))
- balloon_alert(mod.wearer, "too much gravity!")
+ if(HAS_TRAIT_FROM(mod.wearer, TRAIT_TETHER_ATTACHED, REF(src)))
+ balloon_alert(mod.wearer, "already tethered!")
playsound(src, 'sound/weapons/gun/general/dry_fire.ogg', 25, TRUE)
return FALSE
return ..()
@@ -101,8 +101,8 @@
. = ..()
if(!.)
return
- var/obj/projectile/tether = new /obj/projectile/tether(mod.wearer.loc)
- tether.preparePixelProjectile(target, mod.wearer)
+ var/obj/projectile/tether = new /obj/projectile/tether(mod.wearer.loc, src)
+ tether.aim_projectile(target, mod.wearer)
tether.firer = mod.wearer
playsound(src, 'sound/weapons/batonextend.ogg', 25, TRUE)
INVOKE_ASYNC(tether, TYPE_PROC_REF(/obj/projectile, fire))
@@ -117,9 +117,34 @@
hitsound = 'sound/weapons/batonextend.ogg'
hitsound_wall = 'sound/weapons/batonextend.ogg'
suppressed = SUPPRESSED_VERY
- hit_threshhold = LATTICE_LAYER
+ hit_threshhold = ABOVE_NORMAL_TURF_LAYER
+ embed_type = /datum/embedding/tether_projectile
+ shrapnel_type = /obj/item/tether_anchor
/// Reference to the beam following the projectile.
var/line
+ /// Last turf that we passed before impact
+ var/turf/open/last_turf
+ /// MODsuit tether module that fired us
+ var/obj/item/mod/module/tether/parent_module
+
+/obj/projectile/tether/Initialize(mapload, module)
+ . = ..()
+ RegisterSignal(src, COMSIG_PROJECTILE_ON_EMBEDDED, PROC_REF(on_embedded))
+ if (!isnull(module))
+ parent_module = module
+
+/obj/projectile/tether/proc/on_embedded(datum/source, obj/item/payload, atom/hit)
+ SIGNAL_HANDLER
+
+ if (HAS_TRAIT_FROM(hit, TRAIT_TETHER_ATTACHED, REF(parent_module)))
+ return
+
+ firer.AddComponent(/datum/component/tether, hit, 7, "MODtether", payload, parent_module = parent_module, tether_trait_source = REF(parent_module))
+
+/obj/projectile/tether/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
+ . = ..()
+ if (isopenturf(loc))
+ last_turf = loc
/obj/projectile/tether/fire(setAngle)
if(firer)
@@ -128,13 +153,107 @@
/obj/projectile/tether/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
- if(firer)
- firer.throw_at(target, 10, 1, firer, FALSE, FALSE, null, MOVE_FORCE_NORMAL, TRUE)
+ if (!firer)
+ return
+
+ // Funni is handled separately
+ if (ismob(target))
+ return
+
+ if (istype(target, /obj/item/tether_anchor) || isstructure(target) || ismachinery(target))
+ if(HAS_TRAIT_FROM(target, TRAIT_TETHER_ATTACHED, REF(parent_module)))
+ return
+ var/avoid_target_trait = FALSE
+ if (istype(target, /obj/item/tether_anchor))
+ avoid_target_trait = TRUE
+ firer.AddComponent(/datum/component/tether, target, 7, "MODtether", parent_module = parent_module, tether_trait_source = REF(parent_module), no_target_trait = avoid_target_trait)
+ return
+
+ var/hitx = impact_x
+ var/hity = impact_y
+
+ if (!isnull(last_turf) && last_turf != target && last_turf != target.loc)
+ var/turf_dir = get_dir(last_turf, get_turf(target))
+ if (turf_dir & NORTH)
+ hity += 32
+ if (turf_dir & SOUTH)
+ hity -= 32
+ if (turf_dir & EAST)
+ hitx += 32
+ if (turf_dir & WEST)
+ hitx -= 32
+
+ var/obj/item/tether_anchor/anchor = new(last_turf || get_turf(target))
+ anchor.pixel_x = hitx
+ anchor.pixel_y = hity
+ anchor.anchored = TRUE
+ firer.AddComponent(/datum/component/tether, anchor, 7, "MODtether", parent_module = parent_module, tether_trait_source = REF(parent_module))
/obj/projectile/tether/Destroy()
QDEL_NULL(line)
return ..()
+/obj/item/tether_anchor
+ name = "tether anchor"
+ desc = "A reinforced anchor with a tether attachment point. A centuries old EVA tool which saved countless engineers' lives."
+ icon_state = "tether_latched"
+ icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
+ max_integrity = 60
+ interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
+
+/obj/item/tether_anchor/examine(mob/user)
+ . = ..()
+ . += span_info("It can be secured by using a wrench on it. Use right-click to tether yourself to [src].")
+ . += span_info("LMB shortens the tether while RMB lengthens it. Ctrl-click to cut the tether.")
+
+/obj/item/tether_anchor/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ default_unfasten_wrench(user, tool)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/tether_anchor/attack_hand_secondary(mob/user, list/modifiers)
+ if (!can_interact(user) || !user.CanReach(src) || !isturf(loc))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ balloon_alert(user, "attached tether")
+ user.AddComponent(/datum/component/tether, src, 7, "tether")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/item/tether_anchor/mouse_drop_receive(atom/target, mob/user, params)
+ if (!can_interact(user) || !user.CanReach(src) || !isturf(loc))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ if(HAS_TRAIT_FROM(user, TRAIT_TETHER_ATTACHED, REF(src)))
+ balloon_alert(user, "already tethered!")
+ return
+
+ if (target == user)
+ balloon_alert(user, "attached tether")
+ user.AddComponent(/datum/component/tether, src, 7, "tether", tether_trait_source = REF(src))
+ return
+
+ balloon_alert(user, "attaching tether...")
+ to_chat(target, span_userdanger("[user] is trying to attach a tether to you!"))
+ if (!do_after(user, 5 SECONDS, target))
+ return
+
+ if(HAS_TRAIT_FROM(target, TRAIT_TETHER_ATTACHED, REF(src)))
+ balloon_alert(user, "already tethered!")
+ return
+
+ balloon_alert(user, "attached tether")
+ to_chat(target, span_userdanger("[user] attaches a tether to you!"))
+ target.AddComponent(/datum/component/tether, src, 7, "tether", tether_trait_source = REF(src), no_target_trait = TRUE)
+
+/datum/embedding/tether_projectile
+ embed_chance = 65 //spiky
+ fall_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ jostle_pain_mult = 2
+ rip_time = 1 SECONDS
+
///Radiation Protection - Protects the user from radiation, gives them a geiger counter and rad info in the panel.
/obj/item/mod/module/rad_protection
name = "MOD radiation protection module"
diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm
index 5e0240aee160c..c120e01c50324 100644
--- a/code/modules/mod/modules/modules_medical.dm
+++ b/code/modules/mod/modules/modules_medical.dm
@@ -158,7 +158,7 @@
return
var/atom/movable/fired_organ = pop(organ_list)
var/obj/projectile/organ/projectile = new /obj/projectile/organ(mod.wearer.loc, fired_organ)
- projectile.preparePixelProjectile(target, mod.wearer)
+ projectile.aim_projectile(target, mod.wearer)
projectile.firer = mod.wearer
playsound(src, 'sound/mecha/hydraulic.ogg', 25, TRUE)
INVOKE_ASYNC(projectile, TYPE_PROC_REF(/obj/projectile, fire))
diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm
index 446c90cd91de1..e4ccf8994b70d 100644
--- a/code/modules/mod/modules/modules_ninja.dm
+++ b/code/modules/mod/modules/modules_ninja.dm
@@ -322,7 +322,7 @@
if(IS_SPACE_NINJA(mod.wearer) && isliving(target))
mod.wearer.say("Get over here!", forced = type)
var/obj/projectile/net = new /obj/projectile/energy_net(mod.wearer.loc, src)
- net.preparePixelProjectile(target, mod.wearer)
+ net.aim_projectile(target, mod.wearer)
net.firer = mod.wearer
playsound(src, 'sound/weapons/punchmiss.ogg', 25, TRUE)
INVOKE_ASYNC(net, TYPE_PROC_REF(/obj/projectile, fire))
diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm
index e6039f30e0a05..6a92337e9ec94 100644
--- a/code/modules/mod/modules/modules_security.dm
+++ b/code/modules/mod/modules/modules_security.dm
@@ -316,7 +316,7 @@
/// Damage multiplier on projectiles.
var/damage_multiplier = 0.75
/// Speed multiplier on projectiles, higher means slower.
- var/speed_multiplier = 2.5
+ var/speed_multiplier = 0.4
/// List of all tracked projectiles.
var/list/tracked_projectiles = list()
/// Effect image on projectiles.
diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm
index cefa3d158c9c6..45fa6e5b10ff7 100644
--- a/code/modules/mod/modules/modules_supply.dm
+++ b/code/modules/mod/modules/modules_supply.dm
@@ -537,8 +537,8 @@
. = ..()
if(!.)
return
- var/obj/projectile/bomb = new /obj/projectile/bullet/reusable/mining_bomb(mod.wearer.loc)
- bomb.preparePixelProjectile(target, mod.wearer)
+ var/obj/projectile/bomb = new /obj/projectile/bullet/mining_bomb(mod.wearer.loc)
+ bomb.aim_projectile(target, mod.wearer)
bomb.firer = mod.wearer
playsound(src, 'sound/weapons/gun/general/grenade_launch.ogg', 75, TRUE)
INVOKE_ASYNC(bomb, TYPE_PROC_REF(/obj/projectile, fire))
@@ -557,7 +557,7 @@
return
on_deactivation()
-/obj/projectile/bullet/reusable/mining_bomb
+/obj/projectile/bullet/mining_bomb
name = "mining bomb"
desc = "A bomb. Why are you examining this?"
icon_state = "mine_bomb"
@@ -570,13 +570,16 @@
light_outer_range = 1
light_power = 1
light_color = COLOR_LIGHT_ORANGE
- ammo_type = /obj/structure/mining_bomb
+ embed_type = null
-/obj/projectile/bullet/reusable/mining_bomb/handle_drop()
- if(dropped)
- return
- dropped = TRUE
- new ammo_type(get_turf(src), firer)
+/obj/projectile/bullet/mining_bomb/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/projectile_drop, /obj/structure/mining_bomb)
+ RegisterSignal(src, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(handle_drop))
+
+/obj/projectile/bullet/mining_bomb/proc/handle_drop(datum/source, obj/structure/mining_bomb/mining_bomb)
+ SIGNAL_HANDLER
+ addtimer(CALLBACK(mining_bomb, TYPE_PROC_REF(/obj/structure/mining_bomb, prime), firer), mining_bomb.prime_time)
/obj/structure/mining_bomb
name = "mining bomb"
@@ -603,7 +606,6 @@
/obj/structure/mining_bomb/Initialize(mapload, atom/movable/firer)
. = ..()
generate_image()
- addtimer(CALLBACK(src, PROC_REF(prime), firer), prime_time)
/obj/structure/mining_bomb/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
if(same_z_layer)
diff --git a/code/modules/mod/modules/modules_timeline.dm b/code/modules/mod/modules/modules_timeline.dm
index 22c27a37b588b..c5ba0cefdd5b8 100644
--- a/code/modules/mod/modules/modules_timeline.dm
+++ b/code/modules/mod/modules/modules_timeline.dm
@@ -225,7 +225,7 @@
//fire projectile
var/obj/projectile/energy/chrono_beam/chrono_beam = new /obj/projectile/energy/chrono_beam(get_turf(src))
chrono_beam.tem_weakref = WEAKREF(src)
- chrono_beam.preparePixelProjectile(target, mod.wearer)
+ chrono_beam.aim_projectile(target, mod.wearer)
chrono_beam.firer = mod.wearer
playsound(src, 'sound/items/modsuit/time_anchor_set.ogg', 50, TRUE)
INVOKE_ASYNC(chrono_beam, TYPE_PROC_REF(/obj/projectile, fire))
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index 62d9ee514a148..e2d8454b858f5 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -204,6 +204,6 @@
// "Burn" damage is equally strong against internal components and exterior casing
// "Brute" damage mostly damages the casing.
/obj/machinery/modular_computer/bullet_act(obj/projectile/proj)
- return cpu?.bullet_act(proj) || ..()
+ return cpu?.projectile_hit(proj) || ..()
#undef CPU_INTERACTABLE
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 7b5d256d24211..c21e7d9855b8d 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -29,7 +29,7 @@
var/degrees = 0
var/font = PEN_FONT
var/requires_gravity = TRUE // can you use this to write in zero-g
- embedding = list(embed_chance = 50)
+ embed_type = /datum/embedding/pen
sharpness = SHARP_POINTY
var/dart_insert_icon = 'icons/obj/weapons/guns/toy.dmi'
var/dart_insert_casing_icon_state = "overlay_pen"
@@ -37,6 +37,9 @@
/// If this pen can be clicked in order to retract it
var/can_click = TRUE
+/datum/embedding/pen
+ embed_chance = 50
+
/obj/item/pen/Initialize(mapload)
. = ..()
/* MONKE EDIT
@@ -83,11 +86,11 @@
/obj/item/pen/proc/on_inserted_into_dart(datum/source, obj/projectile/dart, mob/user, embedded = FALSE)
SIGNAL_HANDLER
-/obj/item/pen/proc/get_dart_var_modifiers()
+/obj/item/pen/proc/get_dart_var_modifiers(obj/projectile/projectile)
return list(
"damage" = max(5, throwforce),
"speed" = max(0, throw_speed - 3),
- "embedding" = embedding,
+ "embedding" = get_embed().create_copy(projectile),
"armour_penetration" = armour_penetration,
"wound_bonus" = wound_bonus,
"bare_wound_bonus" = bare_wound_bonus,
@@ -189,7 +192,10 @@
"Black and Silver" = "pen-fountain-b",
"Command Blue" = "pen-fountain-cb"
)
- embedding = list("embed_chance" = 75)
+ embed_type = /datum/embedding/pen/captain
+
+/datum/embedding/pen/captain
+ embed_chance = 50
/obj/item/pen/fountain/captain/Initialize(mapload)
. = ..()
@@ -367,7 +373,7 @@
inhand_icon_state = hidden_icon
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- embedding = list(embed_chance = 100) // Rule of cool
+ set_embed(/datum/embedding/edagger_active)
else
name = initial(name)
desc = initial(desc)
@@ -375,15 +381,17 @@
inhand_icon_state = initial(inhand_icon_state)
lefthand_file = initial(lefthand_file)
righthand_file = initial(righthand_file)
- embedding = list(embed_chance = EMBED_CHANCE)
+ set_embed(embed_type)
- updateEmbedding()
if(user)
balloon_alert(user, "[hidden_name] [active ? "active" : "concealed"]")
playsound(src, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 5, TRUE)
set_light_on(active)
return COMPONENT_NO_DEFAULT_MESSAGE
+/datum/embedding/edagger_active
+ embed_chance = 100
+
/obj/item/pen/edagger/proc/on_scan(datum/source, mob/user, list/extra_data)
SIGNAL_HANDLER
LAZYADD(extra_data[DETSCAN_CATEGORY_ILLEGAL], "Hard-light generator detected.")
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index 26df200707e10..0915fcddf1eac 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -239,21 +239,6 @@
charge_light_type = null
connector_type = "slimecore"
-/obj/item/stock_parts/power_store/cell/beam_rifle
- name = "beam rifle capacitor"
- desc = "A high powered capacitor that can provide huge amounts of energy in an instant."
- maxcharge = STANDARD_CELL_CHARGE * 50
- chargerate = STANDARD_CELL_CHARGE * 5 //Extremely energy intensive
-
-/obj/item/stock_parts/power_store/cell/beam_rifle/corrupt()
- return
-
-/obj/item/stock_parts/power_store/cell/beam_rifle/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- charge = clamp((charge-(10000/severity)),0,maxcharge)
-
/obj/item/stock_parts/power_store/cell/emergency_light
name = "miniature power cell"
desc = "A tiny power cell with a very low power capacity. Used in light fixtures to power them in the event of an outage."
diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm
index 8144f169375b0..552ebea63c291 100644
--- a/code/modules/power/rtg.dm
+++ b/code/modules/power/rtg.dm
@@ -73,10 +73,10 @@
tesla_zap(src, 5, power_gen * 0.05)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), src, 2, 3, 4, null, 8), 10 SECONDS) // Not a normal explosion.
-/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/Proj)
+/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/proj)
. = ..()
- if(!going_kaboom && istype(Proj) && Proj.damage > 0 && ((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)))
- log_bomber(Proj.firer, "triggered a", src, "explosion via projectile")
+ if(!going_kaboom && istype(proj) && proj.damage > 0 && ((proj.damage_type == BURN) || (proj.damage_type == BRUTE)))
+ log_bomber(proj.firer, "triggered a", src, "explosion via projectile")
overload()
/obj/machinery/power/rtg/abductor/blob_act(obj/structure/blob/B)
diff --git a/code/modules/power/singularity/boh_tear.dm b/code/modules/power/singularity/boh_tear.dm
deleted file mode 100644
index 7216a78e3d790..0000000000000
--- a/code/modules/power/singularity/boh_tear.dm
+++ /dev/null
@@ -1,48 +0,0 @@
-/// BoH tear
-/// The BoH tear is a stationary singularity with a really high gravitational pull, which collapses briefly after being created
-/// The BoH isn't deleted for 10 minutes (only moved to nullspace) so that admins may retrieve the things back in case of a grief
-#define BOH_TEAR_CONSUME_RANGE 1
-#define BOH_TEAR_GRAV_PULL 25
-
-/obj/boh_tear
- name = "tear in the fabric of reality"
- desc = "Your own comprehension of reality starts bending as you stare this."
- anchored = TRUE
- appearance_flags = LONG_GLIDE
- density = TRUE
- icon = 'icons/effects/96x96.dmi'
- icon_state = "boh_tear"
- plane = MASSIVE_OBJ_PLANE
- plane = ABOVE_LIGHTING_PLANE
- light_outer_range = 6
- move_resist = INFINITY
- obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
- pixel_x = -32
- pixel_y = -32
- resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
- flags_1 = SUPERMATTER_IGNORES_1
-
-/obj/boh_tear/Initialize(mapload)
- . = ..()
- QDEL_IN(src, 5 SECONDS) // vanishes after 5 seconds
-
- AddComponent(
- /datum/component/singularity, \
- consume_range = BOH_TEAR_CONSUME_RANGE, \
- grav_pull = BOH_TEAR_GRAV_PULL, \
- roaming = FALSE, \
- singularity_size = STAGE_SIX, \
- )
-
-/obj/boh_tear/attack_tk(mob/user)
- if(!isliving(user))
- return
- var/mob/living/jedi = user
- to_chat(jedi, span_userdanger("You don't feel like you are real anymore."))
- jedi.dust_animation()
- jedi.spawn_dust()
- addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, attack_hand), jedi), 0.5 SECONDS)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
-#undef BOH_TEAR_CONSUME_RANGE
-#undef BOH_TEAR_GRAV_PULL
diff --git a/code/modules/power/singularity/reality_tear.dm b/code/modules/power/singularity/reality_tear.dm
new file mode 100644
index 0000000000000..f1daff9d1c896
--- /dev/null
+++ b/code/modules/power/singularity/reality_tear.dm
@@ -0,0 +1,70 @@
+/// Tear in the Fabric of Reality ///
+// Typically spawned by placing two bags of holding into one another, collapsing into a wandering singularity after a brief period as a stationary singularity.
+
+/obj/reality_tear
+ name = "tear in the fabric of reality"
+ desc = "Your own comprehension of reality starts bending as you stare this."
+ anchored = TRUE
+ appearance_flags = LONG_GLIDE
+ density = TRUE
+ icon = 'icons/effects/96x96.dmi'
+ icon_state = "boh_tear"
+ plane = MASSIVE_OBJ_PLANE
+ plane = ABOVE_LIGHTING_PLANE
+ light_outer_range = 6
+ move_resist = INFINITY
+ obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
+ pixel_x = -32
+ pixel_y = -32
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
+ flags_1 = SUPERMATTER_IGNORES_1
+ /// Range that our singularity component consumes objects
+ var/singularity_consume_range = 1
+ /// Ranges that the singularity pulls objects
+ var/singularity_grav_pull = 21
+ /// Time before we begin our bagulo spawn
+ var/collapse_spawn_time = 9 SECONDS
+
+/obj/reality_tear/proc/start_disaster()
+ apply_wibbly_filters(src)
+ playsound(loc, 'sound/effects/clockcult_gateway_disrupted.ogg', vary = 200, extrarange = 3, falloff_exponent = 1, frequency = 0.33, pressure_affected = FALSE, ignore_walls = TRUE, falloff_distance = 7)
+ AddComponent(
+ /datum/component/singularity, \
+ consume_range = singularity_consume_range, \
+ grav_pull = singularity_grav_pull, \
+ roaming = FALSE, \
+ singularity_size = STAGE_SIX, \
+ )
+ addtimer(CALLBACK(src, PROC_REF(reality_collapse)), collapse_spawn_time, TIMER_DELETE_ME)
+ animate(src, time = 7.5 SECONDS, transform = transform.Scale(2), flags = ANIMATION_PARALLEL)
+ animate(time = 2 SECONDS, transform = transform.Scale(0.25), easing = ELASTIC_EASING)
+ animate(time = 0.5 SECONDS, alpha = 0)
+
+/obj/reality_tear/proc/reality_collapse()
+ playsound(loc, 'sound/effects/supermatter.ogg', 200, vary = TRUE, extrarange = 3, falloff_exponent = 1, frequency = 0.5, pressure_affected = FALSE, ignore_walls = TRUE, falloff_distance = 7)
+ var/obj/singularity/bagulo = new(loc)
+ bagulo.expand(STAGE_TWO)
+ bagulo.energy = 400
+ qdel(src)
+
+/obj/reality_tear/attack_tk(mob/user)
+ if(!isliving(user))
+ return
+ var/mob/living/jedi = user
+ to_chat(jedi, span_userdanger("You don't feel like you are real anymore."))
+ jedi.dust_animation()
+ jedi.spawn_dust()
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, attack_hand), jedi), 0.5 SECONDS)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+//The temporary tears in reality. Collapses into nothing, and has a significantly lower gravity pull range, but consumes more widely.
+
+/obj/reality_tear/temporary
+ name = "puncture in the fabric of reality"
+ desc = "Count your lucky stars that this wasn't anywhere near you."
+ singularity_consume_range = 2
+ singularity_grav_pull = 3
+ collapse_spawn_time = 2 SECONDS
+
+/obj/reality_tear/temporary/reality_collapse()
+ qdel(src)
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index fc59c5911c351..6425a977291f3 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -29,13 +29,12 @@
if(isnull(loaded_projectile))
return FALSE
AddComponent(/datum/component/pellet_cloud, projectile_type, pellets)
- SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_INIT, target, user, fired_from, randomspread, spread, zone_override, params, distro)
var/next_delay = click_cooldown_override || CLICK_CD_RANGE
if(HAS_TRAIT(user, TRAIT_DOUBLE_TAP))
next_delay = round(next_delay * 0.5)
-
user.changeNext_move(next_delay)
+
if(!tk_firing(user, fired_from))
user.newtonian_move(get_dir(target, user))
else if(ismovable(fired_from))
@@ -44,7 +43,9 @@
var/throwtarget = get_step(fired_from, get_dir(target, fired_from))
firer.safe_throw_at(throwtarget, 1, 2)
update_appearance()
+
SEND_SIGNAL(src, COMSIG_FIRE_CASING, target, user, fired_from, randomspread, spread, zone_override, params, distro, thrown_proj)
+
return TRUE
/obj/item/ammo_casing/proc/tk_firing(mob/living/user, atom/fired_from)
@@ -77,24 +78,22 @@
/obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread, atom/fired_from)
var/turf/curloc = get_turf(fired_from)
if (!istype(targloc) || !istype(curloc) || !loaded_projectile)
- return FALSE
+ return null
var/firing_dir
if(loaded_projectile.firer)
firing_dir = get_dir(fired_from, target)
if(!loaded_projectile.suppressed && firing_effect_type && !tk_firing(user, fired_from))
- new firing_effect_type(get_turf(src), firing_dir)
+ new firing_effect_type(user || get_turf(src), firing_dir)
var/direct_target
if(target && curloc.Adjacent(targloc, target=targloc, mover=src)) //if the target is right on our location or adjacent (including diagonally if reachable) we'll skip the travelling code in the proj's fire()
direct_target = target
- if(!direct_target)
- var/modifiers = params2list(params)
- loaded_projectile.preparePixelProjectile(target, fired_from, modifiers, spread)
+ loaded_projectile.aim_projectile(target, tk_firing(user, fired_from) ? fired_from : user, params2list(params), spread)
var/obj/projectile/loaded_projectile_cache = loaded_projectile
loaded_projectile = null
loaded_projectile_cache.fire(null, direct_target)
- return TRUE
+ return loaded_projectile_cache
/obj/item/ammo_casing/proc/spread(turf/target, turf/current, distro)
var/dx = abs(target.x - current.x)
diff --git a/code/modules/projectiles/ammunition/caseless/foam.dm b/code/modules/projectiles/ammunition/ballistic/foam.dm
similarity index 75%
rename from code/modules/projectiles/ammunition/caseless/foam.dm
rename to code/modules/projectiles/ammunition/ballistic/foam.dm
index 6b3ae12ed9562..f03b7fe2d689b 100644
--- a/code/modules/projectiles/ammunition/caseless/foam.dm
+++ b/code/modules/projectiles/ammunition/ballistic/foam.dm
@@ -1,7 +1,7 @@
-/obj/item/ammo_casing/caseless/foam_dart
+/obj/item/ammo_casing/foam_dart
name = "foam dart"
desc = "It's Donk or Don't! Ages 8 and up."
- projectile_type = /obj/projectile/bullet/reusable/foam_dart
+ projectile_type = /obj/projectile/bullet/foam_dart
caliber = CALIBER_FOAM
icon = 'icons/obj/weapons/guns/toy.dmi'
icon_state = "foamdart"
@@ -12,7 +12,7 @@
var/min_choke_duration = 5 SECONDS
var/max_choke_duration = 12 SECONDS
-/obj/item/ammo_casing/caseless/foam_dart/Initialize(mapload)
+/obj/item/ammo_casing/foam_dart/Initialize(mapload)
. = ..()
AddComponent(/datum/component/edible, \
initial_reagents = list( \
@@ -29,18 +29,22 @@
ADD_TRAIT(src, TRAIT_FISHING_BAIT, INNATE_TRAIT)
RegisterSignal(src, COMSIG_FOOD_EATEN, PROC_REF(on_bite))
-/obj/item/ammo_casing/caseless/foam_dart/proc/on_bite(atom/used_in, mob/living/target, mob/living/user, bitecount, bitesize)
+/obj/item/ammo_casing/foam_dart/proc/on_bite(atom/used_in, mob/living/target, mob/living/user, bitecount, bitesize)
SIGNAL_HANDLER
if (HAS_TRAIT(target, TRAIT_CLUMSY))
inhale(target)
return DESTROY_FOOD
-/obj/item/ammo_casing/caseless/foam_dart/proc/inhale(mob/living/target)
+/obj/item/ammo_casing/foam_dart/proc/inhale(mob/living/target)
visible_message(span_danger("[target] inhales [src]!"), \
span_userdanger("You inhale [src]!"))
target.AddComponent(/datum/status_effect/choke, new src.type, flaming = FALSE, vomit_delay = rand(min_choke_duration, max_choke_duration))
-/obj/item/ammo_casing/caseless/foam_dart/update_icon_state()
+/obj/item/ammo_casing/foam_dart/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless, TRUE)
+
+/obj/item/ammo_casing/foam_dart/update_icon_state()
. = ..()
if(modified)
icon_state = "[base_icon_state]_empty"
@@ -49,12 +53,12 @@
icon_state = "[base_icon_state]"
loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]"
-/obj/item/ammo_casing/caseless/foam_dart/update_desc()
+/obj/item/ammo_casing/foam_dart/update_desc()
. = ..()
desc = "It's Donk or Don't! [modified ? "... Although, this one doesn't look too safe." : "Ages 8 and up."]"
-/obj/item/ammo_casing/caseless/foam_dart/attackby(obj/item/A, mob/user, params)
- var/obj/projectile/bullet/reusable/foam_dart/FD = loaded_projectile
+/obj/item/ammo_casing/foam_dart/attackby(obj/item/A, mob/user, params)
+ var/obj/projectile/bullet/foam_dart/FD = loaded_projectile
if (A.tool_behaviour == TOOL_SCREWDRIVER && !modified)
modified = TRUE
FD.modified = TRUE
@@ -77,18 +81,18 @@
else
return ..()
-/obj/item/ammo_casing/caseless/foam_dart/attack_self(mob/living/user)
- var/obj/projectile/bullet/reusable/foam_dart/FD = loaded_projectile
+/obj/item/ammo_casing/foam_dart/attack_self(mob/living/user)
+ var/obj/projectile/bullet/foam_dart/FD = loaded_projectile
if(FD.pen)
FD.damage = initial(FD.damage)
user.put_in_hands(FD.pen)
to_chat(user, span_notice("You remove [FD.pen] from [src]."))
FD.pen = null
-/obj/item/ammo_casing/caseless/foam_dart/riot
+/obj/item/ammo_casing/foam_dart/riot
name = "riot foam dart"
desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up."
- projectile_type = /obj/projectile/bullet/reusable/foam_dart/riot
+ projectile_type = /obj/projectile/bullet/foam_dart/riot
icon_state = "foamdart_riot"
base_icon_state = "foamdart_riot"
custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT* 1.125)
diff --git a/code/modules/projectiles/ammunition/ballistic/harpoon.dm b/code/modules/projectiles/ammunition/ballistic/harpoon.dm
new file mode 100644
index 0000000000000..61aa16fe56ebd
--- /dev/null
+++ b/code/modules/projectiles/ammunition/ballistic/harpoon.dm
@@ -0,0 +1,14 @@
+/obj/item/ammo_casing/harpoon
+ name = "harpoon"
+ caliber = CALIBER_HARPOON
+ icon_state = "magspear"
+ base_icon_state = "magspear"
+ projectile_type = /obj/projectile/bullet/harpoon
+
+/obj/item/ammo_casing/harpoon/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless)
+
+/obj/item/ammo_casing/harpoon/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]"
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index f878685ec397f..9713d97131aa6 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -6,6 +6,9 @@
caliber = CALIBER_357
projectile_type = /obj/projectile/bullet/a357
+/obj/item/ammo_casing/a357/spent
+ projectile_type = null
+
/obj/item/ammo_casing/a357/match
name = ".357 match bullet casing"
desc = "A .357 bullet casing, manufactured to exceedingly high standards."
diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm
index f739383f80c65..4f35ce029e13b 100644
--- a/code/modules/projectiles/ammunition/ballistic/rifle.dm
+++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm
@@ -126,7 +126,6 @@
custom_materials = AMMO_MATS_AP
advanced_print_req = TRUE
-
///Mining heavy rifle
/obj/item/ammo_casing/minerjdj
diff --git a/code/modules/projectiles/ammunition/caseless/rocket.dm b/code/modules/projectiles/ammunition/ballistic/rocket.dm
similarity index 59%
rename from code/modules/projectiles/ammunition/caseless/rocket.dm
rename to code/modules/projectiles/ammunition/ballistic/rocket.dm
index 030e8275435d9..4abd3608493d9 100644
--- a/code/modules/projectiles/ammunition/caseless/rocket.dm
+++ b/code/modules/projectiles/ammunition/ballistic/rocket.dm
@@ -1,28 +1,47 @@
-/obj/item/ammo_casing/caseless/rocket
+/obj/item/ammo_casing/rocket
name = "\improper PM-9HE"
desc = "An 84mm High Explosive rocket. Fire at people and pray."
caliber = CALIBER_84MM
icon_state = "srm-8"
+ base_icon_state = "srm-8"
projectile_type = /obj/projectile/bullet/rocket
-/obj/item/ammo_casing/caseless/rocket/heap
+/obj/item/ammo_casing/rocket/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless)
+
+/obj/item/ammo_casing/rocket/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]"
+
+/obj/item/ammo_casing/rocket/heap
name = "\improper PM-9HEAP"
desc = "An 84mm High Explosive All Purpose rocket. For when you just need something to not exist anymore."
icon_state = "84mm-heap"
+ base_icon_state = "84mm-heap"
projectile_type = /obj/projectile/bullet/rocket/heap
-/obj/item/ammo_casing/caseless/rocket/weak
+/obj/item/ammo_casing/rocket/weak
name = "\improper PM-9HE Low-Yield"
desc = "An 84mm High Explosive rocket. This one isn't quite as devastating."
projectile_type = /obj/projectile/bullet/rocket/weak
-/obj/item/ammo_casing/caseless/a75
+/obj/item/ammo_casing/a75
desc = "A .75 bullet casing."
caliber = CALIBER_75
icon_state = "s-casing-live"
+ base_icon_state = "s-casing-live"
projectile_type = /obj/projectile/bullet/gyro
-/obj/item/ammo_casing/caseless/ignifist
+/obj/item/ammo_casing/a75/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless)
+
+/obj/item/ammo_casing/a75/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]"
+
+/obj/item/ammo_casing/ignifist
name = "\improper 60mm Ignifist"
desc = "A 60mm anti tank rocket. Lightly tickles flesh, sledge hammers steel."
caliber = CALIBER_60MM
diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
index 33807df662fb4..ce576af7b31df 100644
--- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
@@ -122,7 +122,6 @@
variance = 25 // 6 pellets for 10 stam and 2 damage each
custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*2)
-
/obj/item/ammo_casing/shotgun/dragonsbreath
name = "dragonsbreath shell"
desc = "A shotgun shell which fires a spread of incendiary pellets."
diff --git a/code/modules/projectiles/ammunition/ballistic/smg.dm b/code/modules/projectiles/ammunition/ballistic/smg.dm
index eedbc74993051..700e5c4395614 100644
--- a/code/modules/projectiles/ammunition/ballistic/smg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/smg.dm
@@ -27,7 +27,7 @@
projectile_type = /obj/projectile/bullet/c45/hp
advanced_print_req = TRUE
-/obj/item/ammo_casing/caseless/c45_caseless ///Yes yes caseless parent, it belongs here.
+/obj/item/ammo_casing/c45_caseless ///Yes yes caseless parent, it belongs here.
name = "caseless .45 bullet"
desc = "A .45 bullet casing. This one is caseless!"
caliber = CALIBER_45
@@ -37,7 +37,7 @@
name = "ammo box (caseless .45)"
icon = 'monkestation/icons/obj/weapons/guns/ammo.dmi'
icon_state = "caseless_45box"
- ammo_type = /obj/item/ammo_casing/caseless/c45_caseless
+ ammo_type = /obj/item/ammo_casing/c45_caseless
multiple_sprites = AMMO_BOX_FULL_EMPTY
diff --git a/code/modules/projectiles/ammunition/caseless/_caseless.dm b/code/modules/projectiles/ammunition/caseless/_caseless.dm
deleted file mode 100644
index 45a5e0283030d..0000000000000
--- a/code/modules/projectiles/ammunition/caseless/_caseless.dm
+++ /dev/null
@@ -1,18 +0,0 @@
-/obj/item/ammo_casing/caseless
- desc = "A caseless bullet casing."
- firing_effect_type = null
- heavy_metal = FALSE
-
-/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
- if (!..()) //failed firing
- return FALSE
- if(isgun(fired_from))
- var/obj/item/gun/shot_from = fired_from
- if(shot_from.chambered == src)
- shot_from.chambered = null //Nuke it. Nuke it now.
- qdel(src)
- return TRUE
-
-/obj/item/ammo_casing/caseless/update_icon_state()
- . = ..()
- icon_state = "[initial(icon_state)]"
diff --git a/code/modules/projectiles/ammunition/caseless/energy.dm b/code/modules/projectiles/ammunition/caseless/energy.dm
deleted file mode 100644
index 5487d0ef36e9c..0000000000000
--- a/code/modules/projectiles/ammunition/caseless/energy.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/obj/item/ammo_casing/caseless/laser
- name = "laser casing"
- desc = "You shouldn't be seeing this."
- caliber = CALIBER_LASER
- icon_state = "s-casing-live"
- slot_flags = null
- projectile_type = /obj/projectile/beam
- fire_sound = 'monkestation/sound/weapons/gun/energy/Laser1.ogg'
- firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
diff --git a/code/modules/projectiles/ammunition/caseless/harpoon.dm b/code/modules/projectiles/ammunition/caseless/harpoon.dm
deleted file mode 100644
index 5f1b402cf7f4c..0000000000000
--- a/code/modules/projectiles/ammunition/caseless/harpoon.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-/obj/item/ammo_casing/caseless/harpoon
- name = "harpoon"
- caliber = CALIBER_HARPOON
- icon_state = "magspear"
- projectile_type = /obj/projectile/bullet/harpoon
diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm
index d7c0f553a8d09..dab53d31c1b5e 100644
--- a/code/modules/projectiles/ammunition/energy/laser.dm
+++ b/code/modules/projectiles/ammunition/energy/laser.dm
@@ -141,6 +141,26 @@
projectile_type = /obj/projectile/energy/cryo
select_name = "cryo"
+///not exactly an energy ammo casing, but it's used by the laser gatling.
+/obj/item/ammo_casing/laser
+ name = "laser casing"
+ desc = "You shouldn't be seeing this."
+ caliber = CALIBER_LASER
+ icon_state = "s-casing-live"
+ base_icon_state = "s-casing-live"
+ slot_flags = null
+ projectile_type = /obj/projectile/beam
+ fire_sound = 'sound/weapons/laser.ogg'
+ firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
+
+/obj/item/ammo_casing/laser/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless)
+
+/obj/item/ammo_casing/laser/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]"
+
/obj/item/ammo_casing/energy/laser/plasma_glob
projectile_type = /obj/projectile/beam/laser/plasma_glob
fire_sound = 'monkestation/code/modules/blueshift/sounds/incinerate.ogg'
diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
index ef97e476dcd24..a10e4270e98d1 100644
--- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm
+++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
@@ -44,6 +44,22 @@
top_off(starting=TRUE)
update_icon_state()
+/obj/item/ammo_box/Destroy(force)
+ for (var/obj/item/ammo_casing/casing as anything in stored_ammo)
+ if (!ispath(casing))
+ qdel(casing)
+ stored_ammo = null
+ return ..()
+
+/obj/item/ammo_box/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone in stored_ammo)
+ remove_from_stored_ammo(gone)
+
+/obj/item/ammo_box/proc/remove_from_stored_ammo(atom/movable/gone)
+ stored_ammo -= gone
+ update_appearance()
+
/obj/item/ammo_box/add_weapon_description()
AddElement(/datum/element/weapon_description, attached_proc = PROC_REF(add_notes_box))
@@ -54,13 +70,22 @@
readout += "Up to [span_warning("[max_ammo] [caliber] rounds")] can be found within this magazine. \
\nAccidentally discharging any of these projectiles may void your insurance contract."
- var/obj/item/ammo_casing/mag_ammo = get_round(TRUE)
+ var/obj/item/ammo_casing/mag_ammo = get_and_shuffle_round()
if(istype(mag_ammo))
readout += "\n[mag_ammo.add_notes_ammo()]"
return readout.Join("\n")
+///list of every bullet in the box
+///forces all bullets to lazyload
+/obj/item/ammo_box/proc/ammo_list()
+ for (var/i in 1 to length(stored_ammo))
+ if (ispath(stored_ammo[i]))
+ var/casing_type = stored_ammo[i]
+ stored_ammo[i] = new casing_type(src)
+ return stored_ammo.Copy()
+
/**
* top_off is used to refill the magazine to max, in case you want to increase the size of a magazine with VV then refill it at once
*
@@ -77,69 +102,93 @@
stack_trace("Tried loading unsupported ammocasing type [load_type] into ammo box [type].")
return
- for(var/i in max(1, stored_ammo.len) to max_ammo)
- stored_ammo += new round_check(src)
+ for(var/i in max(1, stored_ammo.len + 1) to max_ammo)
+ stored_ammo += starting ? round_check : new round_check(src)
update_appearance()
-///gets a round from the magazine, if keep is TRUE the round will stay in the gun
-/obj/item/ammo_box/proc/get_round(keep = FALSE)
- if (!stored_ammo.len)
+///gets a round from the magazine
+/obj/item/ammo_box/proc/get_round()
+ var/ammo_len = length(stored_ammo)
+ if (!ammo_len)
+ return null
+ var/casing = stored_ammo[ammo_len]
+ if (ispath(casing))
+ casing = new casing(src)
+ stored_ammo[ammo_len] = casing
+ return casing
+
+/// Gets a round from the magazine and puts it back at the bottom of the ammo list
+/obj/item/ammo_box/proc/get_and_shuffle_round()
+ var/casing = get_round()
+ if (!casing)
return null
- else
- var/b = stored_ammo[stored_ammo.len]
- stored_ammo -= b
- if (keep)
- stored_ammo.Insert(1,b)
- return b
+ stored_ammo -= casing
+ stored_ammo.Insert(1, casing)
+ return casing
///puts a round into the magazine
-/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0)
+/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/new_round, replace_spent = 0)
// Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type.
- if(!R || !(caliber ? (caliber == R.caliber) : (ammo_type == R.type)))
+ if(!new_round || !(caliber ? (caliber == new_round.caliber) : (ammo_type == new_round.type)))
return FALSE
if (stored_ammo.len < max_ammo)
- stored_ammo += R
- R.forceMove(src)
+ stored_ammo += new_round
+ new_round.forceMove(src)
+ if(new_round.custom_materials && !(item_flags & ABSTRACT))
+ var/list/new_materials = custom_materials?.Copy() || list()
+ for(var/mat in new_round.custom_materials)
+ new_materials[mat] += new_round.custom_materials[mat]
+ set_custom_materials(new_materials)
return TRUE
+ if(!replace_spent)
+ return FALSE
+
//for accessibles magazines (e.g internal ones) when full, start replacing spent ammo
- else if(replace_spent)
- for(var/obj/item/ammo_casing/AC in stored_ammo)
- if(!AC.loaded_projectile)//found a spent ammo
- stored_ammo -= AC
- AC.forceMove(get_turf(src.loc))
-
- stored_ammo += R
- R.forceMove(src)
- return TRUE
+ for(var/obj/item/ammo_casing/casing as anything in stored_ammo)
+ if(ispath(casing) || casing.loaded_projectile)
+ continue
+ //found a spent ammo
+ stored_ammo -= casing
+ casing.forceMove(get_turf(src))
+
+ stored_ammo += new_round
+ new_round.forceMove(src)
+ return TRUE
return FALSE
///Whether or not the box can be loaded, used in overrides
/obj/item/ammo_box/proc/can_load(mob/user)
return TRUE
-/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0)
+/obj/item/ammo_box/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if(try_load(user, tool))
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/ammo_box/proc/try_load(mob/living/user, obj/item/tool, silent = FALSE, replace_spent = FALSE)
var/num_loaded = 0
if(!can_load(user))
return
- if(istype(A, /obj/item/ammo_box))
- var/obj/item/ammo_box/AM = A
- for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
- var/did_load = give_round(AC, replace_spent)
+
+ if(istype(tool, /obj/item/ammo_box))
+ var/obj/item/ammo_box/other_box = tool
+ for(var/obj/item/ammo_casing/casing in other_box.ammo_list())
+ var/did_load = give_round(casing, replace_spent)
if(did_load)
- AM.stored_ammo -= AC
+ other_box.stored_ammo -= casing
num_loaded++
if(!did_load || !multiload)
break
if(num_loaded)
- AM.update_appearance()
- if(isammocasing(A))
- var/obj/item/ammo_casing/AC = A
- if(give_round(AC, replace_spent))
- user.transferItemToLoc(AC, src, TRUE)
+ other_box.update_appearance()
+
+ if(isammocasing(tool))
+ var/obj/item/ammo_casing/casing = tool
+ if(give_round(casing, replace_spent))
+ user.transferItemToLoc(casing, src, TRUE)
num_loaded++
- AC.update_appearance()
+ casing.update_appearance()
if(num_loaded)
if(!silent)
@@ -183,33 +232,18 @@
///Count of number of bullets in the magazine
/obj/item/ammo_box/magazine/proc/ammo_count(countempties = TRUE)
var/boolets = 0
- for(var/obj/item/ammo_casing/bullet in stored_ammo)
- if(bullet && (bullet.loaded_projectile || countempties))
+ for(var/obj/item/ammo_casing/bullet as anything in stored_ammo)
+ if(ispath(bullet) || bullet && (bullet.loaded_projectile || countempties))
boolets++
return boolets
-///list of every bullet in the magazine
-/obj/item/ammo_box/magazine/proc/ammo_list(drop_list = FALSE)
- var/list/L = stored_ammo.Copy()
- if(drop_list)
- stored_ammo.Cut()
- return L
-
///drops the entire contents of the magazine on the floor
/obj/item/ammo_box/magazine/proc/empty_magazine()
- var/turf_mag = get_turf(src)
- for(var/obj/item/ammo in stored_ammo)
- ammo.forceMove(turf_mag)
- stored_ammo -= ammo
-
-/obj/item/ammo_box/magazine/handle_atom_del(atom/A)
- stored_ammo -= A
- update_appearance()
-
-/obj/item/ammo_box/handle_atom_del(atom/A)
- stored_ammo.Remove(A)
- return ..()
-
+ var/turf/turf_mag = get_turf(src)
+ var/obj/item/ammo_casing/casing = get_round()
+ while (casing)
+ casing.forceMove(turf_mag)
+ casing = get_round()
/obj/item/ammo_box/advanced
multiple_sprites = AMMO_BOX_FULL_EMPTY
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index e03c66e9cfb0e..5b3eba6c97d4b 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -424,13 +424,13 @@
name = "ammo box (Foam Darts)"
icon = 'icons/obj/weapons/guns/toy.dmi'
icon_state = "foambox"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/foam_dart
max_ammo = 40
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT*5)
/obj/item/ammo_box/foambox/riot
icon_state = "foambox_riot"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/foam_dart/riot
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*25)
diff --git a/code/modules/projectiles/boxes_magazines/external/grenade.dm b/code/modules/projectiles/boxes_magazines/external/grenade.dm
index 2930bdc69c62d..312c258755521 100644
--- a/code/modules/projectiles/boxes_magazines/external/grenade.dm
+++ b/code/modules/projectiles/boxes_magazines/external/grenade.dm
@@ -3,7 +3,7 @@
/obj/item/ammo_box/magazine/m75
name = "specialized magazine (.75)"
icon_state = "75"
- ammo_type = /obj/item/ammo_casing/caseless/a75
+ ammo_type = /obj/item/ammo_casing/a75
caliber = CALIBER_75
multiple_sprites = AMMO_BOX_FULL_EMPTY
max_ammo = 8
@@ -12,7 +12,7 @@
/obj/item/ammo_box/magazine/ignifist
name = "Ignifist 30 Rocket"
icon_state = "ignifist"
- ammo_type = /obj/item/ammo_casing/caseless/ignifist
+ ammo_type = /obj/item/ammo_casing/ignifist
caliber = CALIBER_60MM
max_ammo = 1
multiple_sprites = AMMO_BOX_FULL_EMPTY
diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm
index 3736746503ba3..9bf9b1dff4129 100644
--- a/code/modules/projectiles/boxes_magazines/external/pistol.dm
+++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm
@@ -65,7 +65,7 @@
icon = 'monkestation/icons/obj/weapons/guns/whispering_jester_45/item.dmi'
icon_state = "mag_jester"
multiple_sprites = AMMO_BOX_PER_BULLET
- ammo_type = /obj/item/ammo_casing/caseless/c45_caseless
+ ammo_type = /obj/item/ammo_casing/c45_caseless
caliber = CALIBER_45
max_ammo = 18
diff --git a/code/modules/projectiles/boxes_magazines/external/rechargable.dm b/code/modules/projectiles/boxes_magazines/external/rechargable.dm
index 37ee0d474f730..9c0ea0faeeb12 100644
--- a/code/modules/projectiles/boxes_magazines/external/rechargable.dm
+++ b/code/modules/projectiles/boxes_magazines/external/rechargable.dm
@@ -3,7 +3,7 @@
desc = "A rechargeable, detachable battery that serves as a magazine for laser rifles."
icon_state = "oldrifle-20"
base_icon_state = "oldrifle"
- ammo_type = /obj/item/ammo_casing/caseless/laser
+ ammo_type = /obj/item/ammo_casing/laser
caliber = CALIBER_LASER
max_ammo = 20
diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm
index 7503cd032f60d..1c2a6470796f0 100644
--- a/code/modules/projectiles/boxes_magazines/external/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/external/toy.dm
@@ -1,13 +1,13 @@
/obj/item/ammo_box/magazine/toy
name = "foam force META magazine"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/foam_dart
caliber = CALIBER_FOAM
/obj/item/ammo_box/magazine/toy/smg
name = "foam force SMG magazine"
icon_state = "smg9mm"
base_icon_state = "smg9mm"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/foam_dart
max_ammo = 20
/obj/item/ammo_box/magazine/toy/smg/update_icon_state()
@@ -15,7 +15,7 @@
icon_state = "[base_icon_state]-[LAZYLEN(stored_ammo) ? "full" : "empty"]"
/obj/item/ammo_box/magazine/toy/smg/riot
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/foam_dart/riot
/obj/item/ammo_box/magazine/toy/pistol
name = "foam force pistol magazine"
@@ -24,14 +24,14 @@
multiple_sprites = AMMO_BOX_FULL_EMPTY
/obj/item/ammo_box/magazine/toy/pistol/riot
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/foam_dart/riot
/obj/item/ammo_box/magazine/toy/smgm45
name = "donksoft SMG magazine"
icon_state = "c20r45-toy"
base_icon_state = "c20r45"
caliber = CALIBER_FOAM
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/foam_dart
max_ammo = 20
/obj/item/ammo_box/magazine/toy/smgm45/update_icon_state()
@@ -40,13 +40,13 @@
/obj/item/ammo_box/magazine/toy/smgm45/riot
icon_state = "c20r45-riot"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/foam_dart/riot
/obj/item/ammo_box/magazine/toy/m762
name = "donksoft box magazine"
icon_state = "a762-toy"
base_icon_state = "a762"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/foam_dart
max_ammo = 50
/obj/item/ammo_box/magazine/toy/m762/update_icon_state()
@@ -55,4 +55,4 @@
/obj/item/ammo_box/magazine/toy/m762/riot
icon_state = "a762-riot"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
+ ammo_type = /obj/item/ammo_casing/foam_dart/riot
diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
index f1d08dddcd5b0..3b218d4b75933 100644
--- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
@@ -4,33 +4,39 @@
caliber = CALIBER_357
max_ammo = 7
-/obj/item/ammo_box/magazine/internal/cylinder/get_round(keep = 0)
- rotate()
+///Here, we have to maintain the list size, to emulate a cylinder with several chambers, empty or otherwise.
+/obj/item/ammo_box/magazine/internal/cylinder/remove_from_stored_ammo(atom/movable/gone)
+ for(var/index in 1 to length(stored_ammo))
+ var/obj/item/ammo_casing/bullet = stored_ammo[index]
+ if(gone == bullet)
+ stored_ammo[index] = null
+ update_appearance()
+ return
- var/b = stored_ammo[1]
- if(!keep)
- stored_ammo[1] = null
+/obj/item/ammo_box/magazine/internal/cylinder/get_round()
+ rotate()
+ var/casing = stored_ammo[1]
+ if (ispath(casing))
+ casing = new casing(src)
+ stored_ammo[1] = casing
+ return casing
- return b
+/obj/item/ammo_box/magazine/internal/cylinder/get_and_shuffle_round()
+ return get_round()
/obj/item/ammo_box/magazine/internal/cylinder/proc/rotate()
var/b = stored_ammo[1]
stored_ammo.Cut(1,2)
- stored_ammo.Add(b)
+ stored_ammo.Insert(0, b)
/obj/item/ammo_box/magazine/internal/cylinder/proc/spin()
for(var/i in 1 to rand(0, max_ammo*2))
rotate()
-/obj/item/ammo_box/magazine/internal/cylinder/ammo_list(drop_list = FALSE)
- var/list/L = list()
- for(var/i=1 to stored_ammo.len)
- var/obj/item/ammo_casing/bullet = stored_ammo[i]
- if(bullet)
- L.Add(bullet)
- if(drop_list)//We have to maintain the list size, to emulate a cylinder
- stored_ammo[i] = null
- return L
+/obj/item/ammo_box/magazine/internal/cylinder/ammo_list()
+ var/list/no_nulls_ammo = ..()
+ list_clear_nulls(no_nulls_ammo)
+ return no_nulls_ammo
/obj/item/ammo_box/magazine/internal/cylinder/give_round(obj/item/ammo_casing/R, replace_spent = 0)
if(!R || !(caliber ? (caliber == R.caliber) : (ammo_type == R.type)))
@@ -38,14 +44,15 @@
for(var/i in 1 to stored_ammo.len)
var/obj/item/ammo_casing/bullet = stored_ammo[i]
- if(!bullet || !bullet.loaded_projectile) // found a spent ammo
- stored_ammo[i] = R
- R.forceMove(src)
-
- if(bullet)
- bullet.forceMove(drop_location())
- return TRUE
+ if(bullet && (!istype(bullet) || bullet.loaded_projectile))
+ continue
+ // empty or spent
+ stored_ammo[i] = R
+ R.forceMove(src)
+ if(bullet)
+ bullet.forceMove(drop_location())
+ return TRUE
return FALSE
/obj/item/ammo_box/magazine/internal/cylinder/top_off(load_type, starting=FALSE)
diff --git a/code/modules/projectiles/boxes_magazines/internal/grenade.dm b/code/modules/projectiles/boxes_magazines/internal/grenade.dm
index cc0074f7972aa..821bd5a0e534c 100644
--- a/code/modules/projectiles/boxes_magazines/internal/grenade.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/grenade.dm
@@ -12,7 +12,7 @@
/obj/item/ammo_box/magazine/internal/rocketlauncher
name = "rocket launcher internal magazine"
- ammo_type = /obj/item/ammo_casing/caseless/rocket
+ ammo_type = /obj/item/ammo_casing/rocket
caliber = CALIBER_84MM
max_ammo = 1
diff --git a/code/modules/projectiles/boxes_magazines/internal/revolver.dm b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
index 7b2622bf6289d..e703f1999374d 100644
--- a/code/modules/projectiles/boxes_magazines/internal/revolver.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
@@ -16,10 +16,13 @@
caliber = CALIBER_357
max_ammo = 6
multiload = FALSE
+ start_empty = TRUE
-/obj/item/ammo_box/magazine/internal/rus357/Initialize(mapload)
- stored_ammo += new ammo_type(src)
+/obj/item/ammo_box/magazine/internal/cylinder/rus357/Initialize(mapload)
. = ..()
+ for (var/i in 1 to max_ammo - 1)
+ stored_ammo += new /obj/item/ammo_casing/a357/spent(src)
+ stored_ammo += new /obj/item/ammo_casing/a357(src)
/obj/item/ammo_box/magazine/internal/cylinder/c35sol
ammo_type = /obj/item/ammo_casing/c35sol
diff --git a/code/modules/projectiles/boxes_magazines/internal/rifle.dm b/code/modules/projectiles/boxes_magazines/internal/rifle.dm
index 66d8aec8ce833..df9ab738b262e 100644
--- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm
@@ -31,7 +31,7 @@
/obj/item/ammo_box/magazine/internal/boltaction/harpoon
max_ammo = 1
caliber = CALIBER_HARPOON
- ammo_type = /obj/item/ammo_casing/caseless/harpoon
+ ammo_type = /obj/item/ammo_casing/harpoon
/obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/normal
name = "single round magazine"
diff --git a/code/modules/projectiles/boxes_magazines/internal/toy.dm b/code/modules/projectiles/boxes_magazines/internal/toy.dm
index d369d33d2e6f3..639323f81d86d 100644
--- a/code/modules/projectiles/boxes_magazines/internal/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/toy.dm
@@ -1,5 +1,5 @@
/obj/item/ammo_box/magazine/internal/shot/toy
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
+ ammo_type = /obj/item/ammo_casing/foam_dart
caliber = CALIBER_FOAM
max_ammo = 4
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index d8f6db787d4da..99c751520cf69 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -64,11 +64,7 @@
/// True if a gun dosen't need a pin, mostly used for abstract guns like tentacles and meathooks
var/pinless = FALSE
- var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one.
var/has_manufacturer = TRUE // Set to true by default. This didn't exist until Brad needed to make a new pipe-gun esque weapon.
- var/obj/item/knife/bayonet
- var/knife_x_offset = 0
- var/knife_y_offset = 0
var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite
var/ammo_y_offset = 0
@@ -90,6 +86,7 @@
pin = new pin(src)
add_seclight_point()
+ add_bayonet_point()
if(has_manufacturer)
give_manufacturer_examine()
@@ -97,8 +94,6 @@
/obj/item/gun/Destroy()
if(isobj(pin)) //Can still be the initial path, then we skip
QDEL_NULL(pin)
- if(bayonet)
- QDEL_NULL(bayonet)
if(chambered) //Not all guns are chambered (EMP'ed energy guns etc)
QDEL_NULL(chambered)
if(isatom(suppressed)) //SUPPRESSED IS USED AS BOTH A TRUE/FALSE AND AS A REF, WHAT THE FUCKKKKKKKKKKKKKKKKK
@@ -121,14 +116,16 @@
/obj/item/gun/proc/add_seclight_point()
return
+/// Similarly to add_seclight_point(), handles [the bayonet attachment component][/datum/component/bayonet_attachable]
+/obj/item/gun/proc/add_bayonet_point()
+ return
+
/obj/item/gun/handle_atom_del(atom/A)
if(A == pin)
pin = null
if(A == chambered)
chambered = null
update_appearance()
- if(A == bayonet)
- clear_bayonet()
if(A == suppressed)
clear_suppressor()
return ..()
@@ -152,13 +149,6 @@
else
. += "It doesn't have a firing pin installed, and won't fire."
- if(bayonet)
- . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it."
- if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent.
- . += span_info("[bayonet] looks like it can be unscrewed from [src].")
- if(can_bayonet)
- . += "It has a bayonet lug on it."
-
//called after the gun has successfully fired its chambered ammo.
/obj/item/gun/proc/process_chamber(mob/living/user, empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
handle_chamber(user, empty_chamber, from_firing, chamber_next_round)
@@ -286,31 +276,6 @@
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
-/obj/item/gun/pre_attack(atom/A, mob/living/user, params)
- . = ..()
- if(.)
- return .
- if(isnull(bayonet) || !(user.istate & ISTATE_HARM))
- return .
- return bayonet.melee_attack_chain(user, A, params)
-
-/obj/item/gun/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- if(user.istate & ISTATE_HARM)
- return NONE
-
- if(istype(tool, /obj/item/knife))
- var/obj/item/knife/new_stabber = tool
- if(!can_bayonet || !new_stabber.bayonet || !isnull(bayonet)) //ensure the gun has an attachment point available, and that the knife is compatible with it.
- return ITEM_INTERACT_BLOCKING
- if(!user.transferItemToLoc(new_stabber, src))
- return ITEM_INTERACT_BLOCKING
- to_chat(user, span_notice("You attach [new_stabber] to [src]'s bayonet lug."))
- bayonet = new_stabber
- update_appearance()
- return ITEM_INTERACT_SUCCESS
-
- return NONE
-
/obj/item/gun/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(ismob(interacting_with))
if(try_fire_gun(interacting_with, user, list2params(modifiers)))
@@ -561,10 +526,12 @@
else
shoot_with_empty_chamber(user)
return
- process_chamber(user = user)
- update_appearance()
- semicd = TRUE
- addtimer(CALLBACK(src, PROC_REF(reset_semicd)), modified_delay)
+ // If gun gets destroyed as a result of firing
+ if (!QDELETED(src))
+ process_chamber()
+ update_appearance()
+ semicd = TRUE
+ addtimer(CALLBACK(src, PROC_REF(reset_semicd)), modified_delay)
if(user)
user.update_held_items()
@@ -582,10 +549,7 @@
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
- if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed
- return remove_bayonet(user, I)
-
- else if(pin?.pin_removable && user.is_holding(src))
+ if(pin?.pin_removable && user.is_holding(src))
user.visible_message(span_warning("[user] attempts to remove [pin] from [src] with [I]."),
span_notice("You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)"), null, 3)
if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50))
@@ -630,36 +594,6 @@
QDEL_NULL(pin)
return TRUE
-/obj/item/gun/proc/remove_bayonet(mob/living/user, obj/item/tool_item)
- tool_item?.play_tool_sound(src)
- to_chat(user, span_notice("You unfix [bayonet] from [src]."))
- bayonet.forceMove(drop_location())
-
- if(Adjacent(user) && !issilicon(user))
- user.put_in_hands(bayonet)
-
- return clear_bayonet()
-
-/obj/item/gun/proc/clear_bayonet()
- if(!bayonet)
- return
- bayonet = null
- update_appearance()
- return TRUE
-
-/obj/item/gun/update_overlays()
- . = ..()
- if(bayonet)
- var/mutable_appearance/knife_overlay
- var/state = "bayonet" //Generic state.
- if(icon_exists('icons/obj/weapons/guns/bayonets.dmi', bayonet.icon_state)) //Snowflake state? //MONKESTATION EDIT - Refactored to `icon_exists`.
- state = bayonet.icon_state
- var/icon/bayonet_icons = 'icons/obj/weapons/guns/bayonets.dmi'
- knife_overlay = mutable_appearance(bayonet_icons, state)
- knife_overlay.pixel_x = knife_x_offset
- knife_overlay.pixel_y = knife_y_offset
- . += knife_overlay
-
/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer)
if(!ishuman(user) || !ishuman(target))
return
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 8981e445323de..9100b02a01c1d 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -308,21 +308,28 @@
casing.bounce_away(bounce_angle = bounce_angle, still_warm = TRUE)
SEND_SIGNAL(casing, COMSIG_CASING_EJECTED)
else if(empty_chamber)
- chambered = null
+ clear_chambered()
if (chamber_next_round && (magazine?.max_ammo > 1))
chamber_round()
///Used to chamber a new round and eject the old one
-/obj/item/gun/ballistic/proc/chamber_round(keep_bullet = FALSE, spin_cylinder, replace_new_round)
+/obj/item/gun/ballistic/proc/chamber_round(spin_cylinder, replace_new_round)
if (chambered || !magazine)
return
if (magazine.ammo_count())
- chambered = magazine.get_round(keep_bullet || bolt_type == BOLT_TYPE_NO_BOLT)
- if (bolt_type != BOLT_TYPE_OPEN)
+ chambered = (bolt_type == BOLT_TYPE_OPEN && !bolt_locked) || bolt_type == BOLT_TYPE_NO_BOLT ? magazine.get_and_shuffle_round() : magazine.get_round()
+ if (bolt_type != BOLT_TYPE_OPEN && !(internal_magazine && bolt_type == BOLT_TYPE_NO_BOLT))
chambered.forceMove(src)
+ else
+ RegisterSignal(chambered, COMSIG_MOVABLE_MOVED, PROC_REF(clear_chambered))
if(replace_new_round)
magazine.give_round(new chambered.type)
+/obj/item/gun/ballistic/proc/clear_chambered(datum/source)
+ SIGNAL_HANDLER
+ UnregisterSignal(chambered, COMSIG_MOVABLE_MOVED)
+ chambered = null
+
///updates a bunch of racking related stuff and also handles the sound effects and the like
/obj/item/gun/ballistic/proc/rack(mob/user = null)
if (bolt_type == BOLT_TYPE_NO_BOLT) //If there's no bolt, nothing to rack
@@ -398,59 +405,71 @@
/obj/item/gun/ballistic/can_shoot()
return chambered?.loaded_projectile
-/obj/item/gun/ballistic/attackby(obj/item/A, mob/user, params)
+/obj/item/gun/ballistic/attackby(obj/item/tool, mob/user, params)
. = ..()
if (.)
return
- if (!internal_magazine && istype(A, /obj/item/ammo_box/magazine))
- var/obj/item/ammo_box/magazine/AM = A
+ if (!internal_magazine && istype(tool, /obj/item/ammo_box/magazine))
if (!magazine)
- insert_magazine(user, AM)
+ insert_magazine(user, tool)
else
if (tac_reloads)
- eject_magazine(user, FALSE, AM)
+ eject_magazine(user, FALSE, tool)
else
balloon_alert(user, "already loaded!")
return
- if (isammocasing(A) || istype(A, /obj/item/ammo_box))
+
+ if (isammocasing(tool) || istype(tool, /obj/item/ammo_box))
if (bolt_type == BOLT_TYPE_NO_BOLT || internal_magazine)
- if (chambered && !chambered.loaded_projectile)
- chambered.forceMove(drop_location())
- chambered = null
- var/num_loaded = magazine?.attackby(A, user, params, TRUE)
- if (num_loaded)
- balloon_alert(user, "[num_loaded] [cartridge_wording]\s loaded")
- playsound(src, load_sound, load_sound_volume, load_sound_vary)
- if (chambered == null && bolt_type == BOLT_TYPE_NO_BOLT)
- chamber_round()
- A.update_appearance()
- update_appearance()
+ if (load_gun(tool, user))
+ return
return
- if(istype(A, /obj/item/suppressor))
- var/obj/item/suppressor/S = A
+
+ if(istype(tool, /obj/item/suppressor))
if(!can_suppress)
- balloon_alert(user, "[S.name] doesn't fit!")
+ balloon_alert(user, "[tool.name] doesn't fit!")
return
+
if(!user.is_holding(src))
balloon_alert(user, "not in hand!")
return
+
if(suppressed)
balloon_alert(user, "already has a supressor!")
return
- if(user.transferItemToLoc(A, src))
- balloon_alert(user, "[S.name] attached")
- install_suppressor(A)
- return
- if (can_be_sawn_off)
- if (sawoff(user, A))
- return
- if(can_misfire && istype(A, /obj/item/stack/sheet/cloth))
- if(guncleaning(user, A))
+ if(!user.transferItemToLoc(tool, src))
+ balloon_alert(user, "cannot attach!")
return
+ balloon_alert(user, "[tool.name] attached")
+ install_suppressor(tool)
+ return
+
+ if (can_be_sawn_off && sawoff(user, tool))
+ return
+
return FALSE
+/obj/item/gun/ballistic/proc/load_gun(obj/item/ammo, mob/living/user)
+ if (chambered && !chambered.loaded_projectile)
+ chambered.forceMove(drop_location())
+ if(length(magazine?.stored_ammo) && chambered != magazine.stored_ammo[1])
+ magazine.stored_ammo -= chambered
+ chambered = null
+
+ var/num_loaded = magazine?.try_load(user, ammo, silent = TRUE)
+ if (!num_loaded)
+ return FALSE
+
+ balloon_alert(user, "[num_loaded] [cartridge_wording]\s loaded")
+ playsound(src, load_sound, load_sound_volume, load_sound_vary)
+ if (chambered == null && bolt_type == BOLT_TYPE_NO_BOLT)
+ chamber_round()
+ ammo.update_appearance()
+ update_appearance()
+ return TRUE
+
/obj/item/gun/ballistic/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
if(can_misfire && magazine && chambered?.loaded_projectile && misfire_probability > 0) //monke edit : moved can_misfire from the third spot to the first to cancel some runtime issues
if(prob(misfire_probability))
@@ -459,9 +478,12 @@
if (sawn_off)
bonus_spread += SAWN_OFF_ACC_PENALTY
+
return ..()
/obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
+ if(isnull(chambered))
+ return ..()
if(can_misfire)
misfire_probability += misfire_percentage_increment
misfire_probability = clamp(misfire_probability, 0, misfire_probability_cap)
@@ -543,25 +565,7 @@
eject_magazine(user)
return
if(bolt_type == BOLT_TYPE_NO_BOLT)
- chambered = null
- var/num_unloaded = 0
- for(var/obj/item/ammo_casing/casing in get_ammo_list(FALSE, TRUE))
- casing.forceMove(drop_location())
- var/bounce_angle
- if(user)
- var/sign_x = (istype(user) && !(user.get_held_index_of_item(src) % RIGHT_HANDS)) ? 1 : -1
- bounce_angle = SIMPLIFY_DEGREES(dir2angle(user.dir) + (sign_x * 90) + rand(-45, 45))
- casing.bounce_away(bounce_angle = bounce_angle, still_warm = FALSE, sound_delay = 0)
- num_unloaded++
- var/turf/our_turf = get_turf(drop_location())
- if(our_turf && is_station_level(our_turf.z))
- SSblackbox.record_feedback("tally", "station_mess_created", 1, casing.name)
- if (num_unloaded)
- balloon_alert(user, "[num_unloaded] [cartridge_wording] unloaded")
- playsound(user, eject_sound, eject_sound_volume, eject_sound_vary)
- update_appearance()
- else
- balloon_alert(user, "it's empty!")
+ unload_ammo(user)
return
if(bolt_type == BOLT_TYPE_LOCKING && bolt_locked)
drop_bolt(user)
@@ -572,6 +576,25 @@
rack(user)
return
+/obj/item/gun/ballistic/proc/unload_ammo(mob/living/user, forced = FALSE)
+ var/num_unloaded = 0
+ var/turf/drop_turf = get_turf(drop_location())
+ for(var/obj/item/ammo_casing/casing as anything in get_ammo_list(FALSE))
+ casing.forceMove(drop_location())
+ casing.bounce_away(FALSE, NONE)
+ num_unloaded++
+ if(drop_turf && is_station_level(drop_turf.z))
+ SSblackbox.record_feedback("tally", "station_mess_created", 1, casing.name)
+
+ if (!num_unloaded)
+ if (!forced)
+ balloon_alert(user, "it's empty!")
+ return
+
+ if (!forced)
+ balloon_alert(user, "[num_unloaded] [cartridge_wording]\s unloaded")
+ playsound(user, eject_sound, eject_sound_volume, eject_sound_vary)
+ update_appearance()
/obj/item/gun/ballistic/examine(mob/user)
. = ..()
@@ -651,8 +674,7 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
if(sawn_off)
balloon_alert(user, "it's already shortened!")
return
- if(bayonet)
- balloon_alert(user, "[bayonet.name] must be removed!")
+ if (SEND_SIGNAL(src, COMSIG_GUN_BEING_SAWNOFF, user) & COMPONENT_CANCEL_SAWING_OFF)
return
user.changeNext_move(CLICK_CD_MELEE)
user.visible_message(span_notice("[user] begins to shorten [src]."), span_notice("You begin to shorten [src]..."))
@@ -662,27 +684,30 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
user.visible_message(span_danger("[src] goes off!"), span_danger("[src] goes off in your face!"))
return
- if(do_after(user, 3 SECONDS, target = src))
- if(sawn_off)
- return
- user.visible_message(span_notice("[user] shortens [src]!"), span_notice("You shorten [src]."))
- sawn_off = TRUE
- if(handle_modifications)
- name = "sawn-off [src.name]"
- desc = sawn_desc
- w_class = WEIGHT_CLASS_NORMAL
- //The file might not have a "gun" icon, let's prepare for this
- lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
- inhand_x_dimension = 32
- inhand_y_dimension = 32
- inhand_icon_state = "gun"
- worn_icon_state = "gun"
- slot_flags &= ~ITEM_SLOT_BACK //you can't sling it on your back
- slot_flags |= ITEM_SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally)
- recoil = SAWN_OFF_RECOIL
- update_appearance()
+ if(!do_after(user, 3 SECONDS, target = src))
+ return
+ if(sawn_off)
+ return
+ user.visible_message(span_notice("[user] shortens [src]!"), span_notice("You shorten [src]."))
+ sawn_off = TRUE
+ SEND_SIGNAL(src, COMSIG_GUN_SAWN_OFF)
+ if(!handle_modifications)
return TRUE
+ name = "sawn-off [src.name]"
+ desc = sawn_desc
+ update_weight_class(WEIGHT_CLASS_NORMAL)
+ //The file might not have a "gun" icon, let's prepare for this
+ lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
+ inhand_x_dimension = 32
+ inhand_y_dimension = 32
+ inhand_icon_state = "gun"
+ worn_icon_state = "gun"
+ slot_flags &= ~ITEM_SLOT_BACK //you can't sling it on your back
+ slot_flags |= ITEM_SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally)
+ recoil = SAWN_OFF_RECOIL
+ update_appearance()
+ return TRUE
/obj/item/gun/ballistic/proc/guncleaning(mob/user, obj/item/A)
if(misfire_probability == 0)
@@ -741,11 +766,7 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
///used for sawing guns, causes the gun to fire without the input of the user
/obj/item/gun/ballistic/proc/blow_up(mob/user)
- . = FALSE
- for(var/obj/item/ammo_casing/AC in magazine.stored_ammo)
- if(AC.loaded_projectile)
- process_fire(user, user, FALSE)
- . = TRUE
+ return chambered && process_fire(user, user, FALSE)
/obj/item/gun/ballistic/proc/instant_reload()
SIGNAL_HANDLER
@@ -758,13 +779,6 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
chamber_round()
update_appearance()
-// monkestation edit start
-/obj/item/gun/ballistic/handle_atom_del(atom/A)
- if (istype(A, /obj/item/ammo_casing) && magazine)
- magazine.handle_atom_del(A)
- return ..()
-// monkestation edit end
-
/obj/item/suppressor
name = "suppressor"
desc = "A syndicate small-arms suppressor for maximum espionage."
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index b0014e987f6ac..2437e5459f865 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -79,13 +79,13 @@
fire_delay = 2
burst_size = 3
pin = /obj/item/firing_pin/implant/pindicate
- can_bayonet = TRUE
- knife_x_offset = 26
- knife_y_offset = 12
mag_display = TRUE
mag_display_ammo = TRUE
empty_indicator = TRUE
+/obj/item/gun/ballistic/automatic/c20r/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 26, offset_y = 12)
+
/obj/item/gun/ballistic/automatic/c20r/update_overlays()
. = ..()
if(!chambered && empty_indicator) //this is duplicated due to a layering issue with the select fire icon.
@@ -113,13 +113,13 @@
can_suppress = FALSE
burst_size = 1
actions_types = list()
- can_bayonet = TRUE
- knife_x_offset = 25
- knife_y_offset = 12
mag_display = TRUE
mag_display_ammo = TRUE
empty_indicator = TRUE
+/obj/item/gun/ballistic/automatic/wt550/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 25, offset_y = 12)
+
/obj/item/gun/ballistic/automatic/wt550/Initialize(mapload)
. = ..()
AddComponent(/datum/component/automatic_fire, 0.3 SECONDS)
@@ -322,7 +322,6 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/c585trappiste_pistol
fire_sound = 'monkestation/code/modules/blueshift/sounds/smg_heavy.ogg'
can_suppress = TRUE
- can_bayonet = FALSE
suppressor_x_offset = 9
burst_size = 2
fire_delay = 0.5 SECONDS
@@ -379,7 +378,6 @@
can_suppress = TRUE
suppressor_x_offset = 0
suppressor_y_offset = 0
- can_bayonet = FALSE
burst_size = 1
fire_delay = 0.2 SECONDS
actions_types = list()
@@ -429,7 +427,6 @@
spawn_magazine_type = /obj/item/ammo_box/magazine/c35sol_pistol/stendo
fire_sound = 'monkestation/code/modules/blueshift/sounds/smg_light.ogg'
can_suppress = TRUE
- can_bayonet = FALSE
suppressor_x_offset = 11
burst_size = 2
fire_delay = 0.35 SECONDS
@@ -650,7 +647,6 @@
fire_sound = 'monkestation/code/modules/blueshift/sounds/rifle_heavy.ogg'
suppressed_sound = 'monkestation/code/modules/blueshift/sounds/suppressed_rifle.ogg'
can_suppress = TRUE
- can_bayonet = FALSE
suppressor_x_offset = 12
burst_size = 1
fire_delay = 0.4 SECONDS
@@ -816,7 +812,6 @@
fire_sound = 'monkestation/code/modules/blueshift/sounds/rifle_heavy.ogg'
suppressed_sound = 'monkestation/code/modules/blueshift/sounds/suppressed_rifle.ogg'
can_suppress = TRUE
- can_bayonet = FALSE
suppressor_x_offset = 12
actions_types = list()
burst_size = 1
@@ -967,7 +962,6 @@
can_suppress = TRUE
suppressor_x_offset = 0
suppressor_y_offset = 0
- can_bayonet = FALSE
burst_size = 1
fire_delay = 1.2 SECONDS
actions_types = list()
@@ -1026,7 +1020,6 @@
slot_flags = ITEM_SLOT_BACK
accepted_magazine_type = /obj/item/ammo_box/magazine/wylom
can_suppress = FALSE
- can_bayonet = FALSE
fire_sound = 'monkestation/code/modules/blueshift/sounds/amr_fire.ogg'
fire_sound_volume = 100 // BOOM BABY
recoil = 4
diff --git a/code/modules/projectiles/guns/ballistic/bows/_bow.dm b/code/modules/projectiles/guns/ballistic/bows/_bow.dm
index 5bcb919191cf9..a007834a211e9 100644
--- a/code/modules/projectiles/guns/ballistic/bows/_bow.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/_bow.dm
@@ -32,7 +32,7 @@
drawn = FALSE
if(chambered)
chambered.forceMove(drop_location())
- magazine.get_round(keep = FALSE)
+ magazine.get_round()
chambered = null
to_chat(user, span_warning("Without drawing the bow, the arrow uselessly falls to the ground."))
update_appearance()
@@ -41,7 +41,7 @@
if(chambered || !magazine)
return
if(magazine.ammo_count())
- chambered = magazine.get_round(TRUE)
+ chambered = magazine.get_round()
chambered.forceMove(src)
/obj/item/gun/ballistic/bow/attack_self(mob/user)
@@ -75,7 +75,7 @@
/obj/item/ammo_box/magazine/internal/bow
name = "bowstring"
- ammo_type = /obj/item/ammo_casing/caseless/arrow
+ ammo_type = /obj/item/ammo_casing/arrow
max_ammo = 1
start_empty = TRUE
caliber = CALIBER_ARROW
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
index 764657f12b01c..15727786384b2 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
@@ -1,33 +1,29 @@
///base arrow
-/obj/item/ammo_casing/caseless/arrow
+/obj/item/ammo_casing/arrow
name = "arrow"
desc = "Stabby Stabman!"
icon = 'icons/obj/weapons/bows/arrows.dmi'
icon_state = "arrow"
+ base_icon_state = "arrow"
inhand_icon_state = "arrow"
- projectile_type = /obj/projectile/bullet/reusable/arrow
+ projectile_type = /obj/projectile/bullet/arrow
flags_1 = NONE
throwforce = 1
firing_effect_type = null
caliber = CALIBER_ARROW
- heavy_metal = FALSE
+ ///Whether the bullet type spawns another casing of the same type or not.
+ var/reusable = TRUE
-/obj/item/ammo_casing/caseless/arrow/Initialize(mapload)
+/obj/item/ammo_casing/arrow/Initialize(mapload)
. = ..()
- AddComponent(/datum/element/envenomable_casing)
+ AddElement(/datum/element/envenomable_casing)
+ AddElement(/datum/element/caseless, reusable)
-///base arrow projectile
-/obj/projectile/bullet/reusable/arrow
- name = "arrow"
- desc = "Ow! Get it out of me!"
- icon = 'icons/obj/weapons/bows/arrows.dmi'
- icon_state = "arrow_projectile"
- ammo_type = /obj/item/ammo_casing/caseless/arrow
- damage = 50
- speed = 1
- range = 25
+/obj/item/ammo_casing/arrow/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]"
-///*sigh* NON-REUSABLE base arrow projectile. In the future: let's componentize the reusable subtype, jesus
+///base arrow projectile
/obj/projectile/bullet/arrow
name = "arrow"
desc = "Ow! Get it out of me!"
@@ -36,49 +32,64 @@
damage = 50
speed = 1
range = 25
+ shrapnel_type = null
+ embed_type = /datum/embedding/arrow
-/// despawning arrow type
-/obj/item/ammo_casing/caseless/arrow/despawning/dropped()
- . = ..()
- addtimer(CALLBACK(src, PROC_REF(floor_vanish)), 5 SECONDS)
-
-/obj/item/ammo_casing/caseless/arrow/despawning/proc/floor_vanish()
- if(isturf(loc))
- qdel(src)
+/datum/embedding/arrow
+ embed_chance = 90
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.5
+ pain_mult = 3
+ jostle_pain_mult = 3
+ rip_time = 1 SECONDS
/// holy arrows
-/obj/item/ammo_casing/caseless/arrow/holy
+/obj/item/ammo_casing/arrow/holy
name = "holy arrow"
desc = "A holy diver seeking its target."
icon_state = "holy_arrow"
inhand_icon_state = "holy_arrow"
- projectile_type = /obj/projectile/bullet/reusable/arrow/holy
+ base_icon_state = "holy_arrow"
+ projectile_type = /obj/projectile/bullet/arrow/holy
/// holy arrow projectile
-/obj/projectile/bullet/reusable/arrow/holy
+/obj/projectile/bullet/arrow/holy
name = "holy arrow"
desc = "Here it comes, cultist scum!"
icon_state = "holy_arrow_projectile"
- ammo_type = /obj/item/ammo_casing/caseless/arrow/holy
damage = 20 //still a lot but this is roundstart gear so far less
+ embed_type = /datum/embedding/arrow/holy
+
+/datum/embedding/arrow/holy
+ embed_chance = 50
+ fall_chance = 2
+ jostle_chance = 0
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.5
+ pain_mult = 3
+ rip_time = 1 SECONDS
-/obj/projectile/bullet/reusable/arrow/holy/Initialize(mapload)
+/obj/projectile/bullet/arrow/holy/Initialize(mapload)
. = ..()
//50 damage to revenants
AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 30)
/// special pyre sect arrow
/// in the future, this needs a special sprite, but bows don't support non-hardcoded arrow sprites
-/obj/item/ammo_casing/caseless/arrow/holy/blazing
+/obj/item/ammo_casing/arrow/holy/blazing
name = "blazing star arrow"
desc = "A holy diver seeking its target, blessed with fire. Will ignite on hit, destroying the arrow. But if you hit an already ignited target...?"
projectile_type = /obj/projectile/bullet/arrow/blazing
+ reusable = FALSE
/obj/projectile/bullet/arrow/blazing
name = "blazing arrow"
desc = "THE UNMATCHED POWER OF THE SUN"
icon_state = "holy_arrow_projectile"
damage = 20
+ embed_type = null
/obj/projectile/bullet/arrow/blazing/on_hit(atom/target, blocked, pierce_hit)
. = ..()
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
index 21268c4f83bbd..7d8669c8618ae 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
@@ -7,15 +7,16 @@
inhand_icon_state = null
worn_icon_state = "harpoon_quiver"
/// type of arrow the quivel should hold
- var/arrow_path = /obj/item/ammo_casing/caseless/arrow
+ var/arrow_path = /obj/item/ammo_casing/arrow
/obj/item/storage/bag/quiver/Initialize(mapload)
. = ..()
+ atom_storage.numerical_stacking = TRUE
atom_storage.max_specific_storage = WEIGHT_CLASS_TINY
atom_storage.max_slots = 40
atom_storage.max_total_storage = 100
atom_storage.set_holdable(list(
- /obj/item/ammo_casing/caseless/arrow,
+ /obj/item/ammo_casing/arrow,
))
/obj/item/storage/bag/quiver/PopulateContents()
@@ -23,13 +24,10 @@
for(var/i in 1 to 10)
new arrow_path(src)
-/obj/item/storage/bag/quiver/despawning
- arrow_path = /obj/item/ammo_casing/caseless/arrow/despawning
-
/obj/item/storage/bag/quiver/holy
name = "divine quiver"
desc = "Holds arrows for your divine bow, where they wait to find their target."
icon_state = "holyquiver"
inhand_icon_state = "holyquiver"
worn_icon_state = "holyquiver"
- arrow_path = /obj/item/ammo_casing/caseless/arrow/holy
+ arrow_path = /obj/item/ammo_casing/arrow/holy
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
index b1084e8da6dfe..03fc10ccc2fbb 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_types.dm
@@ -17,7 +17,7 @@
/obj/item/ammo_box/magazine/internal/bow/holy
name = "divine bowstring"
- ammo_type = /obj/item/ammo_casing/caseless/arrow/holy
+ ammo_type = /obj/item/ammo_casing/arrow/holy
/obj/item/gun/ballistic/bow/divine/Initialize(mapload)
. = ..()
diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm
index e938e08e8c640..921e48f0ff90c 100644
--- a/code/modules/projectiles/guns/ballistic/launchers.dm
+++ b/code/modules/projectiles/guns/ballistic/launchers.dm
@@ -84,7 +84,7 @@
/obj/item/gun/ballistic/rocketlauncher/afterattack()
. = ..()
- magazine.get_round(FALSE) //Hack to clear the mag after it's fired
+ magazine.get_round() //Hack to clear the mag after it's fired
/obj/item/gun/ballistic/rocketlauncher/attack_self_tk(mob/user)
return //too difficult to remove the rocket with TK
@@ -135,7 +135,6 @@
accepted_magazine_type = /obj/item/ammo_box/magazine/c980_grenade
fire_sound = 'monkestation/code/modules/blueshift/sounds/grenade_launcher.ogg'
can_suppress = FALSE
- can_bayonet = FALSE
burst_size = 1
fire_delay = 5
actions_types = list()
diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
index 402c12a84a530..c6d5b733e3434 100644
--- a/code/modules/projectiles/guns/ballistic/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -379,7 +379,6 @@
w_class = WEIGHT_CLASS_NORMAL
slot_flags = ITEM_SLOT_BELT
accepted_magazine_type = /obj/item/ammo_box/magazine/whispering_jester_45_magazine
- can_bayonet = FALSE
can_suppress = FALSE
can_unsuppress = FALSE
suppressed = TRUE
@@ -405,7 +404,6 @@
w_class = WEIGHT_CLASS_NORMAL
slot_flags = ITEM_SLOT_BELT
accepted_magazine_type = /obj/item/ammo_box/magazine/whispering_jester_45_magazine/big_lmao
- can_bayonet = FALSE
can_suppress = FALSE
can_unsuppress = TRUE
suppressed = FALSE
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index eb20448808795..632cd29841034 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -15,21 +15,26 @@
var/spin_delay = 10
var/recent_spin = 0
var/last_fire = 0
- gun_flags = GUN_SMOKE_PARTICLES
- box_reload_delay = CLICK_CD_RAPID // honestly this is negligible because of the inherent delay of having to switch hands
/obj/item/gun/ballistic/revolver/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread)
- ..()
- last_fire = world.time
-
+ . = ..()
+ if(.)
+ last_fire = world.time
-/obj/item/gun/ballistic/revolver/chamber_round(keep_bullet, spin_cylinder = TRUE, replace_new_round)
+/obj/item/gun/ballistic/revolver/chamber_round(spin_cylinder = TRUE, replace_new_round)
if(!magazine) //if it mag was qdel'd somehow.
CRASH("revolver tried to chamber a round without a magazine!")
- if(spin_cylinder)
- chambered = magazine.get_round(TRUE)
+ if(chambered)
+ UnregisterSignal(chambered, COMSIG_MOVABLE_MOVED)
+ if (spin_cylinder)
+ chambered = magazine.get_round()
else
chambered = magazine.stored_ammo[1]
+ if (ispath(chambered))
+ chambered = new chambered(src)
+ magazine.stored_ammo[1] = chambered
+ if(chambered)
+ RegisterSignal(chambered, COMSIG_MOVABLE_MOVED, PROC_REF(clear_chambered))
/obj/item/gun/ballistic/revolver/shoot_with_empty_chamber(mob/living/user as mob|obj)
..()
@@ -58,9 +63,9 @@
set category = "Object"
set desc = "Click to spin your revolver's chamber."
- var/mob/M = usr
+ var/mob/user = usr
- if(M.stat || !in_range(M,src))
+ if(user.stat || !in_range(user, src))
return
if (recent_spin > world.time)
@@ -69,7 +74,8 @@
if(do_spin())
playsound(usr, SFX_REVOLVER_SPIN, 30, FALSE)
- usr.visible_message(span_notice("[usr] spins [src]'s chamber."), span_notice("You spin [src]'s chamber."))
+ visible_message(span_notice("[user] spins [src]'s chamber."), span_notice("You spin [src]'s chamber."))
+ balloon_alert(user, "chamber spun")
else
verbs -= /obj/item/gun/ballistic/revolver/verb/spin
@@ -92,8 +98,7 @@
. = ..()
var/live_ammo = get_ammo(FALSE, FALSE)
. += "[live_ammo ? live_ammo : "None"] of those are live rounds."
- if (current_skin)
- . += "It can be spun with alt+click"
+ . += span_notice("It can be spun with [EXAMINE_HINT("alt-click")].")
/obj/item/gun/ballistic/revolver/ignition_effect(atom/A, mob/user)
if(last_fire && last_fire + 15 SECONDS > world.time)
diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm
index bf3e888b3bccd..f82543c2dba79 100644
--- a/code/modules/projectiles/guns/ballistic/rifle.dm
+++ b/code/modules/projectiles/guns/ballistic/rifle.dm
@@ -64,9 +64,6 @@
inhand_icon_state = "moistnugget"
slot_flags = ITEM_SLOT_BACK
accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction
- can_bayonet = TRUE
- knife_x_offset = 27
- knife_y_offset = 13
can_be_sawn_off = TRUE
var/jamming_chance = 20
var/unjam_chance = 10
@@ -74,11 +71,13 @@
var/jammed = FALSE
var/can_jam = FALSE
+/obj/item/gun/ballistic/rifle/boltaction/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 32, offset_y = 12)
+
/obj/item/gun/ballistic/rifle/boltaction/sawoff(mob/user)
. = ..()
if(.)
spread = 36
- can_bayonet = FALSE
update_appearance()
/obj/item/gun/ballistic/rifle/boltaction/attack_self(mob/user)
@@ -104,19 +103,20 @@
return ..()
/obj/item/gun/ballistic/rifle/boltaction/attackby(obj/item/item, mob/user, params)
- . = ..()
- if(!can_jam)
- balloon_alert(user, "can't jam!")
- return
-
- if(!bolt_locked)
- balloon_alert(user, "bolt closed!")
- return
-
- if(istype(item, /obj/item/gun_maintenance_supplies) && do_after(user, 10 SECONDS, target = src))
+ if(istype(item, /obj/item/gun_maintenance_supplies))
+ if(!can_jam)
+ balloon_alert(user, "can't jam!")
+ return
+ if(!bolt_locked)
+ balloon_alert(user, "bolt is closed!")
+ return
+ if(!do_after(user, 10 SECONDS, target = src))
+ return
user.visible_message(span_notice("[user] finishes maintenance of [src]."))
jamming_chance = initial(jamming_chance)
qdel(item)
+ return
+ return ..()
/obj/item/gun/ballistic/rifle/boltaction/blow_up(mob/user)
. = FALSE
@@ -179,11 +179,12 @@
alternative_fire_sound = 'sound/weapons/gun/shotgun/shot.ogg'
can_modify_ammo = TRUE
can_misfire = FALSE
- can_bayonet = TRUE
- knife_y_offset = 11
can_be_sawn_off = FALSE
projectile_damage_multiplier = 0.75
+/obj/item/gun/ballistic/rifle/boltaction/pipegun/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_y = 11)
+
/obj/item/gun/ballistic/rifle/boltaction/pipegun/handle_chamber(mob/living/user, empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
. = ..()
do_sparks(1, TRUE, src)
@@ -217,7 +218,6 @@
icon_state = "arcane_barrage"
inhand_icon_state = "arcane_barrage"
slot_flags = null
- can_bayonet = FALSE
item_flags = NEEDS_PERMIT | DROPDEL | ABSTRACT | NOBLUDGEON
flags_1 = NONE
trigger_guard = TRIGGER_GUARD_ALLOW_ALL
@@ -318,7 +318,6 @@
inhand_icon_state = "moistnugget"
accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/bubba
can_be_sawn_off = FALSE
- knife_x_offset = 35
/obj/item/gun/ballistic/rifle/boltaction/sporterized/Initialize(mapload)
. = ..()
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index 5fa46655ed1a3..5f9723ee2b686 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -26,9 +26,7 @@
var/single_shot_type_overlay = TRUE
///Should we give an overlay to empty guns?
var/display_empty = TRUE
- var/selfcharge = 0
- var/charge_timer = 0
- var/charge_delay = 8
+
///whether the gun's cell drains the cyborg user's cell to recharge
var/use_cyborg_cell = FALSE
///amount to multiply the cost by if we're using a cyborg cell.
@@ -36,6 +34,17 @@
///set to true so the gun is given an empty cell
var/dead_cell = FALSE
+ // Self charging vars
+
+ /// Whether or not our gun charges its own cell on a timer.
+ var/selfcharge = 0
+ /// The amount of time between instances of cell self recharge
+ var/charge_timer = 0
+ /// The amount of seconds_per_tick during process() before the gun charges itself
+ var/charge_delay = 8
+ /// The amount restored by the gun to the cell per self charge tick
+ var/self_charge_amount = STANDARD_ENERGY_GUN_SELF_CHARGE_RATE
+
/obj/item/gun/energy/fire_sounds()
// What frequency the energy gun's sound will make
var/frequency_to_use
@@ -153,7 +162,7 @@
if(charge_timer < charge_delay)
return
charge_timer = 0
- cell.give(STANDARD_ENERGY_GUN_SELF_CHARGE_RATE * seconds_per_tick)
+ cell.give(self_charge_amount * seconds_per_tick)
if(!chambered) //if empty chamber we try to charge a new shot
recharge_newshot(TRUE)
update_appearance()
diff --git a/code/modules/projectiles/guns/energy/beam_rifle.dm b/code/modules/projectiles/guns/energy/beam_rifle.dm
index 34d8bc1f2e317..08057caf28f8d 100644
--- a/code/modules/projectiles/guns/energy/beam_rifle.dm
+++ b/code/modules/projectiles/guns/energy/beam_rifle.dm
@@ -1,575 +1,64 @@
-
-#define ZOOM_LOCK_AUTOZOOM_FREEMOVE 0
-#define ZOOM_LOCK_AUTOZOOM_ANGLELOCK 1
-#define ZOOM_LOCK_CENTER_VIEW 2
-#define ZOOM_LOCK_OFF 3
-
-#define AUTOZOOM_PIXEL_STEP_FACTOR 48
-
-#define AIMING_BEAM_ANGLE_CHANGE_THRESHOLD 0.1
-
-/obj/item/gun/energy/beam_rifle
- name = "particle acceleration rifle"
- desc = "An energy-based anti material marksman rifle that uses highly charged particle beams moving at extreme velocities to decimate whatever is unfortunate enough to be targeted by one."
- desc_controls = "Hold down left click while scoped to aim, when weapon is fully aimed (Tracer goes from red to green as it charges), release to fire. Moving while aiming or changing where you're pointing at while aiming will delay the aiming process depending on how much you changed."
+/obj/item/gun/energy/event_horizon
+ name = "\improper Event Horizon anti-existential beam rifle"
+ desc = "The deranged minds of Nanotrasen, in their great hubris and spite, have birthed forth the definitive conclusion to the arms race. Weaponized black holes, and a platform to deliver them.\
+ To look upon this existential maleficence is to know that the pursuit of profit has consigned all life to this pathetic conclusion; the destruction of reality itself."
icon = 'icons/obj/weapons/guns/energy.dmi'
icon_state = "esniper"
inhand_icon_state = null
worn_icon_state = null
fire_sound = 'sound/weapons/beam_sniper.ogg'
slot_flags = ITEM_SLOT_BACK
- force = 15
+ force = 20 //This is maybe the sanest part of this weapon.
custom_materials = null
- recoil = 4
+ recoil = 2
ammo_x_offset = 3
ammo_y_offset = 3
modifystate = FALSE
charge_sections = 1
weapon_weight = WEAPON_HEAVY
w_class = WEIGHT_CLASS_BULKY
- ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan)
- actions_types = list(/datum/action/item_action/zoom_lock_action)
- cell_type = /obj/item/stock_parts/power_store/cell/beam_rifle
- canMouseDown = TRUE
- var/aiming = FALSE
- var/aiming_time = 12
- var/aiming_time_fire_threshold = 5
- var/aiming_time_left = 12
- var/aiming_time_increase_user_movement = 3
- var/scoped_slow = 1
- var/aiming_time_increase_angle_multiplier = 0.3
- var/last_process = 0
-
- var/lastangle = 0
- var/aiming_lastangle = 0
- var/mob/current_user = null
- var/list/obj/effect/projectile/tracer/current_tracers
-
- var/structure_piercing = 2 //Amount * 2. For some reason structures aren't respecting this unless you have it doubled. Probably with the objects in question's Bump() code instead of this but I'll deal with this later.
- var/structure_bleed_coeff = 0.7
- var/wall_pierce_amount = 0
- var/wall_devastate = 0
- var/aoe_structure_range = 1
- var/aoe_structure_damage = 50
- var/aoe_fire_range = 2
- var/aoe_fire_chance = 40
- var/aoe_mob_range = 1
- var/aoe_mob_damage = 30
- var/impact_structure_damage = 60
- var/projectile_damage = 30
- var/projectile_stun = 0
- var/projectile_setting_pierce = TRUE
- var/delay = 25
- var/lastfire = 0
-
- //ZOOMING
- var/zoom_current_view_increase = 0
- ///The radius you want to zoom by
- var/zoom_target_view_increase = 9.5
- var/zooming = FALSE
- var/zoom_lock = ZOOM_LOCK_OFF
- var/zooming_angle
- var/current_zoom_x = 0
- var/current_zoom_y = 0
-
- var/mob/listeningTo
-
-/obj/item/gun/energy/beam_rifle/apply_fantasy_bonuses(bonus)
- . = ..()
- delay = modify_fantasy_variable("delay", delay, -bonus * 2)
- aiming_time = modify_fantasy_variable("aiming_time", aiming_time, -bonus * 2)
- recoil = modify_fantasy_variable("recoil", recoil, round(-bonus / 2))
-
-/obj/item/gun/energy/beam_rifle/remove_fantasy_bonuses(bonus)
- delay = reset_fantasy_variable("delay", delay)
- aiming_time = reset_fantasy_variable("aiming_time", aiming_time)
- recoil = reset_fantasy_variable("recoil", recoil)
- return ..()
-
-/obj/item/gun/energy/beam_rifle/debug
- delay = 0
- cell_type = /obj/item/stock_parts/power_store/cell/infinite
- aiming_time = 0
- recoil = 0
- pin = /obj/item/firing_pin
-
-/obj/item/gun/energy/beam_rifle/equipped(mob/user)
- set_user(user)
- return ..()
-
-/obj/item/gun/energy/beam_rifle/pickup(mob/user)
- set_user(user)
- return ..()
-
-/obj/item/gun/energy/beam_rifle/dropped(mob/user)
- set_user()
- return ..()
-
-/obj/item/gun/energy/beam_rifle/ui_action_click(mob/user, actiontype)
- if(istype(actiontype, /datum/action/item_action/zoom_lock_action))
- zoom_lock++
- if(zoom_lock > 3)
- zoom_lock = 0
- switch(zoom_lock)
- if(ZOOM_LOCK_AUTOZOOM_FREEMOVE)
- to_chat(user, span_boldnotice("You switch [src]'s zooming processor to free directional."))
- if(ZOOM_LOCK_AUTOZOOM_ANGLELOCK)
- to_chat(user, span_boldnotice("You switch [src]'s zooming processor to locked directional."))
- if(ZOOM_LOCK_CENTER_VIEW)
- to_chat(user, span_boldnotice("You switch [src]'s zooming processor to center mode."))
- if(ZOOM_LOCK_OFF)
- to_chat(user, span_boldnotice("You disable [src]'s zooming system."))
- reset_zooming()
- return
+ ammo_type = list(/obj/item/ammo_casing/energy/event_horizon)
+ selfcharge = TRUE
+ self_charge_amount = STANDARD_ENERGY_GUN_SELF_CHARGE_RATE * 10
- return ..()
-
-/obj/item/gun/energy/beam_rifle/proc/set_autozoom_pixel_offsets_immediate(current_angle)
- if(zoom_lock == ZOOM_LOCK_CENTER_VIEW || zoom_lock == ZOOM_LOCK_OFF)
- return
- current_zoom_x = sin(current_angle) + sin(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase
- current_zoom_y = cos(current_angle) + cos(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase
-
-/obj/item/gun/energy/beam_rifle/proc/handle_zooming()
- if(!zooming || !check_user())
- return
- current_user.client.view_size.setTo(zoom_target_view_increase)
- zoom_current_view_increase = zoom_target_view_increase
- set_autozoom_pixel_offsets_immediate(zooming_angle)
-
-/obj/item/gun/energy/beam_rifle/proc/start_zooming()
- if(zoom_lock == ZOOM_LOCK_OFF)
- return
- zooming = TRUE
-
-/obj/item/gun/energy/beam_rifle/proc/stop_zooming(mob/user)
- if(zooming)
- zooming = FALSE
- reset_zooming(user)
-
-/obj/item/gun/energy/beam_rifle/proc/reset_zooming(mob/user)
- if(!user)
- user = current_user
- if(!user || !user.client)
- return FALSE
- user.client.view_size.zoomIn()
- zoom_current_view_increase = 0
- zooming_angle = 0
- current_zoom_x = 0
- current_zoom_y = 0
-
-/obj/item/gun/energy/beam_rifle/attack_self(mob/user)
- projectile_setting_pierce = !projectile_setting_pierce
- balloon_alert(user, "switched to [projectile_setting_pierce ? "pierce":"impact"] mode")
- aiming_beam()
-
-/obj/item/gun/energy/beam_rifle/proc/update_slowdown()
- if(aiming)
- slowdown = scoped_slow
- else
- slowdown = initial(slowdown)
-
-/obj/item/gun/energy/beam_rifle/Initialize(mapload)
- . = ..()
- fire_delay = delay
- current_tracers = list()
-
-/obj/item/gun/energy/beam_rifle/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
- set_user(null)
- QDEL_LIST(current_tracers)
- listeningTo = null
- return ..()
-
-/obj/item/gun/energy/beam_rifle/emp_act(severity)
+/obj/item/gun/energy/event_horizon/Initialize(mapload)
. = ..()
- if(. & EMP_PROTECT_SELF)
- return
- chambered = null
- recharge_newshot()
-
-/obj/item/gun/energy/beam_rifle/proc/aiming_beam(force_update = FALSE)
- var/diff = abs(aiming_lastangle - lastangle)
- if(!check_user())
- return
- if(diff < AIMING_BEAM_ANGLE_CHANGE_THRESHOLD && !force_update)
- return
- aiming_lastangle = lastangle
- var/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new
- P.gun = src
- P.wall_pierce_amount = wall_pierce_amount
- P.structure_pierce_amount = structure_piercing
- P.do_pierce = projectile_setting_pierce
- if(aiming_time)
- var/percent = ((100/aiming_time)*aiming_time_left)
- P.color = rgb(255 * percent,255 * ((100 - percent) / 100),0)
- else
- P.color = rgb(0, 255, 0)
- var/turf/curloc = get_turf(src)
+ AddComponent(/datum/component/scope, range_modifier = 4)
- var/atom/target_atom = current_user.client.mouse_object_ref?.resolve()
- var/turf/targloc = get_turf(target_atom)
- if(!istype(targloc))
- if(!istype(curloc))
- return
- targloc = get_turf_in_angle(lastangle, curloc, 10)
- var/mouse_modifiers = params2list(current_user.client.mouseParams)
- P.preparePixelProjectile(targloc, current_user, mouse_modifiers, 0)
- P.fire(lastangle)
+/obj/item/gun/energy/event_horizon/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread)
-/obj/item/gun/energy/beam_rifle/process()
- if(!aiming)
- last_process = world.time
+ if(!HAS_TRAIT(user, TRAIT_USER_SCOPED))
+ balloon_alert(user, "must be scoped!")
return
- check_user()
- handle_zooming()
- aiming_time_left = max(0, aiming_time_left - (world.time - last_process))
- aiming_beam(TRUE)
- last_process = world.time
-
-/obj/item/gun/energy/beam_rifle/proc/check_user(automatic_cleanup = TRUE)
- if(!istype(current_user) || !isturf(current_user.loc) || !(src in current_user.held_items) || current_user.incapacitated()) //Doesn't work if you're not holding it!
- if(automatic_cleanup)
- stop_aiming()
- set_user(null)
- return FALSE
- return TRUE
-
-/obj/item/gun/energy/beam_rifle/proc/process_aim()
- if(istype(current_user) && current_user.client && current_user.client.mouseParams)
- var/angle = mouse_angle_from_client(current_user.client)
- current_user.setDir(angle2dir_cardinal(angle))
- var/difference = abs(closer_angle_difference(lastangle, angle))
- delay_penalty(difference * aiming_time_increase_angle_multiplier)
- lastangle = angle
-
-/obj/item/gun/energy/beam_rifle/proc/on_mob_move()
- SIGNAL_HANDLER
- check_user()
- if(aiming)
- delay_penalty(aiming_time_increase_user_movement)
- process_aim()
- INVOKE_ASYNC(src, PROC_REF(aiming_beam), TRUE)
-
-/obj/item/gun/energy/beam_rifle/proc/start_aiming()
- aiming_time_left = aiming_time
- aiming = TRUE
- process_aim()
- aiming_beam(TRUE)
- zooming_angle = lastangle
- start_zooming()
-
-/obj/item/gun/energy/beam_rifle/proc/stop_aiming(mob/user)
- set waitfor = FALSE
- aiming_time_left = aiming_time
- aiming = FALSE
- QDEL_LIST(current_tracers)
- stop_zooming(user)
-
-/obj/item/gun/energy/beam_rifle/proc/set_user(mob/user)
- if(user == current_user)
- return
- stop_aiming(current_user)
- if(listeningTo)
- UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
- listeningTo = null
- if(istype(current_user))
- current_user = null
- if(istype(user))
- current_user = user
- RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_mob_move))
- listeningTo = user
- if(current_user)
- if(!(datum_flags & DF_ISPROCESSING))
- last_process = world.time
- START_PROCESSING(SSfastprocess, src)
- else
- STOP_PROCESSING(SSfastprocess, src)
- last_process = world.time
-
-/obj/item/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
- if(aiming)
- process_aim()
- aiming_beam()
- if(zoom_lock == ZOOM_LOCK_AUTOZOOM_FREEMOVE)
- zooming_angle = lastangle
- set_autozoom_pixel_offsets_immediate(zooming_angle)
- return ..()
-
-/obj/item/gun/energy/beam_rifle/onMouseDown(object, location, params, mob/mob)
- if(istype(mob))
- set_user(mob)
- if(istype(object, /atom/movable/screen) && !istype(object, /atom/movable/screen/click_catcher))
- return
- if((object in mob.contents) || (object == mob))
- return
- start_aiming()
- return ..()
-
-/obj/item/gun/energy/beam_rifle/onMouseUp(object, location, params, mob/M)
- if(istype(object, /atom/movable/screen) && !istype(object, /atom/movable/screen/click_catcher))
- return
- process_aim()
- if(aiming_time_left <= aiming_time_fire_threshold && check_user())
- sync_ammo()
- var/atom/target = M.client.mouse_object_ref?.resolve()
- if(target)
- INVOKE_ASYNC(src, PROC_REF(try_fire_gun), target, M.client.mob, M.client.mouseParams, TRUE)
- stop_aiming()
- QDEL_LIST(current_tracers)
- return ..()
-
-/obj/item/gun/energy/beam_rifle/try_fire_gun(atom/target, mob/living/user, params, passthrough = FALSE)
- if(user.Adjacent(target)) //It's adjacent, is the user, or is on the user's person
- if(target in user.contents) //can't shoot stuff inside us.
- return FALSE
- if(!ismob(target) || (user.istate & ISTATE_HARM)) //melee attack
- return FALSE
- if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected)
- return FALSE
- if(!passthrough && (aiming_time > aiming_time_fire_threshold))
- return FALSE
- if(lastfire > world.time + delay)
- return FALSE
- if(!..())
- return FALSE
- lastfire = world.time
- stop_aiming()
- return TRUE
-
-/obj/item/gun/energy/beam_rifle/proc/sync_ammo()
- for(var/obj/item/ammo_casing/energy/beam_rifle/AC in contents)
- AC.sync_stats()
-
-/obj/item/gun/energy/beam_rifle/proc/delay_penalty(amount)
- aiming_time_left = clamp(aiming_time_left + amount, 0, aiming_time)
-/obj/item/ammo_casing/energy/beam_rifle
- name = "particle acceleration lens"
- desc = "Don't look into barrel!"
- var/wall_pierce_amount = 0
- var/wall_devastate = 0
- var/aoe_structure_range = 1
- var/aoe_structure_damage = 30
- var/aoe_fire_range = 2
- var/aoe_fire_chance = 66
- var/aoe_mob_range = 1
- var/aoe_mob_damage = 20
- var/impact_structure_damage = 50
- var/projectile_damage = 40
- var/projectile_stun = 0
- var/structure_piercing = 2
- var/structure_bleed_coeff = 0.7
- var/do_pierce = TRUE
- var/obj/item/gun/energy/beam_rifle/host
-
-/obj/item/ammo_casing/energy/beam_rifle/proc/sync_stats()
- var/obj/item/gun/energy/beam_rifle/BR = loc
- if(!istype(BR))
- stack_trace("Beam rifle syncing error")
- host = BR
- do_pierce = BR.projectile_setting_pierce
- wall_pierce_amount = BR.wall_pierce_amount
- wall_devastate = BR.wall_devastate
- aoe_structure_range = BR.aoe_structure_range
- aoe_structure_damage = BR.aoe_structure_damage
- aoe_fire_range = BR.aoe_fire_range
- aoe_fire_chance = BR.aoe_fire_chance
- aoe_mob_range = BR.aoe_mob_range
- aoe_mob_damage = BR.aoe_mob_damage
- impact_structure_damage = BR.impact_structure_damage
- projectile_damage = BR.projectile_damage
- projectile_stun = BR.projectile_stun
- delay = BR.delay
- structure_piercing = BR.structure_piercing
- structure_bleed_coeff = BR.structure_bleed_coeff
-
-/obj/item/ammo_casing/energy/beam_rifle/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
. = ..()
- var/obj/projectile/beam/beam_rifle/hitscan/HS_BB = loaded_projectile
- if(!istype(HS_BB))
- return
- HS_BB.impact_direct_damage = projectile_damage
- HS_BB.stun = projectile_stun
- HS_BB.impact_structure_damage = impact_structure_damage
- HS_BB.aoe_mob_damage = aoe_mob_damage
- HS_BB.aoe_mob_range = clamp(aoe_mob_range, 0, 15) //Badmin safety lock
- HS_BB.aoe_fire_chance = aoe_fire_chance
- HS_BB.aoe_fire_range = aoe_fire_range
- HS_BB.aoe_structure_damage = aoe_structure_damage
- HS_BB.aoe_structure_range = clamp(aoe_structure_range, 0, 15) //Badmin safety lock
- HS_BB.wall_devastate = wall_devastate
- HS_BB.wall_pierce_amount = wall_pierce_amount
- HS_BB.structure_pierce_amount = structure_piercing
- HS_BB.structure_bleed_coeff = structure_bleed_coeff
- HS_BB.do_pierce = do_pierce
- HS_BB.gun = host
+ message_admins("[ADMIN_LOOKUPFLW(user)] has fired an anti-existential beam at [ADMIN_VERBOSEJMP(user)].")
-/obj/item/ammo_casing/energy/beam_rifle/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread, atom/fired_from)
- var/turf/curloc = get_turf(user)
- if(!istype(curloc) || !loaded_projectile)
- return FALSE
- var/obj/item/gun/energy/beam_rifle/gun = loc
- if(!targloc && gun)
- targloc = get_turf_in_angle(gun.lastangle, curloc, 10)
- else if(!targloc)
- return FALSE
- var/firing_dir
- if(loaded_projectile.firer)
- firing_dir = loaded_projectile.firer.dir
- if(!loaded_projectile.suppressed && firing_effect_type)
- new firing_effect_type(get_turf(src), firing_dir)
- var/modifiers = params2list(params)
- loaded_projectile.preparePixelProjectile(target, user, modifiers, spread)
- loaded_projectile.fire(gun? gun.lastangle : null, null)
- loaded_projectile = null
- return TRUE
-
-/obj/item/ammo_casing/energy/beam_rifle/hitscan
- projectile_type = /obj/projectile/beam/beam_rifle/hitscan
- select_name = "beam"
- e_cost = LASER_SHOTS(5, 50000) // Beam rifle has a custom cell
+/obj/item/ammo_casing/energy/event_horizon
+ projectile_type = /obj/projectile/beam/event_horizon
+ select_name = "doomsday"
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE)
fire_sound = 'sound/weapons/beam_sniper.ogg'
-/obj/projectile/beam/beam_rifle
- name = "particle beam"
- icon = null
+/obj/projectile/beam/event_horizon
+ name = "anti-existential beam"
+ icon_state = null
hitsound = 'sound/effects/explosion3.ogg'
- damage = 0 //Handled manually.
+ damage = 100 // Does it matter?
damage_type = BURN
armor_flag = ENERGY
range = 150
jitter = 20 SECONDS
- var/obj/item/gun/energy/beam_rifle/gun
- var/structure_pierce_amount = 0 //All set to 0 so the gun can manually set them during firing.
- var/structure_bleed_coeff = 0
- var/structure_pierce = 0
- var/do_pierce = TRUE
- var/wall_pierce_amount = 0
- var/wall_pierce = 0
- var/wall_devastate = 0
- var/aoe_structure_range = 0
- var/aoe_structure_damage = 0
- var/aoe_fire_range = 0
- var/aoe_fire_chance = 0
- var/aoe_mob_range = 0
- var/aoe_mob_damage = 0
- var/impact_structure_damage = 0
- var/impact_direct_damage = 0
- var/list/pierced = list()
-
-/obj/projectile/beam/beam_rifle/proc/AOE(turf/epicenter)
- if(!epicenter)
- return
- new /obj/effect/temp_visual/explosion/fast(epicenter)
- for(var/mob/living/L in range(aoe_mob_range, epicenter)) //handle aoe mob damage
- L.adjustFireLoss(aoe_mob_damage)
- to_chat(L, span_userdanger("\The [src] sears you!"))
- for(var/turf/T in RANGE_TURFS(aoe_fire_range, epicenter)) //handle aoe fire
- if(prob(aoe_fire_chance))
- new /obj/effect/hotspot(T)
- for(var/obj/O in range(aoe_structure_range, epicenter))
- if(!isitem(O))
- O.take_damage(aoe_structure_damage * get_damage_coeff(O), BURN, LASER, FALSE)
-
-/obj/projectile/beam/beam_rifle/prehit_pierce(atom/A)
- if(isclosedturf(A) && (wall_pierce < wall_pierce_amount))
- if(prob(wall_devastate))
- if(iswallturf(A))
- var/turf/closed/wall/W = A
- W.dismantle_wall(TRUE, TRUE)
- else
- SSexplosions.medturf += A
- ++wall_pierce
- return PROJECTILE_PIERCE_PHASE // yeah this gun is a snowflakey piece of garbage
- if(isobj(A) && (structure_pierce < structure_pierce_amount))
- ++structure_pierce
- var/obj/O = A
- O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(A), BURN, ENERGY, FALSE)
- return PROJECTILE_PIERCE_PHASE // ditto and this could be refactored to on_hit honestly
- return ..()
-
-/obj/projectile/beam/beam_rifle/proc/get_damage_coeff(atom/target)
- if(istype(target, /obj/machinery/door))
- return 0.4
- if(istype(target, /obj/structure/window))
- return 0.5
- return 1
-
-/obj/projectile/beam/beam_rifle/proc/handle_impact(atom/target)
- if(isobj(target))
- var/obj/O = target
- O.take_damage(impact_structure_damage * get_damage_coeff(target), BURN, LASER, FALSE)
- if(isliving(target))
- var/mob/living/L = target
- L.adjustFireLoss(impact_direct_damage)
- L.emote("scream")
-
-/obj/projectile/beam/beam_rifle/proc/handle_hit(atom/target, piercing_hit = FALSE)
- set waitfor = FALSE
- if(!is_hostile_projectile())
- return FALSE
- playsound(src, 'sound/effects/explosion3.ogg', 100, TRUE)
- if(!do_pierce)
- AOE(get_turf(target) || get_turf(src))
- if(!QDELETED(target))
- handle_impact(target)
-
-/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = 0, pierce_hit)
- handle_hit(target, pierce_hit)
- return ..()
-
-/obj/projectile/beam/beam_rifle/is_hostile_projectile()
- return TRUE // on hit = boom fire
-
-/obj/projectile/beam/beam_rifle/hitscan
- icon_state = ""
hitscan = TRUE
tracer_type = /obj/effect/projectile/tracer/tracer/beam_rifle
- var/constant_tracer = FALSE
-/obj/projectile/beam/beam_rifle/hitscan/generate_hitscan_tracers(cleanup = TRUE, duration = 5, impacting = TRUE, highlander)
- set waitfor = FALSE
- if(isnull(highlander))
- highlander = constant_tracer
- if(highlander && istype(gun))
- QDEL_LIST(gun.current_tracers)
- for(var/datum/point/p in beam_segments)
- gun.current_tracers += generate_tracer_between_points(p, beam_segments[p], tracer_type, color, 0, hitscan_light_outer_range, hitscan_light_color_override, hitscan_light_intensity)
- else
- for(var/datum/point/p in beam_segments)
- generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_outer_range, hitscan_light_color_override, hitscan_light_intensity)
- if(cleanup)
- QDEL_LIST(beam_segments)
- beam_segments = null
- QDEL_NULL(beam_index)
-
-/obj/projectile/beam/beam_rifle/hitscan/aiming_beam
- tracer_type = /obj/effect/projectile/tracer/tracer/aiming
- name = "aiming beam"
- hitsound = null
- hitsound_wall = null
- damage = 0
- constant_tracer = TRUE
- hitscan_light_outer_range = 0
- hitscan_light_intensity = 0
- hitscan_light_color_override = "#99ff99"
- reflectable = REFLECT_FAKEPROJECTILE
-
-/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/is_hostile_projectile()
- return FALSE // just an aiming reticle
-
-/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit_pierce(atom/target)
- return PROJECTILE_DELETE_WITHOUT_HITTING
+/obj/projectile/beam/event_horizon/on_hit(atom/target, blocked, pierce_hit)
+ . = ..()
-/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit(atom/target, blocked = 0, pierce_hit)
- SHOULD_CALL_PARENT(FALSE) // This is some snowflake stuff so whatever
- qdel(src)
- return BULLET_ACT_BLOCK
+ // Where we droppin' boys?
+ var/turf/rift_loc = get_turf(target)
-#undef AIMING_BEAM_ANGLE_CHANGE_THRESHOLD
-#undef AUTOZOOM_PIXEL_STEP_FACTOR
-#undef ZOOM_LOCK_AUTOZOOM_ANGLELOCK
-#undef ZOOM_LOCK_AUTOZOOM_FREEMOVE
-#undef ZOOM_LOCK_CENTER_VIEW
-#undef ZOOM_LOCK_OFF
+ // Spawn our temporary rift, then activate it.
+ var/obj/reality_tear/temporary/tear = new(rift_loc)
+ tear.start_disaster()
+ message_admins("[ADMIN_LOOKUPFLW(target)] has been hit by an anti-existential beam at [ADMIN_VERBOSEJMP(rift_loc)], creating a singularity.")
diff --git a/code/modules/projectiles/guns/energy/crank_guns.dm b/code/modules/projectiles/guns/energy/crank_guns.dm
index e483cfc090fbd..ee70ceeb25493 100644
--- a/code/modules/projectiles/guns/energy/crank_guns.dm
+++ b/code/modules/projectiles/guns/energy/crank_guns.dm
@@ -7,12 +7,12 @@
ammo_type = list(/obj/item/ammo_casing/energy/laser/musket)
slot_flags = ITEM_SLOT_BACK
obj_flags = UNIQUE_RENAME
- can_bayonet = TRUE
- knife_x_offset = 22
- knife_y_offset = 11
//monke edit: fully charges per crank because it was really confusing and unintuitive
//monke edit: increased cooldown time to compensate for increased charge
+/obj/item/gun/energy/laser/musket/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 22, offset_y = 11)
+
/obj/item/gun/energy/laser/musket/Initialize(mapload)
. = ..()
AddComponent( \
@@ -171,9 +171,6 @@
icon_state = "explorer"
ammo_type = list(/obj/item/ammo_casing/energy/laser/explorer)
selfcharge = 1
- can_bayonet = TRUE
- knife_x_offset = 17
- knife_y_offset = 12
/obj/item/gun/energy/laser/explorer/add_seclight_point()
AddComponent(/datum/component/seclite_attachable, \
@@ -181,7 +178,7 @@
light_overlay = "flight", \
overlay_x = 18, \
overlay_y = 8)
-
+
/obj/item/gun/energy/laser/explorer/Initialize(mapload)
. = ..()
AddComponent( \
@@ -199,3 +196,6 @@
/obj/item/gun/energy/laser/explorer/give_manufacturer_examine()
AddElement(/datum/element/manufacturer_examine, COMPANY_REMOVED)
+
+/obj/item/gun/energy/laser/explorer/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 17, offset_y = 12)
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index c399d09f21c42..a68f54bf31595 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -8,15 +8,15 @@
item_flags = NONE
obj_flags = UNIQUE_RENAME
weapon_weight = WEAPON_LIGHT
- can_bayonet = TRUE
- knife_x_offset = 20
- knife_y_offset = 12
var/mob/holder
var/max_mod_capacity = 100
var/list/modkits = list()
gun_flags = NOT_A_REAL_GUN
var/disablemodification = FALSE //monkeedit - stops removal and addition of mods
+/obj/item/gun/energy/recharge/kinetic_accelerator/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 20, offset_y = 12)
+
/obj/item/gun/energy/recharge/kinetic_accelerator/apply_fantasy_bonuses(bonus)
. = ..()
max_mod_capacity = modify_fantasy_variable("max_mod_capacity", max_mod_capacity, bonus * 10)
@@ -147,7 +147,6 @@
hitsound = 'sound/weapons/bladeslice.ogg' // has a blade
holds_charge = TRUE
unique_frequency = TRUE
- can_bayonet = FALSE
sharpness = SHARP_EDGED
force = 15
wound_bonus = 5
@@ -183,7 +182,6 @@
base_icon_state = "kineticpistol"
recharge_time = 2 SECONDS
ammo_type = list(/obj/item/ammo_casing/energy/kinetic/glock)
- can_bayonet = FALSE
max_mod_capacity = 200
@@ -203,7 +201,6 @@
recharge_time = 3 SECONDS
ammo_type = list(/obj/item/ammo_casing/energy/kinetic/railgun)
weapon_weight = WEAPON_HEAVY
- can_bayonet = FALSE
max_mod_capacity = 0 // Fuck off
recoil = 1 // Railgun go brrrrr
disablemodification = TRUE
@@ -235,7 +232,6 @@
base_icon_state = "kineticshockwave"
recharge_time = 2 SECONDS
ammo_type = list(/obj/item/ammo_casing/energy/kinetic/shockwave)
- can_bayonet = FALSE
max_mod_capacity = 90 //bumped up to 90 to compensate for the 30 you need to spend on the AOE mod that gives it its functionality
@@ -254,7 +250,6 @@
desc = "Mining RnD broke the fabric of space time AGAIN, please return to your nearest centralcommand officer. WARNING FROM THE MINING RND DIRECTOR : DO NOT RAPIDLY PULL TRIGGER : FABRIC OF SPACE TIME LIABLE TO BREAK \
Im being bullied by the admins"
ammo_type = list(/obj/item/ammo_casing/energy/kinetic/meme/nonlethal)
- can_bayonet = FALSE
max_mod_capacity = 0
//Modkits
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index cfaa1462ad0c2..dd58c32d8210f 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -150,11 +150,14 @@
icon_state = "scatterlaser"
range = 255
damage = 6
+ var/size_per_tile = 0.1
+ var/max_scale = 4
-/obj/projectile/beam/laser/accelerator/Range()
+/obj/projectile/beam/laser/accelerator/reduce_range()
..()
damage += 7
- transform *= 1 + ((damage/7) * 0.2)//20% larger per tile
+ transform = matrix()
+ transform *= min(1 + (maximum_range - range) * size_per_tile, max_scale)
///X-ray gun
@@ -216,12 +219,12 @@
shaded_charge = TRUE
ammo_x_offset = 1
obj_flags = UNIQUE_RENAME
- can_bayonet = TRUE
- knife_x_offset = 19
- knife_y_offset = 13
w_class = WEIGHT_CLASS_NORMAL
dual_wield_spread = 10 //as intended by the coders
+/obj/item/gun/energy/laser/thermal/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 19, offset_y = 13)
+
/obj/item/gun/energy/laser/thermal/Initialize(mapload)
. = ..()
AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS)
diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm
index 370954d6e0a1d..0a56204c8922b 100644
--- a/code/modules/projectiles/guns/energy/recharge.dm
+++ b/code/modules/projectiles/guns/energy/recharge.dm
@@ -119,9 +119,9 @@
recharge_time = 2 SECONDS
holds_charge = TRUE
unique_frequency = TRUE
- can_bayonet = TRUE
- knife_x_offset = 20
- knife_y_offset = 12
+
+/obj/item/gun/energy/recharge/ebow/add_bayonet_point()
+ AddComponent(/datum/component/bayonet_attachable, offset_x = 20, offset_y = 12)
/obj/item/gun/energy/recharge/ebow/give_manufacturer_examine()
AddElement(/datum/element/manufacturer_examine, COMPANY_SCARBOROUGH)
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index aaffdd89fed6a..7b539c03b4e2f 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -454,6 +454,6 @@
playsound(user.loc, 'sound/effects/coin2.ogg', 50, TRUE)
user.visible_message(span_warning("[user] flips a coin towards [target]!"), span_danger("You flip a coin towards [target]!"))
var/obj/projectile/bullet/coin/new_coin = new(get_turf(user), target_turf, user)
- new_coin.preparePixelProjectile(target_turf, user)
+ new_coin.aim_projectile(target_turf, user)
new_coin.fire()
return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/projectiles/guns/energy/transforming.dm b/code/modules/projectiles/guns/energy/transforming.dm
index cd7831db51190..740066d6e9703 100644
--- a/code/modules/projectiles/guns/energy/transforming.dm
+++ b/code/modules/projectiles/guns/energy/transforming.dm
@@ -891,7 +891,7 @@
ricochet_decay_chance = 1
ricochet_shoots_firer = FALSE //something something biometrics
impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
- reflectable = REFLECT_NORMAL
+ reflectable = TRUE
light_system = OVERLAY_LIGHT
light_outer_range = 1
light_power = 1
@@ -983,8 +983,7 @@
damage = 25
damage_type = BRUTE
icon_state = "blastwave"
- speed = 1
- pixel_speed_multiplier = 0.5
+ speed = 0.5
eyeblur = 10
jitter = 10 SECONDS
knockdown = 1
@@ -1043,7 +1042,7 @@
to_chat(target, span_reallybig(span_clown("Your blasted right off your shoes!!")))
M.visible_message(span_warning("[M] is is sent rocketing off their shoes!"))
playsound(src, 'sound/items/airhorn.ogg', 100, TRUE, -1)
- var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle))
+ var/atom/throw_target = get_edge_target_turf(target, angle2dir(angle))
M.throw_at(throw_target, 200, 8)
/**
@@ -1068,7 +1067,7 @@
. = ..()
if(isliving(target))
var/mob/living/new_target = target
- var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle))
+ var/atom/throw_target = get_edge_target_turf(target, angle2dir(angle))
new_target.throw_at(throw_target, 4, 1)
/**
@@ -1093,7 +1092,7 @@
armor_flag = ENERGY
hitsound = 'sound/weapons/tap.ogg'
impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
- reflectable = REFLECT_NORMAL
+ reflectable = TRUE
light_system = OVERLAY_LIGHT
light_outer_range = 1
light_power = 1
diff --git a/code/modules/projectiles/guns/special/SRN.dm b/code/modules/projectiles/guns/special/SRN.dm
index 3dc1c66fb3f55..0bc3a8a843112 100644
--- a/code/modules/projectiles/guns/special/SRN.dm
+++ b/code/modules/projectiles/guns/special/SRN.dm
@@ -36,14 +36,14 @@
///SRN Internal Magazine
/obj/item/ammo_box/magazine/internal/SRN_rocket
name = "SRN Rocket"
- ammo_type = /obj/item/ammo_casing/caseless/SRN_rocket
+ ammo_type = /obj/item/ammo_casing/SRN_rocket
caliber = "84mm"
max_ammo = 3
/// SRN caseless ammo casing
-/obj/item/ammo_casing/caseless/SRN_rocket
+/obj/item/ammo_casing/SRN_rocket
name = "\improper Spatial Rift Nullifier Rocket"
desc = "A prototype Spatial Rift Nullifier (SRN) Rocket. Fire at a rogue singularity or Tesla and pray it hits"
caliber = "84mm"
@@ -74,7 +74,7 @@
playsound(src.loc, SFX_SPARKS, vol = 100, vary = TRUE)
return ..()
-/obj/projectile/bullet/SRN_rocket/Impact(atom/impacted)
+/obj/projectile/bullet/SRN_rocket/impact(atom/impacted)
. = ..()
if(istype(impacted, /obj/singularity))
diff --git a/code/modules/projectiles/guns/special/blastcannon.dm b/code/modules/projectiles/guns/special/blastcannon.dm
index 762afb8385817..ad26dcedc851b 100644
--- a/code/modules/projectiles/guns/special/blastcannon.dm
+++ b/code/modules/projectiles/guns/special/blastcannon.dm
@@ -193,7 +193,7 @@
SSexplosions.shake_the_room(start_turf, max(heavy, medium, light, 0), (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium)
var/obj/projectile/blastwave/blastwave = new(loc, heavy, medium, light)
- blastwave.preparePixelProjectile(target, start_turf, params2list(modifiers), spread)
+ blastwave.aim_projectile(target, start_turf, params2list(modifiers), spread)
blastwave.fire()
cached_firer = null
cached_target = null
@@ -314,7 +314,7 @@
/obj/projectile/blastwave/is_hostile_projectile()
return TRUE
-/obj/projectile/blastwave/Range()
+/obj/projectile/blastwave/reduce_range()
. = ..()
if(QDELETED(src))
return
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 445d637f1a182..60979220743db 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -1,6 +1,8 @@
-#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan.
-#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers.
+/// Not actually hitscan but close as we get without actual hitscan.
+#define MOVES_HITSCAN -1
+/// How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers.
+#define MUZZLE_EFFECT_PIXEL_INCREMENT 17
/obj/projectile
name = "projectile"
@@ -16,34 +18,57 @@
blocks_emissive = EMISSIVE_BLOCK_GENERIC
layer = MOB_LAYER
plane = GAME_PLANE_FOV_HIDDEN
- var/generic_name
- //The sound this plays on impact.
+ /// The sound this plays on impact.
var/hitsound = 'sound/weapons/pierce.ogg'
- var/hitsound_wall = ""
+ /// Sound played when the projectile hits a wall
+ var/hitsound_wall
var/mixer_channel
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
- var/def_zone = "" //Aiming at
- /// Set to TRUE if we're grazing, which affects the message / embed chance / damage / effects
- var/grazing = FALSE
- var/atom/movable/firer = null//Who shot it
- var/datum/fired_from = null // the thing that the projectile was fired from (gun, turret, spell)
- var/suppressed = FALSE //Attack message
- var/yo = null
- var/xo = null
- var/atom/original = null // the original target clicked
- var/turf/starting = null // the projectile's starting turf
+ /// Zone at which the projectile is aimed at
+ var/def_zone = ""
+ /// Atom who shot the projectile (Not the gun, the guy who shot the gun)
+ var/atom/movable/firer = null
+ /// The thing that the projectile was fired from (gun, turret, spell)
+ var/datum/fired_from = null
+ /// One of three suppression states: NONE displays the hit message and produces a loud sound,
+ /// QUIET makes a quiet sound and only lets the victim know they've been shot, and VERY only makes a very quiet sound with no messages
+ var/suppressed = SUPPRESSED_NONE
+ /// Original clicked target
+ var/atom/original = null
+ /// Initial target x coordinate offset of the projectile
+ VAR_FINAL/xo = null
+ /// Initial target y coordinate offset of the projectile
+ VAR_FINAL/yo = null
+ /// Projectile's starting turf
+ var/turf/starting = null
+ /// pixel_x where the player clicked. Default is the center.
var/p_x = 16
- var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
-
- //Fired processing vars
- var/fired = FALSE //Have we been fired yet
- var/paused = FALSE //for suspending the projectile midair
- var/last_projectile_move = 0
- var/last_process = 0
- var/time_offset = 0
- var/datum/point/vector/trajectory
- var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location!
+ /// pixel_y where the player clicked. Default is the center
+ var/p_y = 16
+ /// X coordinate at which the projectile entered a new turf
+ var/entry_x
+ /// Y coordinate at which the projectile entered a new turf
+ var/entry_y
+ /// X coordinate at which the projectile visually impacted the target
+ var/impact_x
+ /// Y coordinate at which the projectile visually impacted the target
+ var/impact_y
+ /// Turf of the last atom we've impacted
+ VAR_FINAL/turf/last_impact_turf = null
+
+ /// If the projectile was fired already
+ var/fired = FALSE
+ /// If the projectile is suspended mid-air
+ var/paused = FALSE
+ /// Last time the projectile moved, used for lag compensation if SSprojectiles starts chugging
+ VAR_PRIVATE/last_projectile_move = 0
+ /// Last time the projectile was processed, also used for lag compensation
+ VAR_PRIVATE/last_process = 0
+ /// How many pixels we missed last tick due to lag or speed cap
+ VAR_PRIVATE/overrun = 0
+ /// Projectile's movement vector - this caches sine/cosine of our angle to cut down on trig calculations
+ var/datum/vector/movement_vector
/// We already impacted these things, do not impact them again. Used to make sure we can pierce things we want to pierce. Lazylist, typecache style (object = TRUE) for performance.
var/list/impacted = list()
/// If TRUE, we can hit our firer.
@@ -78,7 +103,7 @@
var/projectile_phasing = NONE
/// Bitflag for things the projectile should hit, but pierce through without deleting itself. Defers to projectile_phasing. Uses pass_flags flags.
var/projectile_piercing = NONE
- /// number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc!
+ /// Number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc!
var/pierces = 0
/// How many times this projectile can pierce something before deleting
var/max_pierces = 0
@@ -86,28 +111,27 @@
/// If objects are below this layer, we pass through them
var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER
- /// During each fire of SSprojectiles, the number of deciseconds since the last fire of SSprojectiles
- /// is divided by this var, and the result truncated to the next lowest integer is
- /// the number of times the projectile's `pixel_move` proc will be called.
- var/speed = 0.8
-
- /// This var is multiplied by SSprojectiles.global_pixel_speed to get how many pixels
- /// the projectile moves during each iteration of the movement loop
- ///
- /// If you want to make a fast-moving projectile, you should keep this equal to 1 and
- /// reduce the value of `speed`. If you want to make a slow-moving projectile, make
- /// `speed` a modest value like 1 and set this to a low value like 0.2.
- var/pixel_speed_multiplier = 1
-
- var/Angle = 0
- 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 = NO_STEPS //Use SLIDE_STEPS in conjunction with legacy
- /// how many times we've ricochet'd so far (instance variable, not a stat)
+ /// How many tiles we pass in a single SSprojectiles tick
+ var/speed = 1.25
+
+ /// The current angle of the projectile. Initially null, so if the arg is missing from [/fire()], we can calculate it from firer and target as fallback.
+ var/angle
+ /// Angle at the moment of firing
+ var/original_angle = 0
+ /// Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
+ var/nondirectional_sprite = FALSE
+ /// Random spread done projectile-side for convinience
+ var/spread = 0
+ /// Gliding does not enjoy something getting moved multiple turfs in a tick, which is why we animate it manually
+ animate_movement = NO_STEPS
+
+ // Ricochet logic
+ /// How many times we've ricochet'd so far (instance variable, not a stat)
var/ricochets = 0
- /// how many times we can ricochet max
+ /// How many times we can ricochet max
var/ricochets_max = 0
+ /// How many times we have to ricochet min (unless we hit an atom we can ricochet off)
+ var/min_ricochets = 0
/// 0-100 (or more, I guess), the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
var/ricochet_chance = 0
/// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet
@@ -125,18 +149,25 @@
/// Do we bounce off of everything reasonable to bounce, or based off of armor flags
var/expanded_bounce = FALSE
- ///If the object being hit can pass ths damage on to something else, it should not do it for this bullet
- var/force_hit = FALSE
-
- //Hitscan
- var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
- var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation.
- /// Last turf an angle was changed in for hitscan projectiles.
- var/turf/last_angle_set_hitscan_store
- var/datum/point/beam_index
- var/turf/hitscan_last //last turf touched during hitscanning.
+ // Hitscan logic
+ /// Wherever this projectile is hitscan. Hitscan projectiles are processed until the end of their path instantly upon being fired and leave a tracer in their path
+ var/hitscan = FALSE
+ /// Associated list of coordinate points in which we changed trajectories in order to calculate hitscan tracers
+ /// Value points to the next point in the beam
+ var/list/datum/point/beam_points
+ /// Last point in the beam
+ var/datum/point/last_point
+ /// Next forceMove will not create tracer end/start effects
+ var/free_hitscan_forceMove = FALSE
+ // Used to prevent duplicate effects during lag chunking
+ /// If a hitscan muzzle effect has been created for this "path", reset during forceMoves.
+ var/spawned_muzzle = FALSE
+
+ /// Hitscan tracer effect left behind the projectile
var/tracer_type
+ /// Hitscan muzzle effect spawned on the firer
var/muzzle_type
+ /// Hitscan impact effect spawned on the target
var/impact_type
//Fancy hitscan lighting effects!
@@ -150,11 +181,16 @@
var/impact_light_outer_range = 2
var/impact_light_color_override
- //Homing
+ // Homing
+ /// If the projectile is homing. Warning - this changes projectile's processing logic, reverting it to segmented processing instead of new raymarching logic
var/homing = FALSE
+ /// Target the projectile is homing on
var/atom/homing_target
- var/homing_turn_speed = 10 //Angle per tick.
- var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target.
+ /// Angles per move segment, distance is based on SSprojectiles.pixels_per_decisecond
+ /// With pixels_per_decisecond set to 16 and homing_turn_speed, the projectile can turn up to 20 pixels per turf passed
+ var/homing_turn_speed = 10
+ // Allowed leeway in pixels
+ var/homing_inaccuracy_min = 0
var/homing_inaccuracy_max = 0
var/homing_offset_x = 0
var/homing_offset_y = 0
@@ -162,20 +198,24 @@
var/damage = 10
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here
- ///Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb
+ /// Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb
var/armor_flag = BULLET
- ///How much armor this projectile pierces.
+ /// How much armor this projectile pierces.
var/armour_penetration = 0
- ///Flat armor ignorance, applied AFTER penetration has reduced the amount of armor by %
+ ///flat armour pen, like the way old armour pen worked. Applied AFTER percentage pen
var/armour_ignorance = 0
- ///Whether or not our bullet lacks penetrative power, and is easily stopped by armor.
+ /// Whether or not our projectile doubles the value of affecting armour
var/weak_against_armour = FALSE
- var/projectile_type = /obj/projectile
- var/range = 50 //This will de-increment every step. When 0, it will deletze the projectile.
- var/decayedRange //stores original range
- var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever
- var/reflectable = NONE // Can it be reflected or not?
- var/fauna_mod = 1 // What is the multiplier vs lavaland fauna and megafauna?
+ /// This will de-increment every step. When 0, it will delete the projectile.
+ var/range = 50
+ /// Original range upon being fired/reflected
+ var/maximum_range
+ /// Amount of original range that falls off when reflecting, so it doesn't go forever
+ var/reflect_range_decrease = 5
+ /// If this projectile can be reflected
+ var/reflectable = FALSE
+ /// What is the multiplier vs lavaland fauna and megafauna?
+ var/fauna_mod = 1
// Status effects applied on hit
var/stun = 0 SECONDS
@@ -198,33 +238,51 @@
/// Slurring applied on projectile hit
var/slur = 0 SECONDS
- var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all.
- var/catastropic_dismemberment = FALSE //If TRUE, this projectile deals its damage to the chest if it dismembers a limb.
- var/impact_effect_type //what type of impact effect to show when hitting something
- var/log_override = FALSE //is this type spammed enough to not log? (KAs)
+ /// Damage the limb must have for it to be dismembered upon getting hit. 0 will prevent dismembering altogether
+ var/dismemberment = 0
+ /// If TRUE, this projectile deals its damage to the chest if it dismembers a limb.
+ var/catastropic_dismemberment = FALSE
+ /// Impact VFX created upon hitting something
+ var/impact_effect_type
+ /// If the act of firing this projectile does not create logs
+ var/log_override = FALSE
+ /// If true, the projectile won't cause any logging whatsoever. Used for hallucinations and shit.
+ var/do_not_log = FALSE
/// We ignore mobs with these factions.
var/list/ignored_factions
-
- ///If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets
+ /// Turf that we have registered connect_loc signal - this is done for performance, as we're moving ~a dozen turfs per tick
+ /// and registering and unregistering signal for every single one of them is stupid. Unregistering the signal from the correct turf in case we get moved by smth else is important
+ var/turf/last_tick_turf
+ /// Remaining pixel movement last tick - used for precise range calculations
+ var/pixels_moved_last_tile = 0
+ /// In order to preserve animations, projectiles are only deleted the tick *after* they impact something.
+ /// Same is applied to reaching the range limit
+ var/deletion_queued = NONE
+ /// How many ticks should we wait in queued deletion mode before qdeleting? Sometimes increased in animations
+ var/ticks_to_deletion = 1
+
+ /// If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets
var/shrapnel_type
- ///If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target
- var/list/embedding
- ///If TRUE, hit mobs even if they're on the floor and not our target
+ /// If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target
+ var/embed_type
+ /// Saves embedding data
+ VAR_PROTECTED/datum/embedding/embed_data
+ /// If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles
var/hit_prone_targets = FALSE
- ///For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead.
+ /// If TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets
+ var/ignore_range_hit_prone_targets = FALSE
+ /// For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead.
var/sharpness = NONE
- ///How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes
+ /// How much we want to drop damage per tile as it travels through the air
+ var/damage_falloff_tile
+ /// How much we want to drop stamina damage (defined by the stamina variable) per tile as it travels through the air
+ var/stamina_falloff_tile
+ /// How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes
var/wound_falloff_tile
- ///How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes
+ /// How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes
var/embed_falloff_tile
- ///Stamina and damage dropoff over distance, for shotguns and the like
- ///It is a flat value that is SUBTRACTED from damage for every tile it moves, I.E 5 dropoff means the projectile looses 5 damage for every tile it moves past the first tile
- var/tile_dropoff = 0
- var/tile_dropoff_s = 0
-
- var/static/list/projectile_connections = list(COMSIG_ATOM_ENTERED = PROC_REF(on_entered))
/// How much accuracy is lost for each tile travelled
- var/accuracy_falloff = 0
+ var/accuracy_falloff = 7
/// How much accuracy before falloff starts to matter. Formula is range - falloff * tiles travelled
var/accurate_range = 100
/// If true directly targeted turfs can be hit
@@ -234,30 +292,49 @@
/obj/projectile/Initialize(mapload)
. = ..()
- decayedRange = range
- if(embedding)
- updateEmbedding()
- AddElement(/datum/element/connect_loc, projectile_connections)
- RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_enter))
+ maximum_range = range
+ if (embed_type)
+ set_embed(embed_type)
+ add_traits(list(TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT), INNATE_TRAIT)
-/obj/projectile/proc/Range()
+/obj/projectile/Destroy()
+ if (hitscan)
+ generate_hitscan_tracers()
+ STOP_PROCESSING(SSprojectiles, src)
+ firer = null
+ original = null
+ QDEL_NULL(embed_data)
+ if (movement_vector)
+ QDEL_NULL(movement_vector)
+ if (beam_points)
+ QDEL_LIST(beam_points)
+ if (last_point)
+ QDEL_NULL(last_point)
+ return ..()
+
+/// Called every time a projectile passes one tile worth of movement
+/obj/projectile/proc/reduce_range()
range--
- if(wound_bonus != CANT_WOUND)
+ pixels_moved_last_tile -= ICON_SIZE_ALL
+ if(wound_falloff_tile && wound_bonus != CANT_WOUND)
wound_bonus += wound_falloff_tile
bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile)
- if(embedding)
- embedding["embed_chance"] += embed_falloff_tile
- if(damage > 0)
- damage -= tile_dropoff
- if(stamina > 0)
- stamina -= tile_dropoff_s
- if(damage < 0 && stamina < 0)
- qdel(src)
+ if(embed_falloff_tile && get_embed())
+ embed_data.embed_chance += embed_falloff_tile
+ if(damage_falloff_tile && damage >= 0)
+ damage += damage_falloff_tile
+ if(stamina_falloff_tile && stamina >= 0)
+ stamina += stamina_falloff_tile
+
SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE)
if(range <= 0 && loc)
- on_range()
+ if (hitscan)
+ qdel(src)
+ return
+ deletion_queued = PROJECTILE_RANGE_DELETE
-/obj/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range
+/// Called next tick after the projectile reaches its maximum range so the animation has time to fully play out
+/obj/projectile/proc/on_range()
SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT)
qdel(src)
@@ -280,111 +357,67 @@
/obj/projectile/proc/on_hit(atom/target, blocked = 0, pierce_hit)
SHOULD_CALL_PARENT(TRUE)
+ // i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
+ // maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
+ var/hit_limb_zone
+ if(isliving(target))
+ var/mob/living/victim = target
+ hit_limb_zone = victim.check_hit_limb_zone_name(def_zone)
+
if(fired_from)
- SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, def_zone, blocked, pierce_hit)
- SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, def_zone, blocked, pierce_hit)
+ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, angle, hit_limb_zone, blocked, pierce_hit)
+ SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, angle, hit_limb_zone, blocked, pierce_hit)
- if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason
+ if(QDELETED(src) || deletion_queued) // in case one of the above signals deleted the projectile for whatever reason
return BULLET_ACT_BLOCK
- var/turf/target_turf = get_turf(target)
- var/hitx
- var/hity
+ var/turf/target_turf = get_turf(target)
if(target == original)
- hitx = target.pixel_x + p_x - 16
- hity = target.pixel_y + p_y - 16
+ impact_x = target.pixel_x + p_x - ICON_SIZE_X / 2
+ impact_y = target.pixel_y + p_y - ICON_SIZE_Y / 2
else
- hitx = target.pixel_x + rand(-8, 8)
- hity = target.pixel_y + rand(-8, 8)
+ impact_x = entry_x + movement_vector?.pixel_x * rand(0, ICON_SIZE_X / 2)
+ impact_y = entry_y + movement_vector?.pixel_y * rand(0, ICON_SIZE_Y / 2)
- if(isturf(target_turf) && hitsound_wall)
- var/volume = clamp(vol_by_damage() + 20, 0, 100)
- if(suppressed)
- volume = 5
- playsound(loc, hitsound_wall, volume, TRUE, -1, mixer_channel = mixer_channel)
+ if(isturf(target) && hitsound_wall)
+ playsound(src, hitsound_wall, clamp(vol_by_damage() + (suppressed ? 0 : 20), 0, 100), TRUE, -1, mixer_channel = mixer_channel)
- if(!isliving(target))
+ if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75))
+ var/turf/closed/wall/target_wall = target_turf
if(impact_effect_type && !hitscan)
- new impact_effect_type(target_turf, hitx, hity)
- if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75))
- var/turf/closed/wall/target_wall = target_turf
- target_wall.add_dent(WALL_DENT_SHOT, hitx, hity)
- //monkestation edit start
- if(damage_walls && target_wall.uses_integrity)
- target_wall.take_damage(damage * wall_dem_mod, damage_type, armor_flag, armour_penetration = armour_penetration)
- //monkestation edit end
+ new impact_effect_type(target_wall, impact_x, impact_y)
+ target_wall.add_dent(WALL_DENT_SHOT, impact_x, impact_y)
+ if(damage_walls)
+ target_wall.take_damage(damage * wall_dem_mod, damage_type, armor_flag, armour_penetration = 100)
return BULLET_ACT_HIT
- var/mob/living/living_target = target
+ if (hitsound)
+ playsound(src, hitsound, vol_by_damage(), TRUE, -1, mixer_channel = mixer_channel)
- if(isobj(living_target.buckled))
- var/obj/buck_source = living_target.buckled
- if(buck_source.cover_amount != 0)
- if(prob(buck_source.cover_amount))
- do_sparks(round((damage / 10)), FALSE, living_target)
- src.Impact(buck_source)
- blocked = 100 ///Brute force time
- damage = 0
- wound_bonus = CANT_WOUND
- embedding = list("embed_chance" = 0)
- qdel(src)
- return BULLET_ACT_HIT
-
- if(blocked != 100) // not completely blocked
- var/obj/item/bodypart/hit_bodypart = living_target.get_bodypart(def_zone)
- if(faction_check(living_target.faction, list(FACTION_MINING, FACTION_BOSS)))
- damage *= fauna_mod
- if (damage)
- if (living_target.blood_volume && damage_type == BRUTE && (isnull(hit_bodypart) || hit_bodypart.can_bleed()))
- var/splatter_dir = dir
- if(starting)
- splatter_dir = get_dir(starting, target_turf)
- living_target.do_splatter_effect(splatter_dir)
- if(prob(damage))
- living_target.blood_particles(amount = rand(1, 1 + round(damage/20, 1)), angle = src.Angle)
-
- else if (!isnull(hit_bodypart) && (hit_bodypart.biological_state & (BIO_METAL|BIO_WIRED)))
- var/random_damage_mult = RANDOM_DECIMAL(0.85, 1.15) // SOMETIMES you can get more or less sparks
- var/damage_dealt = ((damage / (1 - (blocked / 100))) * random_damage_mult)
-
- var/spark_amount = round((damage_dealt / PROJECTILE_DAMAGE_PER_ROBOTIC_SPARK))
- if (spark_amount > 0)
- do_sparks(spark_amount, FALSE, living_target)
-
- else if(impact_effect_type && !hitscan)
- new impact_effect_type(target_turf, hitx, hity)
-
- var/organ_hit_text = ""
- if(def_zone)
- organ_hit_text = " in \the [parse_zone(def_zone)]"
- if(suppressed == SUPPRESSED_VERY)
- playsound(loc, hitsound, 5, TRUE, -1, mixer_channel = mixer_channel)
- else if(suppressed)
- playsound(loc, hitsound, 5, TRUE, -1, mixer_channel = mixer_channel)
- to_chat(living_target, span_userdanger("You're [grazing ? "grazed" : "hit"] by \a [generic_name || src][organ_hit_text]!"))
- else
- playsound(loc, hitsound, vol_by_damage(), TRUE, -1, mixer_channel = mixer_channel)
- living_target.visible_message(
- span_danger("[living_target] is [grazing ? "grazed" : "hit"] by \a [generic_name || src][organ_hit_text]!"),
- span_userdanger("You're [grazing ? "grazed" : "hit"] by \a [generic_name || src][organ_hit_text]!"),
- span_hear("You hear a woosh."),
- // vision_distance = COMBAT_MESSAGE_RANGE,
- )
- if(living_target.is_blind())
- to_chat(living_target, span_userdanger("You feel something hit you[organ_hit_text]!"))
+ if (!isliving(target))
+ if(impact_effect_type && !hitscan)
+ new impact_effect_type(target_turf, impact_x, impact_y)
+ return BULLET_ACT_HIT
+
+ if((blocked >= 100 || (damage && damage_type != BRUTE)) && impact_effect_type && !hitscan)
+ new impact_effect_type(target_turf, impact_x, impact_y)
+ var/mob/living/living_target = target
+ get_embed()?.try_embed_projectile(src, target, hit_limb_zone, blocked, pierce_hit)
var/reagent_note
if(reagents?.reagent_list)
reagent_note = "REAGENTS: [pretty_string_from_reagent_list(reagents.reagent_list)]"
+ if(faction_check(living_target.faction, list(FACTION_MINING, FACTION_BOSS)))
+ damage *= fauna_mod
+
if(ismob(firer))
log_combat(firer, living_target, "shot", src, reagent_note)
return BULLET_ACT_HIT
if(isvehicle(firer))
var/obj/vehicle/firing_vehicle = firer
-
var/list/logging_mobs = firing_vehicle.return_controllers_with_flag(VEHICLE_CONTROL_EQUIPMENT)
if(!LAZYLEN(logging_mobs))
logging_mobs = firing_vehicle.return_drivers()
@@ -396,12 +429,31 @@
return BULLET_ACT_HIT
/obj/projectile/proc/vol_by_damage()
- if(src.damage)
- return clamp((src.damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 100
- else
+ if (suppressed)
+ return 5
+ if (!damage)
return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume
+ return clamp(damage * 0.67, 30, 100) // Multiply projectile damage by 0.67, then CLAMP the value between 30 and 1
+
+/obj/projectile/proc/firer_deleted(datum/source)
+ SIGNAL_HANDLER
+ // Shooting yourself point-blank
+ if (firer == original)
+ original = null
+ if (firer == fired_from)
+ fired_from = null
+ firer = null
+
+/obj/projectile/proc/original_deleted(datum/source)
+ SIGNAL_HANDLER
+ original = null
+
+/obj/projectile/proc/fired_from_deleted(datum/source)
+ SIGNAL_HANDLER
+ fired_from = null
-/obj/projectile/proc/on_ricochet(atom/A)
+/obj/projectile/proc/on_ricochet(atom/target)
+ ricochets++
if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range)
return
@@ -409,33 +461,22 @@
var/best_angle = ricochet_auto_aim_angle
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
best_angle += NICE_SHOT_RICOCHET_BONUS
- for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc))
- if(L.stat == DEAD || !is_in_sight(src, L) || (!ricochet_shoots_firer && L == firer))
+ for(var/mob/living/potential_target in range(ricochet_auto_aim_range, loc))
+ if(potential_target.stat == DEAD || !is_in_sight(src, potential_target) || (!ricochet_shoots_firer && potential_target == firer))
continue
- var/our_angle = abs(closer_angle_difference(Angle, get_angle(src.loc, L.loc)))
+ var/our_angle = abs(closer_angle_difference(angle, get_angle(loc, potential_target.loc)))
if(our_angle < best_angle)
best_angle = our_angle
- unlucky_sob = L
+ unlucky_sob = potential_target
if(unlucky_sob)
set_angle(get_angle(src, unlucky_sob.loc))
+ original = unlucky_sob
-/obj/projectile/proc/store_hitscan_collision(datum/point/point_cache)
- beam_segments[beam_index] = point_cache
- beam_index = point_cache
- beam_segments[beam_index] = null
-
-/obj/projectile/Bump(atom/A)
- SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
- if(!can_hit_target(A, A == original, TRUE, TRUE))
- return
- Impact(A)
-
-/// Signal proc for when a projectile enters a turf.
-/obj/projectile/proc/on_enter(datum/source, atom/old_loc, dir, forced, list/old_locs)
- SIGNAL_HANDLER
-
- UnregisterSignal(old_loc, COMSIG_ATOM_ATTACK_HAND)
+/obj/projectile/Bump(atom/bumped_atom)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom)
+ if (can_hit_target(bumped_atom, bumped_atom == original, TRUE, TRUE))
+ impact(bumped_atom)
/**
* Called when the projectile hits something
@@ -448,114 +489,107 @@
* Furthermore, this proc shouldn't check can_hit_target - this should only be called if can hit target is already checked.
* Also, we select_target to find what to process_hit first.
*/
-/obj/projectile/proc/Impact(atom/A)
- if(!trajectory)
- qdel(src)
- return FALSE
- if(impacted[A]) // NEVER doublehit
- return FALSE
- if(istype(A, /mob/living/))
- var/mob/living/living_atom = A
- if(impacted[living_atom.buckled])
- return FALSE
- var/datum/point/point_cache = trajectory.copy_to()
- var/turf/T = get_turf(A)
- if(ricochets < ricochets_max && check_ricochet_flag(A) && check_ricochet(A))
- ricochets++
- if(A.handle_ricochet(src))
- on_ricochet(A)
- impacted = list() // Shoot a x-ray laser at a pair of mirrors I dare you
- ignore_source_check = TRUE // Firer is no longer immune
- decayedRange = max(0, decayedRange - reflect_range_decrease)
- ricochet_chance *= ricochet_decay_chance
- damage *= ricochet_decay_damage
- stamina *= ricochet_decay_damage
- range = decayedRange
- if(hitscan)
- store_hitscan_collision(point_cache)
- return TRUE
+/obj/projectile/proc/impact(atom/target)
+ // Don't impact anything if we've been queued for deletion
+ if (deletion_queued)
+ return
- var/distance = decayedRange - range
- var/hit_prob = clamp(accurate_range - (accuracy_falloff * distance), 5, 100)
- if(isliving(A))
- var/mob/living/who_is_shot = A
- if(who_is_shot.body_position == LYING_DOWN)
- hit_prob *= 1.2
- // melbert todo : make people more skilled with weapons have a lower miss chance // Consider nuking this TODO and update the projectile refactor
- if(!prob(hit_prob))
- def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * get_dist(get_turf(A), starting)), 5, 100))
- grazing = !prob(hit_prob) // jeez you missed twice? that's a graze
- if(grazing)
- wound_bonus = CANT_WOUND
- bare_wound_bonus = CANT_WOUND
-
- return process_hit(T, select_target(T, A, A), A) // SELECT TARGET FIRST!
+ // never doublehit, otherwise someone may end up running into a projectile from the back
+ if(impacted[target.weak_reference])
+ return
-/**
- * The primary workhorse proc of projectile impacts.
- * This is a RECURSIVE call - process_hit is called on the first selected target, and then repeatedly called if the projectile still hasn't been deleted.
- *
- * Order of operations:
- * 1. Checks if we are deleted, or if we're somehow trying to hit a null, in which case, bail out
- * 2. Adds the thing we're hitting to impacted so we can make sure we don't doublehit
- * 3. Checks piercing - stores this.
- * Afterwards:
- * Hit and delete, hit without deleting and pass through, pass through without hitting, or delete without hitting depending on result
- * If we're going through without hitting, find something else to hit if possible and recurse, set unstoppable movement to true
- * If we're deleting without hitting, delete and return
- * Otherwise, send signal of COMSIG_PROJECTILE_PREHIT to target
- * Then, hit, deleting ourselves if necessary.
- * @params
- * T - Turf we're on/supposedly hitting
- * target - target we're hitting
- * bumped - target we originally bumped. it's here to ensure that if something blocks our projectile by means of Cross() failure, we hit it
- * even if it is not dense.
- * hit_something - only should be set by recursive calling by this proc - tracks if we hit something already
- *
- * Returns if we hit something.
- */
-/obj/projectile/proc/process_hit(turf/T, atom/target, atom/bumped, hit_something = FALSE)
- // 1.
- if(QDELETED(src) || !T || !target)
+ if(ricochets < ricochets_max && check_ricochet_flag(target) && check_ricochet(target) && target.handle_ricochet(src))
+ on_ricochet(target)
+ impacted = list() // Shoot a x-ray laser at a pair of mirrors I dare you
+ ignore_source_check = TRUE // Firer is no longer immune
+ maximum_range = max(0, maximum_range - reflect_range_decrease)
+ ricochet_chance *= ricochet_decay_chance
+ damage *= ricochet_decay_damage
+ stamina *= ricochet_decay_damage
+ range = maximum_range
+ return
+
+ last_impact_turf = get_turf(target)
+ // Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
+ def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * get_dist(last_impact_turf, starting)), 5, 100))
+ var/impact_result = process_hit_loop(select_target(last_impact_turf, target))
+ if (impact_result == PROJECTILE_IMPACT_PASSED)
return
- // 2.
- impacted[target] = TRUE //hash lookup > in for performance in hit-checking
- // 3.
- var/mode = prehit_pierce(target)
- if(mode == PROJECTILE_DELETE_WITHOUT_HITTING)
+ if (hitscan)
qdel(src)
- return hit_something
- else if(mode == PROJECTILE_PIERCE_PHASE)
- if(!(movement_type & PHASING))
- temporary_unstoppable_movement = TRUE
- movement_type |= PHASING
- return process_hit(T, select_target(T, target, bumped), bumped, hit_something) // try to hit something else
- // at this point we are going to hit the thing
- // in which case send signal to it
- var/signal_bitfield = SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args, src) //monkestation edit
- if (signal_bitfield & PROJECTILE_INTERRUPT_HIT || (SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, args) & PROJECTILE_INTERRUPT_HIT))
- if(!(signal_bitfield & PROJECTILE_INTERRUPT_BLOCK_QDEL)) //monkestation edit
- qdel(src)
- return BULLET_ACT_BLOCK
- if(mode == PROJECTILE_PIERCE_HIT)
- ++pierces
- hit_something = TRUE
- var/result = target.bullet_act(src, def_zone, mode == PROJECTILE_PIERCE_HIT)
- if((result == BULLET_ACT_FORCE_PIERCE) || (mode == PROJECTILE_PIERCE_HIT))
+ return
+ deletion_queued = PROJECTILE_IMPACT_DELETE
+
+/*
+ * Main projectile hit loop code
+ * As long as there are valid targets on the hit target's tile, we will loop through all the ones that we have not hit
+ * (and thus invalidated) and try to hit them until either no targets remain or we've been deleted.
+ * Should *never* be called directly, as impact() is the proc queueing projectiles for deletion
+ * If you need to call this directly, you should reconsider the choices that led you to this point
+ */
+/obj/projectile/proc/process_hit_loop(atom/target)
+ SHOULD_NOT_SLEEP(TRUE)
+ PRIVATE_PROC(TRUE)
+
+ // Don't impact anything if we've been queued for deletion
+ if (deletion_queued)
+ return PROJECTILE_IMPACT_PASSED
+
+ var/turf/target_turf = get_turf(target)
+ while (target && !QDELETED(src) && !deletion_queued)
+ // Doublehitting can be an issue with slow projectiles or when the server is chugging
+ impacted[WEAKREF(target)] = TRUE
+ var/mode = prehit_pierce(target)
+ if(mode == PROJECTILE_DELETE_WITHOUT_HITTING)
+ return PROJECTILE_IMPACT_INTERRUPTED
+
+ // If we've phasing through a target, first set ourselves as phasing and then try to locate a new one
+ if(mode == PROJECTILE_PIERCE_PHASE)
+ if(!(movement_type & PHASING))
+ temporary_unstoppable_movement = TRUE
+ movement_type |= PHASING
+ target = select_target(target_turf, target)
+ continue
+
+ var/target_signal = SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, src)
+ if (target_signal & PROJECTILE_INTERRUPT_HIT_PHASE)
+ return PROJECTILE_IMPACT_PASSED
+ if (target_signal & PROJECTILE_INTERRUPT_HIT)
+ return PROJECTILE_IMPACT_INTERRUPTED
+
+ var/self_signal = SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, target)
+ if (self_signal & PROJECTILE_INTERRUPT_HIT_PHASE)
+ return PROJECTILE_IMPACT_PASSED
+ if (self_signal & PROJECTILE_INTERRUPT_HIT)
+ return PROJECTILE_IMPACT_INTERRUPTED
+
+ if(mode == PROJECTILE_PIERCE_HIT)
+ pierces += 1
+
+ // Targets should handle their impact logic on our own and if they decide that we hit them, they call our on_hit
+ var/result = target.projectile_hit(src, def_zone, mode == PROJECTILE_PIERCE_HIT)
+ if (result != BULLET_ACT_FORCE_PIERCE && max_pierces && pierces >= max_pierces)
+ return PROJECTILE_IMPACT_SUCCESSFUL
+
+ // If we're not piercing or phasing, delete ourselves
+ if (result != BULLET_ACT_FORCE_PIERCE && mode != PROJECTILE_PIERCE_HIT && mode != PROJECTILE_PIERCE_PHASE)
+ return PROJECTILE_IMPACT_SUCCESSFUL
+
+ // We've piercing though this one, go look for a new target
if(!(movement_type & PHASING))
temporary_unstoppable_movement = TRUE
movement_type |= PHASING
- return process_hit(T, select_target(T, target, bumped), bumped, TRUE)
- qdel(src)
- return hit_something
+
+ target = select_target(target_turf, target)
+
+ return PROJECTILE_IMPACT_PASSED
/**
* Selects a target to hit from a turf
*
* @params
- * T - The turf
- * target - The "preferred" atom to hit, usually what we Bumped() first.
- * bumped - used to track if something is the reason we impacted in the first place.
+ * our_turf - Turf on which we hit the target
+ * bumped - What we've impacted and why this selection was called in the first place.
* If set, this atom is always treated as dense by can_hit_target.
*
* Priority:
@@ -569,9 +603,9 @@
* 5. Turf
* 6. Nothing
*/
-/obj/projectile/proc/select_target(turf/our_turf, atom/target, atom/bumped)
+/obj/projectile/proc/select_target(turf/our_turf, atom/bumped)
// 1. special bumped border object check
- if((bumped?.flags_1 & ON_BORDER_1) && can_hit_target(bumped, original == bumped, FALSE, TRUE))
+ if((bumped?.flags_1 & ON_BORDER_1) && can_hit_target(bumped, original == bumped, TRUE, TRUE))
return bumped
// 2. original
if(can_hit_target(original, TRUE, FALSE, original == bumped))
@@ -584,9 +618,9 @@
if(length(considering))
return pick(considering)
// 4. objs and other dense things
- for(var/i in our_turf)
- if(can_hit_target(i, i == original, TRUE, i == bumped))
- considering += i
+ for(var/atom/potential_target as anything in our_turf)
+ if(can_hit_target(potential_target, potential_target == original, TRUE, potential_target == bumped))
+ considering += potential_target
if(length(considering))
return pick(considering)
// 5. turf
@@ -595,25 +629,28 @@
// 6. nothing
// (returns null)
-//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.
+/// 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, direct_target = FALSE, ignore_loc = FALSE, cross_failed = FALSE)
- if(QDELETED(target) || impacted[target])
+ if(QDELETED(target) || impacted[target.weak_reference])
return FALSE
if(!ignore_loc && (loc != target.loc) && !(can_hit_turfs && direct_target && loc == target))
return FALSE
// if pass_flags match, pass through entirely - unless direct target is set.
if((target.pass_flags_self & pass_flags) && !direct_target)
return FALSE
- if(!ignore_source_check && firer)
- var/mob/M = firer
- if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target)))
+ if(!ignore_source_check && firer && !direct_target)
+ if(target == firer || (target == firer.loc && ismecha(firer.loc)) || (target in firer.buckled_mobs))
return FALSE
+ if(ismob(firer))
+ var/mob/firer_mob = firer
+ if (firer_mob.buckled == target)
+ return FALSE
if(ignored_factions?.len && ismob(target) && !direct_target)
var/mob/target_mob = target
if(faction_check(target_mob.faction, ignored_factions))
return FALSE
- if((target.density && !target.IsObscured()) || cross_failed) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
+ if(target.density || cross_failed) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
return TRUE
if(!isliving(target))
if(isturf(target)) // non dense turfs
@@ -630,27 +667,19 @@
return FALSE
if(HAS_TRAIT(living_target, TRAIT_IMMOBILIZED) && HAS_TRAIT(living_target, TRAIT_FLOORED) && HAS_TRAIT(living_target, TRAIT_HANDS_BLOCKED))
return FALSE
- if(!hit_prone_targets)
+ if(hit_prone_targets)
var/mob/living/buckled_to = living_target.lowest_buckled_mob()
- if(!buckled_to.density) // Will just be us if we're not buckled to another mob
- return FALSE
- if(living_target.body_position != LYING_DOWN)
+ if((maximum_range - range) <= MAX_RANGE_HIT_PRONE_TARGETS) // after MAX_RANGE_HIT_PRONE_TARGETS tiles, auto-aim hit for mobs on the floor turns off
return TRUE
+ if(ignore_range_hit_prone_targets) // doesn't apply to projectiles that must hit the target in combat mode or something else, no matter what
+ return TRUE
+ if(buckled_to.density) // Will just be us if we're not buckled to another mob
+ return TRUE
+ return FALSE
+ else if(living_target.body_position == LYING_DOWN)
+ return FALSE
return TRUE
-/**
- * Scan if we should hit something and hit it if we need to
- * The difference between this and handling in Impact is
- * In this we strictly check if we need to Impact() something in specific
- * If we do, we do
- * We don't even check if it got hit already - Impact() does that
- * In impact there's more code for selecting WHAT to hit
- * So this proc is more of checking if we should hit something at all BY having an atom cross us.
- */
-/obj/projectile/proc/scan_crossed_hit(atom/movable/A)
- if(can_hit_target(A, direct_target = (A == original)))
- Impact(A)
-
/**
* Scans if we should hit something on the turf we just moved to if we haven't already
*
@@ -663,28 +692,30 @@
// and hope projectiles get refactored again in the future to have a less stupid impact detection system
// that hopefully won't also involve a ton of overhead
if(can_hit_target(original, TRUE, FALSE))
- Impact(original) // try to hit thing clicked on
+ impact(original) // try to hit thing clicked on
+ return
// else, try to hit mobs
- else // because if we impacted original and pierced we'll already have select target'd and hit everything else we should be hitting
- for(var/mob/M in loc) // so I guess we're STILL doing a for loop of mobs because living movement would otherwise have snowflake code for projectile CanPass
- // so the snowflake vs performance is pretty arguable here
- if(can_hit_target(M, M == original, TRUE))
- Impact(M)
- break
+ // because if we impacted original and pierced we'll already have select target'd and hit everything else we should be hitting
+ for(var/mob/potential_target in loc) // so I guess we're STILL doing a for loop of mobs because living movement would otherwise have snowflake code for projectile CanPass
+ // so the snowflake vs performance is pretty arguable here
+ if(can_hit_target(potential_target, potential_target == original, TRUE))
+ impact(potential_target)
+ break
/**
* Projectile crossed: When something enters a projectile's tile, make sure the projectile hits it if it should be hitting it.
*/
-/obj/projectile/proc/on_entered(datum/source, atom/movable/AM)
+/obj/projectile/proc/on_entered(datum/source, atom/movable/entered_atom)
SIGNAL_HANDLER
- scan_crossed_hit(AM)
+ if(can_hit_target(entered_atom, direct_target = (entered_atom == original)))
+ impact(entered_atom)
/**
* Projectile can pass through
* Used to not even attempt to Bump() or fail to Cross() anything we already hit.
*/
/obj/projectile/CanPassThrough(atom/blocker, movement_dir, blocker_opinion)
- return impacted[blocker] ? TRUE : ..()
+ return impacted[blocker.weak_reference] || ..()
/**
* Projectile moved:
@@ -702,7 +733,8 @@
if(temporary_unstoppable_movement)
temporary_unstoppable_movement = FALSE
movement_type &= ~PHASING
- scan_moved_turf() //mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump
+ // Mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump
+ scan_moved_turf()
/**
* Checks if we should pierce something.
@@ -710,286 +742,483 @@
* NOT meant to be a pure proc, since this replaces prehit() which was used to do things.
* Return PROJECTILE_DELETE_WITHOUT_HITTING to delete projectile without hitting at all!
*/
-/obj/projectile/proc/prehit_pierce(atom/A)
- if((projectile_phasing & A.pass_flags_self) && (phasing_ignore_direct_target || original != A))
+/obj/projectile/proc/prehit_pierce(atom/target)
+ if((projectile_phasing & target.pass_flags_self) && (phasing_ignore_direct_target || original != target))
return PROJECTILE_PIERCE_PHASE
- if(projectile_piercing & A.pass_flags_self)
+ if(projectile_piercing & target.pass_flags_self)
return PROJECTILE_PIERCE_HIT
- if(ismovable(A))
- var/atom/movable/AM = A
- if(AM.throwing)
- return (projectile_phasing & LETPASSTHROW)? PROJECTILE_PIERCE_PHASE : ((projectile_piercing & LETPASSTHROW)? PROJECTILE_PIERCE_HIT : PROJECTILE_PIERCE_NONE)
+ if(ismovable(target))
+ var/atom/movable/movable_target = target
+ if(movable_target.throwing)
+ return (projectile_phasing & LETPASSTHROW) ? PROJECTILE_PIERCE_PHASE : ((projectile_piercing & LETPASSTHROW)? PROJECTILE_PIERCE_HIT : PROJECTILE_PIERCE_NONE)
return PROJECTILE_PIERCE_NONE
-/obj/projectile/proc/check_ricochet(atom/A)
- var/chance = ricochet_chance * A.receive_ricochet_chance_mod
+/obj/projectile/proc/check_ricochet(atom/target)
+ var/chance = ricochet_chance * target.receive_ricochet_chance_mod
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
chance += NICE_SHOT_RICOCHET_BONUS
- if(prob(chance))
+ if(ricochets < min_ricochets || prob(chance))
return TRUE
return FALSE
-/obj/projectile/proc/check_ricochet_flag(atom/A)
- if((armor_flag in list(ENERGY, LASER)) && (A.flags_ricochet & RICOCHET_SHINY))
+/obj/projectile/proc/check_ricochet_flag(atom/target)
+ if((armor_flag in list(ENERGY, LASER)) && (target.flags_ricochet & RICOCHET_SHINY))
return TRUE
-
- if((armor_flag in list(BOMB, BULLET)) && (A.flags_ricochet & RICOCHET_HARD))
+ if((armor_flag in list(BOMB, BULLET)) && (target.flags_ricochet & RICOCHET_HARD))
return TRUE
-
- if(expanded_bounce && (A.flags_ricochet & (RICOCHET_HARD | RICOCHET_SHINY)))
- return TRUE
-
return FALSE
-/obj/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight.
- if(!trajectory && isnull(forced_angle) && isnull(Angle))
- return FALSE
- var/datum/point/vector/current = trajectory
- if(!current)
- var/turf/T = get_turf(src)
- current = new(T.x, T.y, T.z, pixel_x, pixel_y, isnull(forced_angle)? Angle : forced_angle, SSprojectiles.global_pixel_speed)
- var/datum/point/vector/v = current.return_vector_after_increments(moves * SSprojectiles.global_iterations_per_move)
- return v.return_turf()
-
-/obj/projectile/proc/return_pathing_turfs_in_moves(moves, forced_angle)
- var/turf/current = get_turf(src)
- var/turf/ending = return_predicted_turf_after_moves(moves, forced_angle)
- return get_line(current, ending)
-
/obj/projectile/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
return TRUE //Bullets don't drift in space
-/obj/projectile/process()
- last_process = world.time
- if(!loc || !fired || !trajectory)
- fired = FALSE
- return PROCESS_KILL
- if(paused || !isturf(loc))
- last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks.
- return
- var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset
- time_offset = 0
- var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :<
- if(required_moves == MOVES_HITSCAN)
- required_moves = SSprojectiles.global_max_tick_moves
- else
- if(required_moves > SSprojectiles.global_max_tick_moves)
- var/overrun = required_moves - SSprojectiles.global_max_tick_moves
- required_moves = SSprojectiles.global_max_tick_moves
- time_offset += overrun * speed
- time_offset += MODULUS(elapsed_time_deciseconds, speed)
- SEND_SIGNAL(src, COMSIG_PROJECTILE_BEFORE_MOVE)
- for(var/i in 1 to required_moves)
- pixel_move(pixel_speed_multiplier, FALSE)
-
-/obj/projectile/proc/fire(angle, atom/direct_target)
+/obj/projectile/proc/fire(fire_angle, atom/direct_target)
LAZYINITLIST(impacted)
- if(fired_from)
- SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
- if(firer)
+ if (firer)
+ RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(firer_deleted))
SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original)
- //If no angle needs to resolve it from xo/yo!
- if(shrapnel_type && LAZYLEN(embedding))
- AddElement(/datum/element/embed, projectile_payload = shrapnel_type)
+ if (fired_from)
+ if (firer != fired_from)
+ RegisterSignal(fired_from, COMSIG_QDELETING, PROC_REF(fired_from_deleted))
+ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
+ if (original)
+ if (firer != original)
+ RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted))
if(!log_override && firer && original)
log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]")
//note: mecha projectile logging is handled in /obj/item/mecha_parts/mecha_equipment/weapon/action(). try to keep these messages roughly the sameish just for consistency's sake.
- if(direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots
- process_hit(get_turf(direct_target), direct_target)
- if(QDELETED(src))
+ if (direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots
+ impact(direct_target)
+ if (QDELETED(src))
return
- if(isnum(angle))
- set_angle(angle)
- if(spread)
- set_angle(Angle + ((rand() - 0.5) * spread))
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))
+ if (isnum(fire_angle))
+ set_angle(fire_angle)
+ else 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)
+ var/turf/target = locate(clamp(starting.x + xo, 1, world.maxx), clamp(starting.y + yo, 1, world.maxy), starting.z)
set_angle(get_angle(src, target))
- original_angle = Angle
- if(!nondirectional_sprite)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
- trajectory_ignore_forcemove = TRUE
+ if (spread)
+ set_angle(angle + (rand() - 0.5) * spread)
+ original_angle = angle
+ movement_vector = new(speed, angle)
+ if (hitscan)
+ beam_points = list()
+ free_hitscan_forceMove = TRUE
forceMove(starting)
- trajectory_ignore_forcemove = FALSE
- trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed)
last_projectile_move = world.time
fired = TRUE
- play_fov_effect(starting, 6, "gunfire", dir = NORTH, angle = Angle)
+ play_fov_effect(starting, 6, "gunfire", dir = NORTH, angle = angle)
SEND_SIGNAL(src, COMSIG_PROJECTILE_FIRE)
- if(hitscan)
+ if (hitscan && !deletion_queued)
+ record_hitscan_start()
process_hitscan()
- if(!(datum_flags & DF_ISPROCESSING))
+ if (QDELETED(src))
+ return
+ if (!(datum_flags & DF_ISPROCESSING))
START_PROCESSING(SSprojectiles, src)
- pixel_move(pixel_speed_multiplier, FALSE) //move it now!
+ // move it now to avoid potentially hitting yourself with firer-hitting projectiles
+ if (!deletion_queued && !hitscan)
+ process_movement(max(FLOOR(speed, 1), 1), tile_limit = TRUE)
+
+/// Makes projectile home onto the passed target with minor inaccuracy
+/obj/projectile/proc/set_homing_target(atom/target)
+ if(!target || (!isturf(target) && !isturf(target.loc)))
+ return FALSE
+ homing = TRUE
+ homing_target = target
+ 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
-/obj/projectile/proc/set_angle(new_angle) //wrapper for overrides.
- Angle = new_angle
+/obj/projectile/proc/set_angle(new_angle)
+ if (angle == new_angle)
+ return
if(!nondirectional_sprite)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
- if(trajectory)
- trajectory.set_angle(new_angle)
- if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store))
- last_angle_set_hitscan_store = loc
- var/datum/point/point_cache = new (src)
- point_cache = trajectory.copy_to()
- store_hitscan_collision(point_cache)
- return TRUE
+ transform = transform.TurnTo(angle, new_angle)
+ angle = new_angle
+ if(movement_vector)
+ movement_vector.set_angle(new_angle)
+ if(fired && hitscan && isturf(loc))
+ create_hitscan_point()
/// Same as set_angle, but the reflection continues from the center of the object that reflects it instead of the side
-/obj/projectile/proc/set_angle_centered(new_angle)
- Angle = new_angle
+/obj/projectile/proc/set_angle_centered(center_turf, new_angle)
+ if (angle == new_angle)
+ return
if(!nondirectional_sprite)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
- if(trajectory)
- trajectory.set_angle(new_angle)
-
- var/list/coordinates = trajectory.return_coordinates()
- trajectory.set_location(coordinates[1], coordinates[2], coordinates[3]) // Sets the trajectory to the center of the tile it bounced at
-
- if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) // Handles hitscan projectiles
- last_angle_set_hitscan_store = loc
- var/datum/point/point_cache = new (src)
- point_cache.initialize_location(coordinates[1], coordinates[2], coordinates[3]) // Take the center of the hitscan collision tile
- store_hitscan_collision(point_cache)
- return TRUE
+ transform = transform.TurnTo(angle, new_angle)
+ free_hitscan_forceMove = TRUE
+ forceMove(center_turf)
+ entry_x = 0
+ entry_y = 0
+ angle = new_angle
+ if(movement_vector)
+ movement_vector.set_angle(new_angle)
+ if(fired && hitscan && isturf(loc))
+ create_hitscan_point(tile_center = TRUE)
+/obj/projectile/vv_edit_var(var_name, var_value)
+ if(var_name == NAMEOF(src, angle))
+ set_angle(var_value)
+ return TRUE
+ return ..()
+/*
+ * Projectile's process calculates the amount of pixels that it needs to move per tick and calls moveloop processing
+ * There is a strict cap on how many pixels it can move in a tick to prevent them from turning into hitscans during lag
+ * Path that the projectile could not finish would be stored in the overrun variable to be processed next tick
+ */
-/obj/projectile/forceMove(atom/target)
- if(!isloc(target) || !isloc(loc) || !z)
- return ..()
- var/zc = target.z != z
- var/old = loc
- if(zc)
- before_z_change(old, target)
- . = ..()
- if(QDELETED(src)) // we coulda bumped something
+/obj/projectile/process()
+ last_process = world.time
+ if(!loc || !fired || !movement_vector)
+ fired = FALSE
+ return PROCESS_KILL
+
+ // If last tick the projectile impacted something or reached its range, don't process it
+ if (deletion_queued == PROJECTILE_IMPACT_DELETE)
+ ticks_to_deletion -= 1
+ if (!ticks_to_deletion)
+ qdel(src)
return
- if(trajectory && !trajectory_ignore_forcemove && isturf(target))
- if(hitscan)
- finalize_hitscan_and_generate_tracers(FALSE)
- trajectory.initialize_location(target.x, target.y, target.z, 0, 0)
- if(hitscan)
- record_hitscan_start(RETURN_PRECISE_POINT(src))
- if(zc)
- after_z_change(old, target)
-/obj/projectile/proc/after_z_change(atom/olcloc, atom/newloc)
+ if (deletion_queued == PROJECTILE_RANGE_DELETE)
+ on_range()
+ return
-/obj/projectile/proc/before_z_change(atom/oldloc, atom/newloc)
+ if(paused || !isturf(loc))
+ // Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks.
+ last_projectile_move = last_process
+ return
-/obj/projectile/vv_edit_var(var_name, var_value)
- switch(var_name)
- if(NAMEOF(src, Angle))
- set_angle(var_value)
- return TRUE
- else
- return ..()
+ if (hitscan)
+ process_hitscan()
+ return
-/obj/projectile/proc/set_pixel_speed(new_speed)
- if(trajectory)
- trajectory.set_speed(new_speed)
- return TRUE
+ // Calculates how many pixels should be moved this tick, including overrun debt from the previous tick
+ var/elapsed_time = world.time - last_projectile_move
+ var/pixels_to_move = elapsed_time * SSprojectiles.pixels_per_decisecond * speed + overrun
+ overrun = 0
+
+ if (pixels_to_move > SSprojectiles.max_pixels_per_tick)
+ overrun = pixels_to_move - SSprojectiles.max_pixels_per_tick
+ pixels_to_move = SSprojectiles.max_pixels_per_tick
+
+ overrun += MODULUS(pixels_to_move, 1)
+ pixels_to_move = FLOOR(pixels_to_move, 1)
+ SEND_SIGNAL(src, COMSIG_PROJECTILE_BEFORE_MOVE)
+
+ // Registering turf entries is done here instead of a connect_loc because else it could be called multiple times per tick and waste performance
+ if (last_tick_turf)
+ UnregisterSignal(last_tick_turf, COMSIG_ATOM_ENTERED)
+
+ process_movement(pixels_to_move)
+
+ if (!QDELETED(src) && !deletion_queued && isturf(loc))
+ RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
+ last_tick_turf = loc
+
+/*
+ * Main projectile movement cycle.
+ * Normal behavior moves projectiles in a straight line through tiles, but it gets trickier with homing.
+ * Every pixels_per_decisecond we will stop and call process_homing(), which while a bit rough, does not have a significant performance impact
+ * This proc needs to be very performant, so do not add overridable logic that can be handled in homing or animations here.
+ *
+ * pixels_to_move determines how many pixels the projectile should move
+ * hitscan prevents animation logic from running
+ * tile_limit prevents any movements past the first tile change
+ */
+/obj/projectile/proc/process_movement(pixels_to_move, hitscan = FALSE, tile_limit = FALSE)
+ if (!isturf(loc) || !movement_vector)
+ return 0
+ var/total_move_distance = pixels_to_move
+ var/movements_done = 0
+ last_projectile_move = world.time
+ while (pixels_to_move > 0 && isturf(loc) && !QDELETED(src) && !deletion_queued)
+ // Because pixel_x/y represents offset and not actual visual position of the projectile, we add 16 pixels to each and cut the excess because projectiles are not meant to be highly offset by default
+ var/pixel_x_actual = pixel_x + ICON_SIZE_X / 2
+ if(pixel_x_actual > ICON_SIZE_X)
+ pixel_x_actual = pixel_x_actual % ICON_SIZE_X
+
+ var/pixel_y_actual = pixel_y + ICON_SIZE_Y / 2
+ if(pixel_y_actual > ICON_SIZE_Y)
+ pixel_y_actual = pixel_y_actual % ICON_SIZE_Y
+
+ var/distance_to_border = INFINITY
+ // What distances do we need to move to hit the horizontal/vertical turf border
+ var/x_to_border = INFINITY
+ var/y_to_border = INFINITY
+ // If we're moving strictly up/down/left/right then one of these can be 0 and produce div by zero
+ if (movement_vector.pixel_x)
+ var/x_border_dist = -pixel_x_actual
+ if (movement_vector.pixel_x > 0)
+ x_border_dist = ICON_SIZE_X - pixel_x_actual
+ x_to_border = x_border_dist / movement_vector.pixel_x
+ distance_to_border = x_to_border
+
+ if (movement_vector.pixel_y)
+ var/y_border_dist = -pixel_y_actual
+ if (movement_vector.pixel_y > 0)
+ y_border_dist = ICON_SIZE_Y - pixel_y_actual
+ y_to_border = y_border_dist / movement_vector.pixel_y
+ distance_to_border = min(distance_to_border, y_to_border)
+
+ // Something went extremely wrong
+ if (distance_to_border == INFINITY)
+ stack_trace("WARNING: Projectile had an empty movement vector and tried to process")
+ qdel(src)
+ return movements_done
+
+ var/distance_to_move = min(distance_to_border, pixels_to_move)
+ // For homing we cap the maximum distance to move every loop
+ if (homing && distance_to_move > SSprojectiles.pixels_per_decisecond)
+ distance_to_move = SSprojectiles.pixels_per_decisecond
+
+ // Figure out if we move to the next turf and if so, what its positioning relatively to us is
+ var/x_shift = distance_to_move >= x_to_border ? SIGN(movement_vector.pixel_x) : 0
+ var/y_shift = distance_to_move >= y_to_border ? SIGN(movement_vector.pixel_y) : 0
+ var/moving_turfs = x_shift || y_shift
+ // Calculate where in the turf we will be when we cross the edge.
+ // This is a projectile variable because its also used in hit VFX
+ entry_x = pixel_x + movement_vector.pixel_x * distance_to_move - x_shift * ICON_SIZE_X
+ entry_y = pixel_y + movement_vector.pixel_y * distance_to_move - y_shift * ICON_SIZE_Y
+ var/delete_distance = 0
+
+ if (moving_turfs)
+ var/turf/new_turf = locate(x + x_shift, y + y_shift, z)
+ // We've hit an invalid turf, end of a z level or smth went wrong
+ if (!istype(new_turf))
+ qdel(src)
+ return movements_done
+
+ // Move to the next tile
+ step_towards(src, new_turf)
+ SEND_SIGNAL(src, COMSIG_PROJECTILE_MOVE_PROCESS_STEP)
+ // We hit something and got deleted, stop the loop
+ if (QDELETED(src))
+ return movements_done
+ if (loc != new_turf)
+ moving_turfs = FALSE
+ // If we've impacted something, we need to animate our movement until the actual hit
+ // Otherwise the projectile visually disappears slightly before the actual impact
+ // Not if we're hitscan, however, microop time!
+ if (deletion_queued && !hitscan)
+ // distance_to_move is how much we have to step to get to the next turf, hypotenuse is how much we need
+ // to move in the next turf to get from entry to impact position
+ delete_distance = distance_to_move + sqrt((impact_x - entry_x) ** 2 + (impact_y - entry_y) ** 2)
+
+ movements_done += 1
+ // We cannot move more than one turf worth of distance per loop, so this is a safe solution
+ pixels_moved_last_tile += distance_to_move
+ if (!deletion_queued && pixels_moved_last_tile >= ICON_SIZE_ALL)
+ reduce_range()
+ if (QDELETED(src))
+ return
+ // Similarly with range out deletion, need to calculate how many pixels we can actually move before deleting
+ if (deletion_queued)
+ delete_distance = distance_to_move - (ICON_SIZE_ALL - pixels_moved_last_tile)
+
+ if (deletion_queued)
+ // Hitscans don't need to wait before deleting
+ if (hitscan)
+ return movements_done
+
+ // We moved to the next turf first, then impacted something
+ // This means that we need to offset our visual position back to the previous turf, then figure out
+ // how much we moved on the next turf (or we didn't move at all in which case we both shifts are 0 anyways)
+ if (moving_turfs)
+ pixel_x -= x_shift * ICON_SIZE_X
+ pixel_y -= y_shift * ICON_SIZE_Y
+
+ // Similarly to normal animate code, but use lowered deletion distance instead.
+ var/delete_x = pixel_x + movement_vector.pixel_x * delete_distance
+ var/delete_y = pixel_y + movement_vector.pixel_y * delete_distance
+ // In order to keep a consistent speed, calculate at what point between ticks we get deleted
+ var/animate_time = world.tick_lag * delete_distance / total_move_distance
+ // Sometimes we need to move *just a bit* more than we can afford this tick - in this case, delete a tick after
+ // so we don't disappear before impact. This shouldn't be more than 1, ever.
+ if (delete_distance > pixels_to_move)
+ ticks_to_deletion += 1
+ // We can use animation chains to visually disappear between ticks.
+ if (!move_animate(delete_x, delete_y, animate_time, deleting = TRUE))
+ animate(src, pixel_x = delete_x, pixel_y = delete_y, time = animate_time, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE)
+ animate(alpha = 0, time = 0, flags = ANIMATION_CONTINUE)
+ return movements_done
+
+ pixels_to_move -= distance_to_move
+ // animate() instantly changes pixel_x/y values and just interpolates them client-side so next loop processes properly
+ if (hitscan)
+ pixel_x = entry_x
+ pixel_y = entry_y
+ else
+ // We need to shift back to the tile we were on before moving
+ pixel_x -= x_shift * ICON_SIZE_X
+ pixel_y -= y_shift * ICON_SIZE_Y
+ if (!move_animate(entry_x, entry_y))
+ animate(src, pixel_x = entry_x, pixel_y = entry_y, time = world.tick_lag * distance_to_move / total_move_distance, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE)
+
+ // Homing caps our movement speed per loop while leaving per tick speed intact, so we can just call process_homing every loop here
+ if (homing)
+ process_homing()
+
+ // We've hit a timestop field, abort any remaining movement
+ if (paused)
+ return movements_done
+
+ // Prevents long-range high-speed projectiles from ruining the server performance by moving 100 tiles per tick when subsystem is set to a high cap
+ if (TICK_CHECK)
+ // If we ran out of time, add whatever distance we're yet to pass to overrun debt to be processed next tick and break the loop
+ overrun += pixels_to_move
+ return movements_done
+
+ if (tile_limit && moving_turfs)
+ return movements_done
+
+ return movements_done
+
+/// Called every time projectile animates its movement, in case child wants to have custom animations.
+/// Returning TRUE cancels normal animation
+/obj/projectile/proc/move_animate(animate_x, animate_y, animate_time = world.tick_lag, deleting = FALSE)
return FALSE
-/obj/projectile/proc/record_hitscan_start(datum/point/point_cache)
- if(point_cache)
- beam_segments = list()
- beam_index = point_cache
- beam_segments[beam_index] = null //record start.
+/// Called every projectile loop for homing or alternatively, custom trajectory changes.
+/obj/projectile/proc/process_homing()
+ if(!homing_target)
+ return
+ var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target)
+ new_point.pixel_x += homing_offset_x
+ new_point.pixel_y += homing_offset_y
+ var/new_angle = closer_angle_difference(angle, angle_between_points(RETURN_PRECISE_POINT(src), new_point))
+ set_angle(angle + clamp(new_angle, -homing_turn_speed, homing_turn_speed))
+/// Attempts to force the projectile to move until the subsystem runs out of processing time, the projectile impacts something or gets frozen by timestop
/obj/projectile/proc/process_hitscan()
- var/safety = range * 10
- record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, Angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1))
- while(loc && !QDELETED(src))
- if(paused)
- stoplag(1)
+ if (isnull(movement_vector))
+ qdel(src)
+ return
+
+ while (isturf(loc) && !QDELETED(src))
+ process_movement(ICON_SIZE_ALL, hitscan = TRUE)
+
+ if (QDELETED(src))
+ return
+
+ if (!TICK_CHECK && !paused)
continue
- if(safety-- <= 0)
- if(loc)
- Bump(loc)
- if(!QDELETED(src))
- qdel(src)
- return //Kill!
- pixel_move(1, TRUE)
-/obj/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE)
- if(!loc || !trajectory)
+ create_hitscan_point()
+ // Create tracers if we get timestopped or lagchunk so there aren't weird delays
+ generate_hitscan_tracers(impact_point = FALSE, impact_visual = FALSE)
+ record_hitscan_start(offset = FALSE)
return
- last_projectile_move = world.time
- if(!nondirectional_sprite && !hitscanning)
+
+/// Creates (or wipes clean) list of tracer keypoints and creates a first point.
+/obj/projectile/proc/record_hitscan_start(offset = TRUE)
+ if (isnull(beam_points))
+ beam_points = list()
+ else
+ QDEL_LIST_ASSOC(beam_points)
+ QDEL_NULL(last_point)
+ last_point = RETURN_PRECISE_POINT(src)
+ // If moving, increment its position a bit to prevent it from looking like its coming from firer's ass
+ if (offset && !isnull(movement_vector))
+ last_point.increment(movement_vector.pixel_x * MUZZLE_EFFECT_PIXEL_INCREMENT, movement_vector.pixel_y * MUZZLE_EFFECT_PIXEL_INCREMENT)
+ beam_points[last_point] = null
+
+/// Creates a new keypoint in which the tracer will split
+/obj/projectile/proc/create_hitscan_point(impact = FALSE, tile_center = FALSE, broken_segment = FALSE)
+ var/atom/handle_atom = last_impact_turf || src
+ var/atom/used_point = tile_center ? loc : src
+ var/datum/point/new_point = impact ? new /datum/point(handle_atom.x, handle_atom.y, handle_atom.z, impact_x, impact_y) : RETURN_PRECISE_POINT(used_point)
+ if (!broken_segment)
+ beam_points[last_point] = new_point
+ beam_points[new_point] = null
+ last_point = new_point
+
+/obj/projectile/forceMove(atom/target)
+ if (!hitscan || isnull(beam_points))
+ return ..()
+ create_hitscan_point()
+ . = ..()
+ if(!isturf(loc) || !isturf(target) || !z || QDELETED(src) || deletion_queued)
+ return
+ if (isnull(movement_vector) || free_hitscan_forceMove)
+ return
+ // Create firing VFX and start a new chain because we most likely got teleported
+ generate_hitscan_tracers(impact_point = FALSE)
+ original_angle = angle
+ spawned_muzzle = FALSE
+ record_hitscan_start(offset = FALSE)
+
+/obj/projectile/proc/generate_hitscan_tracers(impact_point = TRUE, impact_visual = TRUE)
+ if (!length(beam_points))
+ return
+
+ if (impact_point)
+ create_hitscan_point(impact = TRUE)
+
+ if (tracer_type)
+ // Stores all turfs we've created light effects on, in order to not dupe them if we enter a reflector loop
+ // Uses an assoc list for performance reasons
+ var/list/passed_turfs = list()
+ for (var/beam_point in beam_points)
+ generate_tracer(beam_point, passed_turfs)
+
+ if (muzzle_type && !spawned_muzzle)
+ spawned_muzzle = TRUE
+ var/datum/point/start_point = beam_points[1]
+ var/atom/movable/muzzle_effect = new muzzle_type(loc)
+ start_point.move_atom_to_src(muzzle_effect)
var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
- if(homing)
- process_homing()
- var/forcemoved = FALSE
- for(var/i in 1 to SSprojectiles.global_iterations_per_move)
- if(QDELETED(src) || !trajectory) //monkestation edit: adds the trajectory check
- return
- trajectory.increment(trajectory_multiplier)
- var/turf/T = trajectory.return_turf()
- if(!istype(T))
- qdel(src)
- return
- if (T == loc)
- continue
- if (T.z == loc.z)
- step_towards(src, T)
- hitscan_last = loc
- SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP)
- continue
- var/old = loc
- before_z_change(loc, T)
- trajectory_ignore_forcemove = TRUE
- forceMove(T)
- trajectory_ignore_forcemove = FALSE
- after_z_change(old, loc)
- if(!hitscanning)
- pixel_x = trajectory.return_px()
- pixel_y = trajectory.return_py()
- forcemoved = TRUE
- hitscan_last = loc
- SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP)
- if(QDELETED(src)) //deleted on last move
+ matrix.Turn(original_angle)
+ muzzle_effect.transform = matrix
+ muzzle_effect.color = color
+ muzzle_effect.set_light(muzzle_flash_range, l_power = muzzle_flash_intensity, l_color = muzzle_flash_color_override || color)
+ QDEL_IN(muzzle_effect, PROJECTILE_TRACER_DURATION)
+
+ if (impact_type && impact_visual)
+ var/atom/movable/impact_effect = new impact_type(loc)
+ last_point.move_atom_to_src(impact_effect)
+ var/matrix/matrix = new
+ matrix.Turn(angle)
+ impact_effect.transform = matrix
+ impact_effect.color = color
+ impact_effect.set_light(impact_light_outer_range, l_power = impact_light_intensity, l_color = impact_light_color_override || color)
+ QDEL_IN(impact_effect, PROJECTILE_TRACER_DURATION)
+
+/obj/projectile/proc/generate_tracer(datum/point/start_point, list/passed_turfs)
+ if (isnull(beam_points[start_point]))
return
- if(!hitscanning && !forcemoved)
- pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move
- pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move
- animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 1, flags = ANIMATION_END_NOW)
- Range()
-/obj/projectile/proc/process_homing() //may need speeding up in the future performance wise.
- if(!homing_target)
- return FALSE
- var/datum/point/PT = RETURN_PRECISE_POINT(homing_target)
- PT.x += clamp(homing_offset_x, 1, world.maxx)
- PT.y += clamp(homing_offset_y, 1, world.maxy)
- var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT))
- set_angle(Angle + clamp(angle, -homing_turn_speed, homing_turn_speed))
-
-/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
+ var/datum/point/end_point = beam_points[start_point]
+ var/datum/point/midpoint = point_midpoint_points(start_point, end_point)
+ var/obj/effect/projectile/tracer/tracer_effect = new tracer_type(midpoint.return_turf())
+ tracer_effect.apply_vars(
+ angle_override = angle_between_points(start_point, end_point),
+ p_x = midpoint.pixel_x,
+ p_y = midpoint.pixel_y,
+ color_override = color,
+ scaling = pixel_length_between_points(start_point, end_point) / ICON_SIZE_ALL
+ )
+ SET_PLANE_EXPLICIT(tracer_effect, GAME_PLANE_FOV_HIDDEN, src)
+
+ QDEL_IN(tracer_effect, PROJECTILE_TRACER_DURATION)
+
+ if (!hitscan_light_outer_range || !hitscan_light_intensity)
+ return
+
+ var/list/turf/light_line = get_line(start_point.return_turf(), end_point.return_turf())
+ for (var/turf/light_turf as anything in light_line)
+ if (passed_turfs[light_turf])
+ continue
+ passed_turfs[light_turf] = TRUE
+ QDEL_IN(new /obj/effect/abstract/projectile_lighting(light_turf, hitscan_light_color_override || color, hitscan_light_outer_range, hitscan_light_intensity), PROJECTILE_TRACER_DURATION)
/**
* Aims the projectile at a target.
@@ -1004,29 +1233,55 @@
* - deviation: (Optional) How the trajectory should deviate from the target in degrees.
* - //Spread is FORCED!
*/
-/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, list/modifiers = null, deviation = 0)
+/obj/projectile/proc/aim_projectile(atom/target, atom/source, list/modifiers = null, deviation = 0)
if(!(isnull(modifiers) || islist(modifiers)))
- stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params.")
+ stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params. Modifiers were the following: [modifiers]")
modifiers = null
var/turf/source_loc = get_turf(source)
var/turf/target_loc = get_turf(target)
+
if(isnull(source_loc))
stack_trace("WARNING: Projectile [type] fired from nullspace.")
qdel(src)
return FALSE
- trajectory_ignore_forcemove = TRUE
- forceMove(source_loc)
- trajectory_ignore_forcemove = FALSE
+ if(fired)
+ stack_trace("WARNING: Projectile [type] was aimed after already being fired.")
+ qdel(src)
+ return FALSE
+ free_hitscan_forceMove = TRUE
+ forceMove(source_loc)
starting = source_loc
pixel_x = source.pixel_x
pixel_y = source.pixel_y
original = target
+
+ // Trim off excess pixel_x/y by converting them into turf offset
+ if (abs(pixel_x) > ICON_SIZE_X / 2)
+ for (var/i in 1 to floor(abs(pixel_x) + ICON_SIZE_X / 2) / ICON_SIZE_X)
+ var/turf/new_loc = get_step(source_loc, pixel_x > 0 ? EAST : WEST)
+ if (!istype(new_loc))
+ break
+ source_loc = new_loc
+ pixel_x = pixel_x % (ICON_SIZE_X / 2)
+
+ if (abs(pixel_y) > ICON_SIZE_Y / 2)
+ for (var/i in 1 to floor(abs(pixel_y) + ICON_SIZE_Y / 2) / ICON_SIZE_Y)
+ var/turf/new_loc = get_step(source_loc, pixel_y > 0 ? NORTH : SOUTH)
+ if (!istype(new_loc))
+ break
+ source_loc = new_loc
+ pixel_y = pixel_y % (ICON_SIZE_X / 2)
+
+ // We've got moved by turf offsets
+ if (starting != source_loc)
+ starting = source_loc
+ forceMove(source_loc)
+
if(length(modifiers))
var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, target_loc && target, modifiers)
-
p_x = calculated[2]
p_y = calculated[3]
set_angle(calculated[1] + deviation)
@@ -1085,86 +1340,14 @@
//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/list/screenview = view_to_pixels(user.client.view)
- var/ox = round(screenview[1] / 2) - user.client.pixel_x //"origin" x
- var/oy = round(screenview[2] / 2) - user.client.pixel_y //"origin" y
+ var/ox = round(screenview[1] * 0.5) - user.client.pixel_x //"origin" x
+ var/oy = round(screenview[2] * 0.5) - user.client.pixel_y //"origin" y
angle = ATAN2(tx - oy, ty - ox)
return list(angle, p_x, p_y)
-/obj/projectile/Destroy()
- if(hitscan)
- finalize_hitscan_and_generate_tracers()
- STOP_PROCESSING(SSprojectiles, src)
- cleanup_beam_segments()
- if(trajectory)
- QDEL_NULL(trajectory)
- original = null
- . = ..()
- impacted?.len = 0
-
-/obj/projectile/proc/cleanup_beam_segments()
- QDEL_LIST_ASSOC(beam_segments)
- beam_segments = list()
- QDEL_NULL(beam_index)
-
-/obj/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE)
- if(trajectory && beam_index)
- var/datum/point/point_cache = trajectory.copy_to()
- beam_segments[beam_index] = point_cache
- generate_hitscan_tracers(null, null, impacting)
-
-/obj/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE)
- if(!length(beam_segments))
- return
- if(tracer_type)
- var/tempref = REF(src)
- for(var/datum/point/p in beam_segments)
- generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_outer_range, hitscan_light_color_override, hitscan_light_intensity, tempref)
- if(muzzle_type && duration > 0)
- var/datum/point/p = beam_segments[1]
- var/atom/movable/thing = new muzzle_type
- p.move_atom_to_src(thing)
- var/matrix/matrix = new
- matrix.Turn(original_angle)
- thing.transform = matrix
- thing.color = color
- thing.set_light(l_outer_range = muzzle_flash_range, l_power = muzzle_flash_intensity, l_color = muzzle_flash_color_override? muzzle_flash_color_override : color)
- QDEL_IN(thing, duration)
- if(impacting && impact_type && duration > 0)
- var/datum/point/p = beam_segments[beam_segments[beam_segments.len]]
- var/atom/movable/thing = new impact_type
- p.move_atom_to_src(thing)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- thing.transform = matrix
- thing.color = color
- thing.set_light(l_outer_range = impact_light_outer_range, l_power = impact_light_intensity, l_color = impact_light_color_override? impact_light_color_override : color)
- QDEL_IN(thing, duration)
- if(cleanup)
- cleanup_beam_segments()
-
/obj/projectile/experience_pressure_difference()
return
-///Like [/obj/item/proc/updateEmbedding] but for projectiles instead, call this when you want to add embedding or update the stats on the embedding element
-/obj/projectile/proc/updateEmbedding()
- if(!shrapnel_type || !LAZYLEN(embedding))
- return
-
- AddElement(/datum/element/embed,\
- embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\
- fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\
- pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\
- pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\
- remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\
- rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\
- ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\
- impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\
- jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\
- jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\
- pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\
- projectile_payload = shrapnel_type)
- return TRUE
-
/**
* Is this projectile considered "hostile"?
*
@@ -1184,7 +1367,7 @@
///Checks if the projectile can embed into someone
/obj/projectile/proc/can_embed_into(atom/hit)
- return embedding && shrapnel_type && iscarbon(hit) && !HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE)
+ return shrapnel_type && get_embed()?.can_embed(src, hit)
/// Reflects the projectile off of something
/obj/projectile/proc/reflect(atom/hit_atom)
@@ -1200,14 +1383,11 @@
firer = hit_atom
yo = new_y - current_tile.y
xo = new_x - current_tile.x
- var/new_angle_s = Angle + rand(120,240)
+ var/new_angle_s = angle + rand(120, 240)
while(new_angle_s > 180) // Translate to regular projectile degrees
new_angle_s -= 360
set_angle(new_angle_s)
-#undef MOVES_HITSCAN
-#undef MUZZLE_EFFECT_PIXEL_INCREMENT
-
/// Fire a projectile from this atom at another atom
/atom/proc/fire_projectile(projectile_type, atom/target, sound, firer, mixer_channel)
if (!isnull(sound))
@@ -1221,5 +1401,31 @@
bullet.yo = target.y - startloc.y
bullet.xo = target.x - startloc.x
bullet.original = target
- bullet.preparePixelProjectile(target, src)
+ bullet.aim_projectile(target, src)
bullet.fire()
+ return bullet
+
+#undef MOVES_HITSCAN
+#undef MUZZLE_EFFECT_PIXEL_INCREMENT
+
+/// Fetches, or lazyloads, our embedding datum
+/obj/projectile/proc/get_embed()
+ RETURN_TYPE(/datum/embedding)
+ if (embed_data)
+ return embed_data
+ if (embed_type)
+ embed_data = new embed_type(src)
+ return embed_data
+
+/// Sets our embedding datum to a different one. Can also take types
+/obj/projectile/proc/set_embed(datum/embedding/new_embed, dont_delete = FALSE)
+ if (new_embed == embed_data)
+ return
+
+ if (!isnull(embed_data) && !dont_delete)
+ qdel(embed_data)
+
+ if (ispath(new_embed))
+ new_embed = new new_embed()
+
+ embed_data = new_embed
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index c5ea38a0a6a8f..5d95bf0b17557 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -15,13 +15,12 @@
light_color = COLOR_SOFT_RED
ricochets_max = 50 //Honk!
ricochet_chance = 80
- reflectable = REFLECT_NORMAL
+ reflectable = TRUE
wound_bonus = -20
bare_wound_bonus = 10
/obj/projectile/beam/laser
- generic_name = "laser beam"
tracer_type = /obj/effect/projectile/tracer/laser
muzzle_type = /obj/effect/projectile/muzzle/laser
impact_type = /obj/effect/projectile/impact/laser
@@ -29,14 +28,13 @@
bare_wound_bonus = 40
/obj/projectile/beam/laser/lasrifle
- generic_name = "rifle beam"
damage = 25
range = 30
tracer_type = /obj/effect/projectile/tracer/laser/rifle
impact_type = /obj/effect/projectile/impact/laser/rifle
muzzle_type = /obj/effect/projectile/muzzle/laser/rifle
hitscan = TRUE
- tile_dropoff = 1 //This makes ricochets less impactful
+ damage_falloff_tile = -1 //This makes ricochets less impactful
armour_penetration = -30 //armor is * 130% more effective against it
wound_bonus = -15
wound_falloff_tile = 3
@@ -62,17 +60,16 @@
icon_state = "carbine_laser"
impact_effect_type = /obj/effect/temp_visual/impact_effect/yellow_laser
damage = 9
+ wound_bonus = -40
+ speed = 0.9
//overclocked laser, does a bit more damage but has much higher wound power (-0 vs -20)
/obj/projectile/beam/laser/hellfire
name = "hellfire laser"
wound_bonus = 0
damage = 25
- speed = 0.6 // higher power = faster, that's how light works right
-
-/obj/projectile/beam/laser/hellfire/Initialize(mapload)
- . = ..()
- transform *= 2
+ speed = 1.6
+ light_color = "#FF969D"
/obj/projectile/beam/laser/heavylaser
name = "heavy laser"
@@ -122,7 +119,6 @@
/obj/projectile/beam/practice
name = "practice laser"
- generic_name = "practice laser beam"
damage = 0
/obj/projectile/beam/scatter
@@ -172,7 +168,6 @@
/obj/projectile/beam/pulse
name = "pulse"
- generic_name = "pulse beam"
icon_state = "u_laser"
damage = 50
impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
@@ -219,6 +214,7 @@
return //don't want the emitters to miss
/obj/projectile/beam/emitter/hitscan
+ icon_state = null
hitscan = TRUE
muzzle_type = /obj/effect/projectile/muzzle/laser/emitter
tracer_type = /obj/effect/projectile/tracer/laser/emitter
@@ -233,7 +229,6 @@
impact_light_intensity = 7
impact_light_outer_range = 2.5
impact_light_color_override = COLOR_LIME
- range = 255 //come on, have some fun now! monkestation edit
/obj/projectile/beam/lasertag
name = "laser tag beam"
@@ -263,6 +258,7 @@
impact_type = /obj/effect/projectile/impact/laser
/obj/projectile/beam/lasertag/redtag/hitscan
+ icon_state = null
hitscan = TRUE
/obj/projectile/beam/lasertag/bluetag
@@ -273,6 +269,7 @@
impact_type = /obj/effect/projectile/impact/laser/blue
/obj/projectile/beam/lasertag/bluetag/hitscan
+ icon_state = null
hitscan = TRUE
/obj/projectile/magic/shrink/alien
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index 59327ea104099..c17974893ba1a 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -8,11 +8,11 @@
sharpness = SHARP_POINTY
impact_effect_type = /obj/effect/temp_visual/impact_effect
shrapnel_type = /obj/item/shrapnel/bullet
- embedding = list(embed_chance=20, fall_chance=2, jostle_chance=0, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.5, pain_mult=3, rip_time=10)
+ embed_type = /datum/embedding/bullet
wound_bonus = 0
wound_falloff_tile = -5
embed_falloff_tile = -3
-
+ speed = 1.7
//light_system = OVERLAY_LIGHT
//light_outer_range = 1.25
//light_power = 1
@@ -20,8 +20,15 @@
//light_on = TRUE
//This applies to casings too, for some reason, and regardless this shouldn't apply to every bullet not every bullet should or does have a tracer.
- speed = 0.4 //twice as fast
-
/obj/projectile/bullet/smite
name = "divine retribution"
damage = 10
+
+/datum/embedding/bullet
+ embed_chance=20
+ fall_chance=2
+ jostle_chance=0
+ ignore_throwspeed_threshold=TRUE
+ pain_stam_pct=0.5
+ pain_mult=3
+ rip_time=10
diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm
index 5b4d070b60de6..d6863d724d9f8 100644
--- a/code/modules/projectiles/projectile/bullets/_incendiary.dm
+++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm
@@ -30,12 +30,12 @@
pass_flags = PASSTABLE | PASSMOB
sharpness = NONE
shrapnel_type = null
- embedding = null
+ embed_type = null
impact_effect_type = null
suppressed = SUPPRESSED_VERY
damage_type = BURN
armor_flag = BOMB
- speed = 1.2
+ speed = 0.8
wound_bonus = 30
bare_wound_bonus = 30
wound_falloff_tile = -4
@@ -95,7 +95,7 @@
var/turf/current_turf = get_turf(src)
if(!current_turf)
return
- var/turf/throw_at_turf = get_turf_in_angle(Angle, current_turf, 7)
+ var/turf/throw_at_turf = get_turf_in_angle(angle, current_turf, 7)
var/thrown_items = 0
for(var/iter in current_turf.contents)
diff --git a/code/modules/projectiles/projectile/bullets/cannonball.dm b/code/modules/projectiles/projectile/bullets/cannonball.dm
index 2f57a3dcc99bd..d47d95f9b9174 100644
--- a/code/modules/projectiles/projectile/bullets/cannonball.dm
+++ b/code/modules/projectiles/projectile/bullets/cannonball.dm
@@ -8,7 +8,7 @@
dismemberment = 0
paralyze = 5 SECONDS
stutter = 20 SECONDS
- embedding = null
+ embed_type = null
hitsound = 'sound/effects/meteorimpact.ogg'
hitsound_wall = 'sound/weapons/sonic_jackhammer.ogg'
/// If our cannonball hits something, it reduces the damage by this value.
diff --git a/code/modules/projectiles/projectile/bullets/crossbow.dm b/code/modules/projectiles/projectile/bullets/crossbow.dm
index 1be3c58fd390a..c063797f9476f 100644
--- a/code/modules/projectiles/projectile/bullets/crossbow.dm
+++ b/code/modules/projectiles/projectile/bullets/crossbow.dm
@@ -4,17 +4,27 @@
name = "rebar"
icon_state = "rebar"
damage = 30
- speed = 0.4
+ speed = 2.5
dismemberment = 1 //because a 1 in 100 chance to just blow someones arm off is enough to be cool but also not enough to be reliable
armour_penetration = 20
wound_bonus = -20
bare_wound_bonus = 20
- embedding = list("embed_chance" = 60, "fall_chance" = 2, "jostle_chance" = 2, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.4, "pain_mult" = 4, "jostle_pain_mult" = 2, "rip_time" = 10)
+ embed_type = /datum/embedding/rebar
embed_falloff_tile = -5
wound_falloff_tile = -2
shrapnel_type = /obj/item/ammo_casing/rebar
accuracy_falloff = 7
+/datum/embedding/rebar
+ embed_chance = 60
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 4
+ jostle_pain_mult = 2
+ rip_time = 10
+
/obj/projectile/bullet/rebar/proc/handle_drop(datum/source, obj/item/ammo_casing/rebar/newcasing)
/obj/projectile/bullet/rebar/syndie
@@ -23,7 +33,6 @@
damage = 60
dismemberment = 2 //It's a budget sniper rifle.
armour_penetration = 30 //A bit better versus armor.
- armour_ignorance = 10 //Makes it one tap limbs against about the same level of armour as it did before the AP rework
wound_bonus = 10
bare_wound_bonus = 20
embed_falloff_tile = -3
@@ -33,22 +42,32 @@
name = "zaukerite shard"
icon_state = "rebar_zaukerite"
damage = 60
- speed = 0.6
+ speed = 1.6
dismemberment = 10
damage_type = TOX
eyeblur = 5
armour_penetration = 20 // not nearly as good, as its not as sharp.
wound_bonus = 10
bare_wound_bonus = 40
- embedding = list("embed_chance" =100, "fall_chance" = 0, "jostle_chance" = 5, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.8, "pain_mult" = 6, "jostle_pain_mult" = 2, "rip_time" = 30)
+ embed_type = /datum/embedding/zaukerite
embed_falloff_tile = 0 // very spiky.
shrapnel_type = /obj/item/ammo_casing/rebar/zaukerite
+/datum/embedding/zaukerite
+ embed_chance = 100
+ fall_chance = 0
+ jostle_chance = 5
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.8
+ pain_mult = 6
+ jostle_pain_mult = 2
+ rip_time = 30
+
/obj/projectile/bullet/rebar/hydrogen
name = "metallic hydrogen bolt"
icon_state = "rebar_hydrogen"
damage = 45
- speed = 0.6
+ speed = 1.6
projectile_piercing = PASSMOB|PASSVEHICLE
projectile_phasing = ~(PASSMOB|PASSVEHICLE)
max_pierces = 3
@@ -60,12 +79,22 @@
wound_bonus = -15
bare_wound_bonus = 10
shrapnel_type = /obj/item/ammo_casing/rebar/hydrogen
- embedding = list("embed_chance" = 50, "fall_chance" = 2, "jostle_chance" = 3, "ignore_throwspeed_threshold" = TRUE, "pain_stam_pct" = 0.6, "pain_mult" = 4, "jostle_pain_mult" = 2, "rip_time" =18)
+ embed_type = /datum/embedding/hydrogen
embed_falloff_tile = -3
shrapnel_type = /obj/item/ammo_casing/rebar/hydrogen
accurate_range = 205 //15 tiles before falloff starts to kick in
-/obj/projectile/bullet/rebar/hydrogen/Impact(atom/A) // TODO projectile refactor
+/datum/embedding/hydrogen
+ embed_chance = 50
+ fall_chance = 2
+ jostle_chance = 3
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.6
+ pain_mult = 4
+ jostle_pain_mult = 2
+ rip_time = 18
+
+/obj/projectile/bullet/rebar/hydrogen/impact(atom/A) // TODO projectile refactor
. = ..()
def_zone = ran_zone(def_zone, clamp(205-(7*get_dist(get_turf(A), starting)), 5, 100))
@@ -83,7 +112,7 @@
armour_penetration = 100
wound_bonus = -100
bare_wound_bonus = -100
- embedding = list(embed_chance = 0)
+ embed_type = null
embed_falloff_tile = -3
shrapnel_type = /obj/item/ammo_casing/rebar/healium
@@ -146,5 +175,15 @@
armour_penetration = 80
wound_bonus = -20
bare_wound_bonus = 80
- embedding = list(embed_chance=100, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embedding/harpoon
wound_falloff_tile = -5
+
+/datum/embedding/harpoon
+ embed_chance = 100
+ fall_chance = 3
+ jostle_chance = 4
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 5
+ jostle_pain_mult = 6
+ rip_time = 10
diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
index 405552a8909c2..1bd88d75fdea0 100644
--- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm
+++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
@@ -2,7 +2,7 @@
name = "dart"
icon_state = "cbbolt"
damage = 6
- embedding = null
+ embed_type = null
shrapnel_type = null
var/inject_flags = null
diff --git a/code/modules/projectiles/projectile/bullets/dnainjector.dm b/code/modules/projectiles/projectile/bullets/dnainjector.dm
index fdb051e7f8006..b4029b07f2da6 100644
--- a/code/modules/projectiles/projectile/bullets/dnainjector.dm
+++ b/code/modules/projectiles/projectile/bullets/dnainjector.dm
@@ -4,7 +4,7 @@
var/obj/item/dnainjector/injector
damage = 5
hitsound_wall = SFX_SHATTER
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/dnainjector/on_hit(atom/target, blocked = 0, pierce_hit)
diff --git a/code/modules/projectiles/projectile/bullets/foam_dart.dm b/code/modules/projectiles/projectile/bullets/foam_dart.dm
new file mode 100644
index 0000000000000..c1091771e417e
--- /dev/null
+++ b/code/modules/projectiles/projectile/bullets/foam_dart.dm
@@ -0,0 +1,39 @@
+/obj/projectile/bullet/foam_dart
+ name = "foam dart"
+ desc = "I hope you're wearing eye protection."
+ damage = 0 // It's a damn toy.
+ damage_type = OXY
+ icon = 'icons/obj/weapons/guns/toy.dmi'
+ icon_state = "foamdart_proj"
+ base_icon_state = "foamdart_proj"
+ range = 10
+ embed_type = null
+ var/modified = FALSE
+ var/obj/item/pen/pen = null
+
+/obj/projectile/bullet/foam_dart/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(handle_drop))
+
+/obj/projectile/bullet/foam_dart/proc/handle_drop(datum/source, obj/item/ammo_casing/foam_dart/newcasing)
+ SIGNAL_HANDLER
+ newcasing.modified = modified
+ var/obj/projectile/bullet/foam_dart/newdart = newcasing.loaded_projectile
+ newdart.modified = modified
+ newdart.damage_type = damage_type
+ if(pen)
+ newdart.pen = pen
+ pen.forceMove(newdart)
+ pen = null
+ newdart.damage = 5
+ newdart.update_appearance()
+
+/obj/projectile/bullet/foam_dart/Destroy()
+ pen = null
+ return ..()
+
+/obj/projectile/bullet/foam_dart/riot
+ name = "riot foam dart"
+ icon_state = "foamdart_riot_proj"
+ base_icon_state = "foamdart_riot_proj"
+ stamina = 25
diff --git a/code/modules/projectiles/projectile/bullets/grenade.dm b/code/modules/projectiles/projectile/bullets/grenade.dm
index e226f023a17bd..d4e3829677ee6 100644
--- a/code/modules/projectiles/projectile/bullets/grenade.dm
+++ b/code/modules/projectiles/projectile/bullets/grenade.dm
@@ -5,11 +5,11 @@
icon = 'monkestation/icons/obj/guns/40mm_grenade.dmi'
icon_state = "40mm_projectile"
damage = 60
- embedding = null
+ embed_type = null
shrapnel_type = null
range = 30
-/obj/projectile/bullet/a40mm/Range() //because you lob the grenade to achieve the range :)
+/obj/projectile/bullet/a40mm/reduce_range() //because you lob the grenade to achieve the range :)
if(!has_gravity(get_area(src)))
range++
return ..()
diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm
index dbd5cc9f92beb..8bff2b4f1c906 100644
--- a/code/modules/projectiles/projectile/bullets/lmg.dm
+++ b/code/modules/projectiles/projectile/bullets/lmg.dm
@@ -1,33 +1,27 @@
// C3D (Borgs)
/obj/projectile/bullet/c3d
- generic_name = "bullet"
damage = 20
// Mech LMG
/obj/projectile/bullet/lmg
- generic_name = "bullet"
damage = 20
// Mech FNX-99
/obj/projectile/bullet/incendiary/fnx99
- generic_name = "bullet"
damage = 20
// Turrets
/obj/projectile/bullet/manned_turret
- generic_name = "bullet"
damage = 20
/obj/projectile/bullet/manned_turret/hmg
- generic_name = "bullet"
icon_state = "redtrac"
/obj/projectile/bullet/syndicate_turret
- generic_name = "bullet"
damage = 20
// 7.12x82mm (SAW)
@@ -43,7 +37,7 @@
/obj/projectile/bullet/mm712x82/ap
name = "7.12x82mm armor-piercing bullet"
armour_penetration = 85
- speed = 0.3 //monke edit
+ speed = 3.3 //monke edit
/obj/projectile/bullet/mm712x82/hp
name = "7.12x82mm hollow-point bullet"
@@ -58,7 +52,7 @@
name = "7.12x82mm incendiary bullet"
damage = 15
fire_stacks = 3
- speed = 0.6 //monke edit
+ speed = 1.6 //monke edit
/obj/projectile/bullet/mm712x82/match
name = "7.12x82mm match bullet"
@@ -66,7 +60,7 @@
ricochet_chance = 60
ricochet_auto_aim_range = 4
ricochet_incidence_leeway = 55
- speed = 0.3 //monke edit
+ speed = 3.3 //monke edit
/obj/projectile/bullet/mm712x82/bouncy
name = "7.12x82mm rubber bullet"
@@ -76,7 +70,7 @@
ricochet_auto_aim_range = 4
ricochet_incidence_leeway = 0
ricochet_decay_chance = 0.9
- speed = 0.6 //monke edit
+ speed = 1.6 //monke edit
// 12.7x70mm (Malone / tank MG)
@@ -119,7 +113,7 @@
/obj/projectile/bullet/c65xeno/pierce
name = "6.5mm subcaliber tungsten sabot round"
icon_state = "gaussphase"
- speed = 0.3
+ speed = 3.3
damage = 6
armour_penetration = 60
wound_bonus = 5
@@ -142,7 +136,7 @@
/obj/projectile/bullet/c65xeno/pierce/evil
name = "6.5mm UDS"
icon_state = "gaussphase"
- speed = 0.3
+ speed = 3.3
damage = 7
armour_penetration = 60
wound_bonus = 10
@@ -162,7 +156,7 @@
icon_state = "redtrac"
damage = 5
bare_wound_bonus = 0
- speed = 0.7 ///half of standard
+ speed = 1.4 ///half of standard
/// How many firestacks the bullet should impart upon a target when impacting
biotype_damage_multiplier = 4
var/firestacks_to_give = 1
@@ -181,7 +175,7 @@
icon_state = "redtrac"
damage = 10
bare_wound_bonus = 0
- speed = 0.7 ///half of standard
+ speed = 1.4 ///half of standard
/// How many firestacks the bullet should impart upon a target when impacting
biotype_damage_multiplier = 4
projectile_piercing = PASSMOB
diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm
index 216f5916ac96a..74cf8d8fc6eab 100644
--- a/code/modules/projectiles/projectile/bullets/pistol.dm
+++ b/code/modules/projectiles/projectile/bullets/pistol.dm
@@ -3,13 +3,23 @@
/obj/projectile/bullet/c9mm
name = "9mm bullet"
damage = 30
- embedding = list(embed_chance=15, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ embed_type = /datum/embedding/bullet_c9mm
+
+/datum/embedding/bullet_c9mm
+ embed_chance = 15
+ fall_chance = 3
+ jostle_chance = 4
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 5
+ jostle_pain_mult = 6
+ rip_time = 10
/obj/projectile/bullet/c9mm/ap
name = "9mm armor-piercing bullet"
damage = 27
armour_penetration = 75
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/c9mm/hp
@@ -72,7 +82,7 @@
ricochet_decay_damage = 0.8
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/c35sol/ripper // .35 Sol ripper, similar to the detective revolver's dumdum rounds, causes slash wounds and is weak to armor
name = ".35 Sol ripper bullet"
@@ -81,19 +91,19 @@
sharpness = SHARP_EDGED
wound_bonus = 20
bare_wound_bonus = 20
- embedding = list(
- embed_chance = 75,
- fall_chance = 3,
- jostle_chance = 4,
- ignore_throwspeed_threshold = TRUE,
- pain_stam_pct = 0.4,
- pain_mult = 5,
- jostle_pain_mult = 6,
- rip_time = 1 SECONDS,
- )
-
+ embed_type = /datum/embedding/c35sol
embed_falloff_tile = -15
+/datum/embedding/c35sol
+ embed_chance = 75
+ fall_chance = 3
+ jostle_chance = 4
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 5
+ jostle_pain_mult = 6
+ rip_time = 1 SECONDS
+
/obj/projectile/bullet/c35sol/pierce // What it says on the tin, AP rounds
name = ".35 Sol Short armor piercing bullet"
damage = 13
@@ -116,7 +126,7 @@
weak_against_armour = TRUE
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/c585trappiste/hollowpoint
name = ".585 Trappiste hollowhead bullet"
@@ -140,4 +150,4 @@
damage = 4
stamina = 24
sharpness = NONE
- embedding = null
+ embed_type = null
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index 1224d2be5633c..c0255bade612d 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -9,9 +9,19 @@
ricochet_auto_aim_range = 3
wound_bonus = -20
bare_wound_bonus = 10
- embedding = list(embed_chance=25, fall_chance=2, jostle_chance=2, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=5, rip_time=1 SECONDS)
+ embed_type = /datum/embedding/n762
embed_falloff_tile = -4
+/datum/embedding/n762
+ embed_chance = 25
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ jostle_pain_mult = 5
+ rip_time = 1 SECONDS
+
// .50AE (Desert Eagle)
/obj/projectile/bullet/a50ae
@@ -29,9 +39,19 @@
ricochet_auto_aim_range = 3
wound_bonus = -20
bare_wound_bonus = 10
- embedding = list(embed_chance=25, fall_chance=2, jostle_chance=2, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=5, rip_time=1 SECONDS)
+ embed_type = /datum/embedding/bullet_c38
embed_falloff_tile = -4
+/datum/embedding/bullet_c38
+ embed_chance = 25
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ jostle_pain_mult = 5
+ rip_time = 1 SECONDS
+
/obj/projectile/bullet/c38/match
name = ".38 Match bullet"
ricochets_max = 4
@@ -53,7 +73,7 @@
ricochet_decay_damage = 1 //0.8 to 1 monkestation edit let them have fun
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
// premium .38 ammo from cargo, weak against armor, lower base damage, but excellent at embedding and causing slice wounds at close range
/obj/projectile/bullet/c38/dumdum
@@ -64,10 +84,20 @@
sharpness = SHARP_EDGED
wound_bonus = 20
bare_wound_bonus = 20
- embedding = list(embed_chance=75, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=1 SECONDS)
+ embed_type = /datum/embedding/bullet_c38_dumdum
wound_falloff_tile = -5
embed_falloff_tile = -15
+/datum/embedding/bullet_c38_dumdum
+ embed_chance = 75
+ fall_chance = 3
+ jostle_chance = 4
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 5
+ jostle_pain_mult = 6
+ rip_time = 1 SECONDS
+
/obj/projectile/bullet/c38/trac
name = ".38 TRAC bullet"
damage = 10
@@ -138,7 +168,7 @@
stamina = 17.5
weak_against_armour = TRUE
sharpness = NONE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/g45l
name = ".45 Long bullet"
@@ -146,8 +176,17 @@
weak_against_armour = TRUE // High fire rate
wound_bonus = -10
sharpness = SHARP_EDGED
- embedding = list(embed_chance=25, fall_chance=2, jostle_chance=2, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=5, rip_time=1 SECONDS)
-
+ embed_type = /datum/embedding/g45l
+
+/datum/embedding/g45l
+ embed_chance = 25
+ fall_chance = 2
+ jostle_chance = 2
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 3
+ jostle_pain_mult = 5
+ rip_time = 1 SECONDS
//.45-70, mining revolver
diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm
index df100d9e3fdff..b7790872bc93b 100644
--- a/code/modules/projectiles/projectile/bullets/rifle.dm
+++ b/code/modules/projectiles/projectile/bullets/rifle.dm
@@ -5,14 +5,12 @@
name = "7.62 bullet"
damage = 60
armour_penetration = 0
- armour_ignorance = 10
wound_bonus = -45
wound_falloff_tile = 0
/obj/projectile/bullet/a762/surplus
name = "7.62 surplus bullet"
weak_against_armour = TRUE //this is specifically more important for fighting carbons than fighting noncarbons. Against a simple mob, this is still a full force bullet
- armour_ignorance = 0
/obj/projectile/bullet/a762/enchanted
name = "enchanted 7.62 bullet"
@@ -72,18 +70,19 @@
wound_bonus = -5
bare_wound_bonus = 10
shrapnel_type = /obj/item/shrapnel/stingball
- embedding = list(
- embed_chance = 50,
- fall_chance = 5,
- jostle_chance = 5,
- ignore_throwspeed_threshold = TRUE,
- pain_stam_pct = 0.4,
- pain_mult = 2,
- jostle_pain_mult = 3,
- rip_time = 0.5 SECONDS,
- )
+ embed_type = /datum/embedding/c40sol
embed_falloff_tile = -5
+/datum/embedding/c40sol
+ embed_chance = 50
+ fall_chance = 5
+ jostle_chance = 5
+ ignore_throwspeed_threshold = TRUE
+ pain_stam_pct = 0.4
+ pain_mult = 2
+ jostle_pain_mult = 3
+ rip_time = 0.5 SECONDS
+
/obj/projectile/bullet/c40sol/pierce
name = ".40 Sol match bullet"
icon_state = "gaussphase"
@@ -128,8 +127,6 @@
gaslighter.adjust_fire_stacks(firestacks_to_give)
gaslighter.ignite_mob()
-
-
///.310 Strilka, like 7.62 nagant but also not
/obj/projectile/bullet/strilka310
@@ -154,7 +151,7 @@
ricochet_decay_damage = 0.7
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/strilka310/ap
name = ".310 armor-piercing bullet"
@@ -163,4 +160,3 @@
wound_falloff_tile = -2
wound_bonus = -45
speed = 0.3
-
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index 303bb93edb66e..a126aa98f0d0f 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -19,14 +19,13 @@
name = "tungsten sabot-slug"
icon_state = "gauss"
damage = 25 //10 less than slugs.
- speed = 0.25 //sub-caliber + lighter = speed. (Smaller number = faster)
+ speed = 4 //sub-caliber + lighter = speed.
armour_penetration = 25
wound_bonus = -25
ricochets_max = 2 //Unlike slugs which tend to squish on impact, these are hard enough to bounce rarely.
ricochet_chance = 50
ricochet_auto_aim_range = 0
ricochet_incidence_leeway = 50
- embedding = null
demolition_mod = 2 //High-velocity tungsten > steel doors
projectile_piercing = PASSMOB
@@ -49,7 +48,6 @@
stamina = 55
wound_bonus = 20
sharpness = NONE
- embedding = null
/obj/projectile/bullet/incendiary/shotgun
name = "incendiary slug"
@@ -61,13 +59,9 @@
damage = 35
leaves_fire_trail = FALSE
-
-
/obj/projectile/bullet/pellet
icon_state = "pellet"
- tile_dropoff = 0.45
- tile_dropoff_s = 0.25
- sharpness = SHARP_POINTY
+ damage_falloff_tile = -0.45
/obj/projectile/bullet/pellet/shotgun_buckshot ///6 pellets
name = "buckshot pellet"
@@ -81,9 +75,10 @@
damage = 2 //monkestation edit 3 to 2
stamina = 10
sharpness = NONE
- embedding = null
- tile_dropoff_s = 0 //monkestation edit
- speed = 1.2
+ embed_type = null
+ speed = 0.8
+ stamina_falloff_tile = 0
+ damage_falloff_tile = 0
ricochets_max = 4
ricochet_chance = 120
ricochet_decay_chance = 0.9
@@ -94,7 +89,7 @@
/// Subtracted from the ricochet chance for each tile traveled
var/tile_dropoff_ricochet = 4
-/obj/projectile/bullet/pellet/shotgun_rubbershot/Range()
+/obj/projectile/bullet/pellet/shotgun_rubbershot/reduce_range()
if(ricochet_chance > 0)
ricochet_chance -= tile_dropoff_ricochet
. = ..()
@@ -136,10 +131,10 @@
/obj/projectile/bullet/pellet/shotgun_improvised ///8 pellets
- tile_dropoff = 0.35 //Come on it does 6 damage don't be like that.
damage = 6
wound_bonus = 0
bare_wound_bonus = 5
+ damage_falloff_tile = -0.35
/obj/projectile/bullet/pellet/shotgun_improvised/Initialize(mapload)
. = ..()
@@ -150,7 +145,6 @@
..()
-
/obj/projectile/bullet/incendiary/shotgun/dragonsbreath ///4 pellets
name = "dragonsbreath pellet"
damage = 5
@@ -164,7 +158,6 @@
range = 7
icon_state = "spark"
color = "#FFFF00"
- embedding = null
/obj/projectile/bullet/shotgun_frag12
name ="frag12 slug"
@@ -187,7 +180,7 @@
/obj/projectile/bullet/pellet/trickshot
name = "trickshot pellet"
damage = 6
- tile_dropoff = 0
+ damage_falloff_tile = 0
ricochets_max = 5
ricochet_chance = 100
ricochet_decay_chance = 0
@@ -198,8 +191,7 @@
name = "incapacitating pellet"
damage = 1
stamina = 6
- tile_dropoff_s = 3 //monkestation edit spitting distance
- embedding = null
+ stamina_falloff_tile = -3
/obj/projectile/bullet/pellet/shotgun_buckshot/beehive ///4 pellets
name = "hornet flechette"
@@ -232,7 +224,6 @@
eyeblur = 1 SECONDS
sharpness = NONE
range = 7
- embedding = list(embed_chance=75, pain_chance=50, fall_chance=15, jostle_chance=80, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.9, pain_mult=2, rip_time=10)
/obj/projectile/bullet/pellet/shotgun_buckshot/antitide/on_range()
do_sparks(1, TRUE, src)
@@ -251,6 +242,7 @@
hitsound = SFX_CLOWN_STEP
range = 4
icon_state = "guardian"
+ embed_data = null
/obj/projectile/bullet/honkshot/Initialize(mapload)
. = ..()
@@ -338,7 +330,6 @@
/obj/projectile/bullet/pellet/beeshot/proc/afterspawn(list/mob/spawned)
return
-
// Mech Scattershot
/obj/projectile/bullet/scattershot
diff --git a/code/modules/projectiles/projectile/bullets/smg.dm b/code/modules/projectiles/projectile/bullets/smg.dm
index 62a297b183102..9353902327940 100644
--- a/code/modules/projectiles/projectile/bullets/smg.dm
+++ b/code/modules/projectiles/projectile/bullets/smg.dm
@@ -17,7 +17,7 @@
ricochet_decay_damage = 1
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
/obj/projectile/bullet/c45/ap
name = ".45 armor-piercing bullet"
@@ -50,7 +50,7 @@
name = "4.6x30mm armor-piercing bullet"
damage = 15
armour_penetration = 40
- embedding = null
+ embed_type = null
/obj/projectile/bullet/incendiary/c46x30mm
name = "4.6x30mm incendiary bullet"
@@ -61,14 +61,14 @@
name = "4.6x30mm saltshot bullet"
damage = 0
stamina = 20
- embedding = null
+ embed_type = null
sharpness = NONE
/obj/projectile/bullet/c46x30mm/rub
name = "4.6x30mm rubber bullet"
damage = 4
stamina = 25
- embedding = null
+ embed_type = null
sharpness = NONE
diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm
index 4045de994d0ca..e4b8ed071368b 100644
--- a/code/modules/projectiles/projectile/bullets/sniper.dm
+++ b/code/modules/projectiles/projectile/bullets/sniper.dm
@@ -2,7 +2,7 @@
/obj/projectile/bullet/p50
name =".50 BMG bullet"
- speed = 0.4
+ speed = 2.5
range = 400 // Enough to travel from one corner of the Z to the opposite corner and then some.
damage = 70
paralyze = 100
@@ -88,11 +88,12 @@
name = ".50 BMG aggression dissuasion round"
icon_state = "gaussstrong"
damage = 25
- speed = 0.3
+ speed = 3
range = 16
/obj/projectile/bullet/p50/marksman
name = ".50 BMG marksman round"
+ icon_state = null
damage = 50
range = 50
paralyze = 0
diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm
index 49e8a03022c0f..f47f0ddcd8930 100644
--- a/code/modules/projectiles/projectile/bullets/special.dm
+++ b/code/modules/projectiles/projectile/bullets/special.dm
@@ -9,7 +9,6 @@
icon = 'icons/obj/hydroponics/harvest.dmi'
icon_state = "banana"
range = 200
- embedding = null
shrapnel_type = null
/obj/projectile/bullet/honker/Initialize(mapload)
@@ -44,6 +43,7 @@
/// Marksman Shot
/obj/projectile/bullet/marksman
name = "marksman nanoshot"
+ icon_state = null
hitscan = TRUE
damage = 30
tracer_type = /obj/effect/projectile/tracer/solar
@@ -73,14 +73,13 @@
return ..()
coin_check.check_splitshot(firer, src)
- Impact(coin_check)
+ impact(coin_check)
/// Marksman Coin
/obj/projectile/bullet/coin
name = "marksman coin"
icon_state = "coinshot"
- pixel_speed_multiplier = 0.333
- speed = 1
+ speed = 0.33
damage = 5
color = "#dbdd4c"
@@ -100,7 +99,7 @@
/obj/projectile/bullet/coin/Initialize(mapload, turf/the_target, mob/original_firer)
src.original_firer = original_firer
target_turf = the_target
- range = (get_dist(original_firer, target_turf) + 3) * 3 // 3 tiles past the origin (the *3 is because Range() ticks 3 times a tile because of the slower speed)
+ range = (get_dist(original_firer, target_turf) + 3) * 3 // 3 tiles past the origin (the *3 is because reduce_range() ticks 3 times a tile because of the slower speed)
. = ..()
@@ -167,7 +166,7 @@
if(Adjacent(current_turf, target_turf))
new_splitshot.fire(get_angle(current_turf, target_turf), direct_target = next_target)
else
- new_splitshot.preparePixelProjectile(next_target, get_turf(src))
+ new_splitshot.aim_projectile(next_target, get_turf(src))
new_splitshot.fire()
if(istype(next_target, /obj/projectile/bullet/coin)) // handle further splitshot checks
diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm
index 6b715fdb74f5b..dc3a33b28783c 100644
--- a/code/modules/projectiles/projectile/energy/_energy.dm
+++ b/code/modules/projectiles/projectile/energy/_energy.dm
@@ -4,7 +4,7 @@
damage = 0
damage_type = BURN
armor_flag = ENERGY
- reflectable = REFLECT_NORMAL
+ reflectable = TRUE
impact_effect_type = /obj/effect/temp_visual/impact_effect/energy
/obj/projectile/energy/Initialize(mapload)
diff --git a/code/modules/projectiles/projectile/energy/kinetic.dm b/code/modules/projectiles/projectile/energy/kinetic.dm
index b901dabd9898a..bebb8ccaa784d 100644
--- a/code/modules/projectiles/projectile/energy/kinetic.dm
+++ b/code/modules/projectiles/projectile/energy/kinetic.dm
@@ -110,7 +110,7 @@
damage = 100
range = 7
pressure_decrease = 0.10 // Pressured enviorments are a no go for the railgun
- speed = 0.1 // NYOOM
+ speed = 10 // NYOOM
projectile_piercing = PASSMOB
diff --git a/code/modules/projectiles/projectile/energy/nuclear_particle.dm b/code/modules/projectiles/projectile/energy/nuclear_particle.dm
index 0bd983b70e22e..5805d0db496e8 100644
--- a/code/modules/projectiles/projectile/energy/nuclear_particle.dm
+++ b/code/modules/projectiles/projectile/energy/nuclear_particle.dm
@@ -6,7 +6,7 @@
armor_flag = ENERGY
damage_type = TOX
damage = 10
- speed = 0.4
+ speed = 2.5
hitsound = 'sound/weapons/emitter2.ogg'
impact_type = /obj/effect/projectile/impact/xray
var/static/list/particle_colors = list(
diff --git a/code/modules/projectiles/projectile/energy/tesla.dm b/code/modules/projectiles/projectile/energy/tesla.dm
index 4a9a9a1068b4a..88d192401d25d 100644
--- a/code/modules/projectiles/projectile/energy/tesla.dm
+++ b/code/modules/projectiles/projectile/energy/tesla.dm
@@ -29,7 +29,7 @@
name = "tesla orb"
icon_state = "ice_1"
damage = 0
- speed = 1.5
+ speed = 0.66
var/shock_damage = 5
/obj/projectile/energy/tesla_cannon/on_hit(atom/target, blocked = 0, pierce_hit)
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index c3b8822fe17aa..920a367d3819e 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -263,7 +263,8 @@
/obj/projectile/magic/locker/Destroy()
locker_suck = FALSE
- RemoveElement(/datum/element/connect_loc, projectile_connections) //We do this manually so the forcemoves don't "hit" us. This behavior is kinda dumb, someone refactor this
+ if (last_tick_turf)
+ UnregisterSignal(last_tick_turf, COMSIG_ATOM_ENTERED)
for(var/atom/movable/AM in contents)
AM.forceMove(get_turf(src))
. = ..()
@@ -312,7 +313,7 @@
/obj/projectile/magic/flying/on_hit(mob/living/target, blocked = 0, pierce_hit)
. = ..()
if(isliving(target))
- var/atom/throw_target = get_edge_target_turf(target, angle2dir(Angle))
+ var/atom/throw_target = get_edge_target_turf(target, angle2dir(angle))
target.throw_at(throw_target, 200, 4)
/obj/projectile/magic/bounty
@@ -432,9 +433,11 @@
/// The icon the trail uses.
var/trail_icon = 'icons/obj/wizard.dmi'
/// The icon state the trail uses.
- var/trail_icon_state = "trail"
+ var/trail_icon_state = "arrow"
+ /// Can we spawn a trail effect again?
+ COOLDOWN_DECLARE(trail_cooldown)
-/obj/projectile/magic/aoe/Range()
+/obj/projectile/magic/aoe/reduce_range()
if(trigger_range >= 1)
for(var/mob/living/nearby_guy in range(trigger_range, get_turf(src)))
if(nearby_guy.stat == DEAD)
@@ -446,38 +449,31 @@
return ..()
-/obj/projectile/magic/aoe/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE)
+/obj/projectile/magic/aoe/prehit_pierce(atom/target)
if(can_only_hit_target && target != original)
- return FALSE
+ return PROJECTILE_PIERCE_PHASE
return ..()
-/obj/projectile/magic/aoe/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
- . = ..()
- if(trail)
- create_trail()
-
-/// Creates and handles the trail that follows the projectile.
-/obj/projectile/magic/aoe/proc/create_trail()
- if(!trajectory)
+/obj/projectile/magic/aoe/move_animate(animate_x, animate_y, animate_time = world.tick_lag, deleting = FALSE)
+ if(!trail || !movement_vector || deleting || !COOLDOWN_FINISHED(src, trail_cooldown))
return
- var/datum/point/vector/previous = trajectory.return_vector_after_increments(1, -1)
- var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf())
- trail.pixel_x = previous.return_px()
- trail.pixel_y = previous.return_py()
- trail.icon = trail_icon
- trail.icon_state = trail_icon_state
- //might be changed to temp overlay
- trail.set_density(FALSE)
- trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- QDEL_IN(trail, trail_lifespan)
+ var/obj/effect/overlay/trail_effect = new /obj/effect/overlay(loc)
+ trail_effect.pixel_x = pixel_x
+ trail_effect.pixel_y = pixel_y
+ trail_effect.icon = trail_icon
+ trail_effect.icon_state = trail_icon_state
+ trail_effect.set_density(FALSE)
+ trail_effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ QDEL_IN(trail_effect, trail_lifespan)
+ COOLDOWN_START(src, trail_cooldown, trail_lifespan)
/obj/projectile/magic/aoe/lightning
name = "lightning bolt"
icon_state = "tesla_projectile" //Better sprites are REALLY needed and appreciated!~
damage = 15
damage_type = BURN
- speed = 0.3
+ speed = 3.5
/// The power of the zap itself when it electrocutes someone
var/zap_power = 20000
@@ -574,7 +570,7 @@
visible_message(span_warning("[src] bounces off the aura around [target]!"))
return PROJECTILE_PIERCE_PHASE
-/obj/projectile/magic/fire_ball/Impact(atom/A)
+/obj/projectile/magic/fire_ball/impact(atom/A)
. = ..()
if(.)
playsound(src, 'sound/items/dodgeball.ogg', 200, channel = CHANNEL_SOUND_EFFECTS) //this is a very quiet sound
@@ -626,8 +622,7 @@
name = "magic missile"
icon_state = "magicm"
range = 100
- speed = 1
- pixel_speed_multiplier = 0.2
+ speed = 0.2
trigger_range = 0
can_only_hit_target = TRUE
paralyze = 6 SECONDS
@@ -653,8 +648,7 @@
antimagic_flags = MAGIC_RESISTANCE_HOLY
ignored_factions = list(FACTION_CULT)
range = 105
- speed = 1
- pixel_speed_multiplier = 1/7
+ speed = 0.15
/obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
diff --git a/code/modules/projectiles/projectile/reusable/_reusable.dm b/code/modules/projectiles/projectile/reusable/_reusable.dm
deleted file mode 100644
index 73825c9b13952..0000000000000
--- a/code/modules/projectiles/projectile/reusable/_reusable.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-/obj/projectile/bullet/reusable
- name = "reusable bullet"
- desc = "How do you even reuse a bullet?"
- impact_effect_type = null
- embedding = null
- shrapnel_type = null
- var/ammo_type = /obj/item/ammo_casing/caseless
- var/dropped = FALSE
-
-/obj/projectile/bullet/reusable/on_hit(atom/target, blocked = 0, pierce_hit)
- . = ..()
- handle_drop()
-
-/obj/projectile/bullet/reusable/on_range()
- handle_drop()
- ..()
-
-/obj/projectile/bullet/reusable/proc/handle_drop()
- if(!dropped)
- var/turf/T = get_turf(src)
- new ammo_type(T)
- dropped = TRUE
diff --git a/code/modules/projectiles/projectile/reusable/foam_dart.dm b/code/modules/projectiles/projectile/reusable/foam_dart.dm
deleted file mode 100644
index dee37630d68e8..0000000000000
--- a/code/modules/projectiles/projectile/reusable/foam_dart.dm
+++ /dev/null
@@ -1,68 +0,0 @@
-/obj/projectile/bullet/reusable/foam_dart
- name = "foam dart"
- desc = "I hope you're wearing eye protection."
- damage = 0 // It's a damn toy.
- damage_type = OXY
- icon = 'icons/obj/weapons/guns/toy.dmi'
- icon_state = "foamdart_proj"
- base_icon_state = "foamdart_proj"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart
- range = 10
- var/modified = FALSE
- var/obj/item/pen/pen = null
-
-/obj/projectile/bullet/reusable/foam_dart/handle_drop(cancel=TRUE)
- if(dropped || cancel)
- return
- var/turf/T = get_turf(src)
- dropped = 1
- var/obj/item/ammo_casing/caseless/foam_dart/newcasing = new ammo_type(T)
- newcasing.modified = modified
- var/obj/projectile/bullet/reusable/foam_dart/newdart = newcasing.loaded_projectile
- newdart.modified = modified
- newdart.damage_type = damage_type
- if(pen)
- newdart.pen = pen
- pen.forceMove(newdart)
- pen = null
- newdart.damage = 5
- newdart.update_appearance()
-
-
-/obj/projectile/bullet/reusable/foam_dart/Destroy()
- pen = null
- return ..()
-
-/obj/projectile/bullet/reusable/foam_dart/riot
- name = "riot foam dart"
- icon_state = "foamdart_riot_proj"
- base_icon_state = "foamdart_riot_proj"
- ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
- stamina = 11.5
-
-/obj/projectile/bullet/reusable/foam_dart/on_hit(atom/target, blocked = 0, pierce_hit)
- . = ..()
- var/mob/living/carbon/ctarget = target
- if (. != BULLET_ACT_HIT || blocked != 0 || (def_zone != BODY_ZONE_PRECISE_MOUTH && def_zone != BODY_ZONE_HEAD && def_zone != BODY_ZONE_PRECISE_EYES) || !istype(ctarget))
- handle_drop(cancel=FALSE)
- return
- if(!ctarget.has_mouth() || ctarget.is_mouth_covered(ITEM_SLOT_HEAD) || ctarget.is_mouth_covered(ITEM_SLOT_MASK) || ctarget.has_status_effect(/datum/status_effect/choke))
- handle_drop(cancel=FALSE)
- return
- var/inhale_prob = def_zone == BODY_ZONE_PRECISE_MOUTH ? 15 : 5
- if(istype(src, /obj/projectile/bullet/reusable/foam_dart/riot))
- inhale_prob *= 2
- if((prob(inhale_prob) && hits_front(target)) || HAS_TRAIT(target, TRAIT_CURSED))
- var/obj/item/ammo_casing/caseless/foam_dart/item = new ammo_type
- item.inhale(target)
-
-/obj/projectile/bullet/reusable/foam_dart/proc/hits_front(mob/target)
- if(target.flags_1 & IS_SPINNING_1 || !starting)
- return TRUE
-
- if(target.loc == starting)
- return FALSE
-
- var/target_to_starting = get_dir(target, starting)
- var/target_dir = target.dir
- return target_dir & target_to_starting
diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm
index 49d1dee6c3fc7..d708372c99dcd 100644
--- a/code/modules/projectiles/projectile/special/curse.dm
+++ b/code/modules/projectiles/projectile/special/curse.dm
@@ -11,7 +11,7 @@
damage_type = BURN
damage = 10
paralyze = 20
- speed = 2
+ speed = 0.5
range = 16
var/datum/beam/arm
var/handedness = 0
diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm
index 18066f95ea1b5..bf80877df9f7b 100644
--- a/code/modules/projectiles/projectile/special/rocket.dm
+++ b/code/modules/projectiles/projectile/special/rocket.dm
@@ -2,7 +2,7 @@
name ="explosive bolt"
icon_state= "bolter"
damage = 50
- embedding = null
+ embed_type = null
shrapnel_type = null
/obj/projectile/bullet/gyro/on_hit(atom/target, blocked = 0, pierce_hit)
@@ -199,7 +199,7 @@ among other potential differences. This granularity is helpful for things like t
light_color = COLOR_SOFT_RED
ricochets_max = 50 //Honk!
ricochet_chance = 80
- reflectable = REFLECT_NORMAL
+ reflectable = TRUE
wound_bonus = -20
bare_wound_bonus = 10
tracer_type = /obj/effect/projectile/tracer/heavy_laser
diff --git a/code/modules/projectiles/projectile/special/saboteur.dm b/code/modules/projectiles/projectile/special/saboteur.dm
index 15588957af488..5ca876e772157 100644
--- a/code/modules/projectiles/projectile/special/saboteur.dm
+++ b/code/modules/projectiles/projectile/special/saboteur.dm
@@ -6,7 +6,7 @@
/obj/projectile/energy/fisher
name = "attenuated kinetic force"
- alpha = 0
+ icon_state = null
damage = 0
damage_type = BRUTE
armor_flag = BOMB
diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm
index 90eadd0bb097b..704452fdf7111 100644
--- a/code/modules/projectiles/projectile/special/wormhole.dm
+++ b/code/modules/projectiles/projectile/special/wormhole.dm
@@ -1,6 +1,6 @@
/obj/projectile/beam/wormhole
name = "bluespace beam"
- icon_state = "spark"
+ icon_state = null
hitsound = SFX_SPARKS
damage = 0
pass_flags = PASSGLASS | PASSTABLE | PASSGRILLE | PASSMOB
diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm
index 7c2ef29c2afa5..41af944cef288 100644
--- a/code/modules/reagents/reagent_containers/cups/drinks.dm
+++ b/code/modules/reagents/reagent_containers/cups/drinks.dm
@@ -30,11 +30,11 @@
qdel(src)
target.Bumped(B)
-/obj/item/reagent_containers/cup/glass/bullet_act(obj/projectile/P)
+/obj/item/reagent_containers/cup/glass/bullet_act(obj/projectile/proj)
. = ..()
if(QDELETED(src))
return
- if(P.damage > 0 && P.damage_type == BRUTE)
+ if(proj.damage > 0 && proj.damage_type == BRUTE)
var/atom/T = get_turf(src)
smash(T)
diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
index 0cf7eb341783b..3a396dbf99b50 100644
--- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
@@ -655,14 +655,14 @@
update_appearance()
make_froth(offset_x = 0, offset_y = sabraged ? 13 : 15, intensity = froth_severity) //the y offset for sabraged is lower because the bottle's lip is smashed
///Type of cork to fire away
- var/obj/projectile/bullet/reusable/cork_to_fire = sabraged ? /obj/projectile/bullet/reusable/champagne_cork/sabrage : /obj/projectile/bullet/reusable/champagne_cork
+ var/obj/projectile/bullet/cork_to_fire = sabraged ? /obj/projectile/bullet/champagne_cork/sabrage : /obj/projectile/bullet/champagne_cork
///Our resulting cork projectile
- var/obj/projectile/bullet/reusable/champagne_cork/popped_cork = new cork_to_fire (drop_location())
+ var/obj/projectile/bullet/champagne_cork/popped_cork = new cork_to_fire (drop_location())
popped_cork.firer = user
popped_cork.fired_from = src
popped_cork.fire(dir2angle(user.dir) + rand(-30, 30))
-/obj/projectile/bullet/reusable/champagne_cork
+/obj/projectile/bullet/champagne_cork
name = "champagne cork"
icon = 'icons/obj/drinks/drink_effects.dmi'
icon_state = "champagne_cork"
@@ -676,14 +676,18 @@
ricochet_incidence_leeway = 0
range = 7
knockdown = 2 SECONDS
- ammo_type = /obj/item/trash/champagne_cork
+ var/drop_type = /obj/item/trash/champagne_cork
-/obj/projectile/bullet/reusable/champagne_cork/sabrage
+/obj/projectile/bullet/champagne_cork/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/projectile_drop, drop_type)
+
+/obj/projectile/bullet/champagne_cork/sabrage
icon_state = "champagne_cork_sabrage"
damage = 12
ricochets_max = 2 //bit heavier
range = 6
- ammo_type = /obj/item/trash/champagne_cork/sabrage
+ drop_type = /obj/item/trash/champagne_cork/sabrage
/obj/item/trash/champagne_cork
name = "champagne cork"
diff --git a/code/modules/reagents/reagent_containers/cups/soda.dm b/code/modules/reagents/reagent_containers/cups/soda.dm
index 959493bf21259..e5444e86ca73c 100644
--- a/code/modules/reagents/reagent_containers/cups/soda.dm
+++ b/code/modules/reagents/reagent_containers/cups/soda.dm
@@ -81,17 +81,17 @@
return TRUE
return ..()
-/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/P)
+/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/proj)
. = ..()
if(QDELETED(src))
return
- if(P.damage > 0 && P.damage_type == BRUTE)
- var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(src.loc)
- crushed_can.icon_state = icon_state
- var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs))
- crushed_can.throw_at(throw_target, rand(1,2), 7)
- qdel(src)
+ if(!proj.damage || proj.damage_type != BRUTE)
return
+ var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(loc)
+ crushed_can.icon_state = icon_state
+ var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs))
+ crushed_can.throw_at(throw_target, rand(1,2), 7)
+ qdel(src)
/obj/item/reagent_containers/cup/soda_cans/proc/open_soda(mob/user)
if(prob(fizziness))
@@ -143,7 +143,7 @@
burst_soda(hit_atom, hide_message = TRUE)
visible_message(span_danger("[src]'s impact with [hit_atom] causes it to rupture, spilling everywhere!"))
- var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(src.loc)
+ var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(loc)
crushed_can.icon_state = icon_state
moveToNullspace()
QDEL_IN(src, 1 SECONDS) // give it a second so it can still be logged for the throw impact
diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm
index 70d681e15af2c..06e602a2a443f 100644
--- a/code/modules/religion/burdened/psyker.dm
+++ b/code/modules/religion/burdened/psyker.dm
@@ -227,7 +227,7 @@
ricochet_auto_aim_angle = 10
ricochet_auto_aim_range = 3
wound_bonus = -10
- embedding = null
+ embed_type = null
/datum/action/cooldown/spell/pointed/psychic_projection
name = "Psychic Projection"
diff --git a/code/modules/religion/hunt/atlatl.dm b/code/modules/religion/hunt/atlatl.dm
index 7281e505e4b8b..54ddbff36449a 100644
--- a/code/modules/religion/hunt/atlatl.dm
+++ b/code/modules/religion/hunt/atlatl.dm
@@ -28,10 +28,16 @@
/obj/item/gun/ballistic/atlatl/proc/drop_spear()
if(chambered)
chambered.forceMove(drop_location())
- magazine.get_round(keep = FALSE)
+ magazine.get_round()
chambered = null
update_appearance()
+/obj/item/gun/ballistic/atlatl/chamber_round(keep_bullet = FALSE, spin_cylinder, replace_new_round)
+ if(chambered || !magazine)
+ return
+ if(magazine.ammo_count())
+ chambered = magazine.get_round()
+ chambered.forceMove(src)
/obj/item/gun/ballistic/atlatl/equipped(mob/user, slot, initial)
. = ..()
@@ -40,59 +46,54 @@
drop_spear()
update_appearance()
-/obj/item/gun/ballistic/atlatl/afterattack(atom/target, mob/living/user, flag, params, passthrough = FALSE)
- if(!chambered)
- return
- . = ..() //fires, removing the spear
- update_appearance()
-
/obj/item/gun/ballistic/atlatl/shoot_with_empty_chamber(mob/living/user)
return //no clicking sounds please
-
/obj/item/ammo_box/magazine/internal/atlatl
name = "notch"
- ammo_type = /obj/item/ammo_casing/caseless/thrownspear
+ ammo_type = /obj/item/ammo_casing/thrownspear
max_ammo = 1
start_empty = TRUE
caliber = CALIBER_SPEAR
-/obj/item/ammo_casing/caseless/thrownspear
+/obj/item/ammo_casing/thrownspear
name = "throwing spear"
desc = "A light spear made for throwing from an atlatl"
icon = 'icons/obj/weapons/guns/atlatl/thrownspear.dmi'
icon_state = "thrownspear"
+ base_icon_state = "thrownspear"
custom_materials = "wood"
- inhand_icon_state = null
- projectile_type = /obj/projectile/bullet/reusable/thrownspear
+ projectile_type = /obj/projectile/bullet/thrownspear
flags_1 = NONE
throwforce = 25
- w_class = WEIGHT_CLASS_BULKY
firing_effect_type = null
- caliber = CALIBER_SPEAR
- heavy_metal = FALSE
+ caliber = CALIBER_SPEAR
-/obj/item/ammo_casing/caseless/thrownspear/Initialize(mapload)
+/obj/item/ammo_casing/thrownspear/Initialize(mapload)
. = ..()
- AddComponent(/datum/element/envenomable_casing)
+ AddElement(/datum/element/envenomable_casing)
+ AddElement(/datum/element/caseless, TRUE)
+/obj/item/ammo_casing/thrownspear/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]"
-/obj/projectile/bullet/reusable/thrownspear
+/obj/projectile/bullet/thrownspear
name = "thrown spear"
desc = "Beasts be felled!"
icon = 'icons/obj/weapons/guns/atlatl/thrownspear.dmi'
icon_state = "spear_projectile"
- ammo_type = /obj/item/ammo_casing/caseless/thrownspear
damage = 50
speed = 1.5
range = 20
wound_bonus = -20
+ shrapnel_type = null
/// How much the damage is multiplied by when we hit a mob with the correct biotype
var/biotype_damage_multiplier = 3
/// What biotype we look for
var/biotype_we_look_for = MOB_BEAST
-/obj/projectile/bullet/reusable/thrownspear/on_hit(atom/target, blocked, pierce_hit)
+/obj/projectile/bullet/thrownspear/on_hit(atom/target, blocked, pierce_hit)
if(ismineralturf(target))
var/turf/closed/mineral/mineral_turf = target
mineral_turf.gets_drilled(firer, FALSE)
@@ -106,7 +107,6 @@
damage *= biotype_damage_multiplier
return ..()
-
/obj/item/storage/bag/spearquiver
name = "large quiver"
desc = "A large quiver to hold a few spears for your atlatl"
@@ -117,7 +117,7 @@
inhand_icon_state = null
worn_icon_state = "spearquiver"
/// type of arrow the quiver should hold
- var/arrow_path = /obj/item/ammo_casing/caseless/thrownspear
+ var/arrow_path = /obj/item/ammo_casing/thrownspear
/obj/item/storage/bag/spearquiver/Initialize(mapload)
. = ..()
@@ -125,7 +125,7 @@
atom_storage.max_slots = 5
atom_storage.max_total_storage = 100
atom_storage.set_holdable(list(
- /obj/item/ammo_casing/caseless/thrownspear,
+ /obj/item/ammo_casing/thrownspear,
))
/obj/item/storage/bag/spearquiver/PopulateContents()
diff --git a/code/modules/religion/hunt/hunting_rites.dm b/code/modules/religion/hunt/hunting_rites.dm
index aef8e731d98ab..747cd44463ea1 100644
--- a/code/modules/religion/hunt/hunting_rites.dm
+++ b/code/modules/religion/hunt/hunting_rites.dm
@@ -91,4 +91,4 @@ GLOBAL_LIST_EMPTY(sect_of_the_hunt_preys)
..()
var/altar_turf = get_turf(religious_tool)
for (var/i in 1 to 3)
- new /obj/item/ammo_casing/caseless/thrownspear(altar_turf)
+ new /obj/item/ammo_casing/thrownspear(altar_turf)
diff --git a/code/modules/religion/pyre_rites.dm b/code/modules/religion/pyre_rites.dm
index 63b0b4080ec34..7ba1cca666e7f 100644
--- a/code/modules/religion/pyre_rites.dm
+++ b/code/modules/religion/pyre_rites.dm
@@ -124,11 +124,11 @@
invoke_msg = "... a blazing star is born!"
favor_cost = 2000
///arrow to enchant
- var/obj/item/ammo_casing/caseless/arrow/holy/enchant_target
+ var/obj/item/ammo_casing/arrow/holy/enchant_target
/datum/religion_rites/blazing_star/perform_rite(mob/living/user, atom/religious_tool)
- for(var/obj/item/ammo_casing/caseless/arrow/holy/can_enchant in get_turf(religious_tool))
- if(istype(can_enchant, /obj/item/ammo_casing/caseless/arrow/holy/blazing))
+ for(var/obj/item/ammo_casing/arrow/holy/can_enchant in get_turf(religious_tool))
+ if(istype(can_enchant, /obj/item/ammo_casing/arrow/holy/blazing))
continue
enchant_target = can_enchant
return ..()
@@ -137,7 +137,7 @@
/datum/religion_rites/blazing_star/invoke_effect(mob/living/user, atom/movable/religious_tool)
..()
- var/obj/item/ammo_casing/caseless/arrow/holy/enchanting = enchant_target
+ var/obj/item/ammo_casing/arrow/holy/enchanting = enchant_target
var/turf/tool_turf = get_turf(religious_tool)
enchant_target = null
if(QDELETED(enchanting) || !(tool_turf == enchanting.loc)) //check if the arrow is still there
@@ -145,6 +145,6 @@
return FALSE
enchanting.visible_message(span_notice("[enchant_target] is blessed by holy fire!"))
playsound(tool_turf, 'sound/effects/pray.ogg', 50, TRUE)
- new /obj/item/ammo_casing/caseless/arrow/holy/blazing(tool_turf)
+ new /obj/item/ammo_casing/arrow/holy/blazing(tool_turf)
qdel(enchanting)
return TRUE
diff --git a/code/modules/research/designs/autolathe/security_designs.dm b/code/modules/research/designs/autolathe/security_designs.dm
index 93ef4f1077de7..4ecc523bf2782 100644
--- a/code/modules/research/designs/autolathe/security_designs.dm
+++ b/code/modules/research/designs/autolathe/security_designs.dm
@@ -167,7 +167,7 @@
id = "riot_dart"
build_type = AUTOLATHE
materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT) //Discount for making individually - no box = less iron!
- build_path = /obj/item/ammo_casing/caseless/foam_dart/riot
+ build_path = /obj/item/ammo_casing/foam_dart/riot
category = list(
RND_CATEGORY_HACKED,
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO,
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index e8dc8e4b349be..fc84baefa56a7 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -202,8 +202,8 @@
autolathe_exportable = FALSE
/datum/design/beamrifle
- name = "Beam Marksman Rifle Part Kit (Lethal)"
- desc = "The gunkit for a powerful long ranged anti-material rifle that fires charged particle beams to obliterate targets."
+ name = "Event Horizon Anti-Existential Beam Rifle Part Kit (DOOMSDAY DEVICE)"
+ desc = "The kit that produces a weapon made to end your foes on an existential level. Why the fuck can you make this?"
id = "beamrifle"
build_type = PROTOLATHE | AWAY_LATHE
materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/diamond =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/uranium = SHEET_MATERIAL_AMOUNT * 4, /datum/material/silver = SHEET_MATERIAL_AMOUNT * 2.25, /datum/material/gold =SHEET_MATERIAL_AMOUNT * 2.5)
diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm
index 18278fb593a94..0e10dbc6d7ee6 100644
--- a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm
+++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm
@@ -31,7 +31,7 @@
/datum/action/cooldown/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster)
var/obj/projectile/to_fire = new projectile_type()
- to_fire.preparePixelProjectile(victim, caster)
+ to_fire.aim_projectile(victim, caster)
SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, victim, to_fire)
to_fire.fire()
diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm
index 254613b53ce9d..b97c92cb9bc8b 100644
--- a/code/modules/spells/spell_types/pointed/_pointed.dm
+++ b/code/modules/spells/spell_types/pointed/_pointed.dm
@@ -168,7 +168,7 @@
/datum/action/cooldown/spell/pointed/projectile/proc/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration)
to_fire.firer = owner
to_fire.fired_from = src
- to_fire.preparePixelProjectile(target, owner)
+ to_fire.aim_projectile(target, owner)
RegisterSignal(to_fire, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_cast_hit))
if(istype(to_fire, /obj/projectile/magic))
diff --git a/code/modules/spells/spell_types/pointed/spell_cards.dm b/code/modules/spells/spell_types/pointed/spell_cards.dm
index 340a59c5a67fa..a749f9827ee39 100644
--- a/code/modules/spells/spell_types/pointed/spell_cards.dm
+++ b/code/modules/spells/spell_types/pointed/spell_cards.dm
@@ -82,4 +82,4 @@
to_fire.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount)
to_fire.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount)
- to_fire.preparePixelProjectile(target, user, null, current_angle)
+ to_fire.aim_projectile(target, user, null, current_angle)
diff --git a/code/modules/spells/spell_types/projectile/_basic_projectile.dm b/code/modules/spells/spell_types/projectile/_basic_projectile.dm
index 343de438cd669..bcab5633eccb7 100644
--- a/code/modules/spells/spell_types/projectile/_basic_projectile.dm
+++ b/code/modules/spells/spell_types/projectile/_basic_projectile.dm
@@ -25,6 +25,6 @@
/datum/action/cooldown/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster)
var/obj/projectile/to_fire = new projectile_type()
- to_fire.preparePixelProjectile(target, caster)
+ to_fire.aim_projectile(target, caster)
SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire)
to_fire.fire()
diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm
index e03ef211af0d2..eddec573f9e80 100644
--- a/code/modules/spells/spell_types/self/summonitem.dm
+++ b/code/modules/spells/spell_types/self/summonitem.dm
@@ -128,18 +128,17 @@
// If its on someone, properly drop it
if(ismob(item_to_retrieve.loc))
- var/mob/holding_mark = item_to_retrieve.loc
-
- // Items in silicons warp the whole silicon
- if(issilicon(holding_mark))
- holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly disappears!"))
- holding_mark.forceMove(caster.loc)
- holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly appears!"))
- item_to_retrieve = null
+ if(!issilicon(item_to_retrieve.loc))
break
+ // Items in silicons warp the whole silicon
+ var/mob/holding_mark = item_to_retrieve.loc
+ holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly disappears!"))
+ holding_mark.forceMove(caster.loc)
+ holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly appears!"))
SEND_SIGNAL(holding_mark, COMSIG_MAGIC_RECALL, caster, item_to_retrieve)
- holding_mark.dropItemToGround(item_to_retrieve)
+ playsound(holding_mark, 'sound/magic/summonitems_generic.ogg', 50, TRUE)
+ return
else if(isobj(item_to_retrieve.loc))
var/obj/retrieved_item = item_to_retrieve.loc
@@ -169,6 +168,13 @@
if(!item_to_retrieve)
return
+ SEND_SIGNAL(item_to_retrieve, COMSIG_MAGIC_RECALL, caster, item_to_retrieve)
+
+ if (ismob(item_to_retrieve.loc))
+ var/mob/holder = item_to_retrieve.loc
+ if (!holder.dropItemToGround(item_to_retrieve, force = TRUE))
+ return
+
item_to_retrieve.loc?.visible_message(span_warning("[item_to_retrieve] suddenly disappears!"))
if(isitem(item_to_retrieve) && caster.put_in_hands(item_to_retrieve))
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index a5bd92bfc9a9b..08e1207f5bf2c 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -358,9 +358,14 @@
if(wound_desc)
check_list += "\t\t[wound_desc]"
- for(var/obj/item/embedded_thing in embedded_objects)
- var/stuck_word = embedded_thing.isEmbedHarmless() ? "stuck" : "embedded"
- check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!"
+ for(var/obj/item/embedded_thing as anything in embedded_objects)
+ var/harmless = embedded_thing.get_embed().is_harmless()
+ var/stuck_wordage = harmless ? "stuck to" : "embedded in"
+ var/embed_text = "\t There is [icon2html(embedded_thing, examiner)] \a [embedded_thing] [stuck_wordage] your [plaintext_zone]!"
+ if (harmless)
+ check_list += span_italics(span_notice(embed_text))
+ else
+ check_list += span_boldwarning(embed_text)
if(current_gauze)
check_list += span_notice("\t There is some [current_gauze.name] wrapped around your [name].")
@@ -1182,15 +1187,15 @@
if(embed in embedded_objects) // go away
return
// We don't need to do anything with projectile embedding, because it will never reach this point
- RegisterSignal(embed, COMSIG_ITEM_EMBEDDING_UPDATE, PROC_REF(embedded_object_changed))
embedded_objects += embed
+ RegisterSignal(embed, COMSIG_ITEM_EMBEDDING_UPDATE, PROC_REF(embedded_object_changed))
refresh_bleed_rate()
/// INTERNAL PROC, DO NOT USE
/// Cleans up any attachment we have to the embedded object, removes it from our list
/obj/item/bodypart/proc/_unembed_object(obj/item/unembed)
- UnregisterSignal(unembed, COMSIG_ITEM_EMBEDDING_UPDATE)
embedded_objects -= unembed
+ UnregisterSignal(unembed, COMSIG_ITEM_EMBEDDING_UPDATE)
refresh_bleed_rate()
/obj/item/bodypart/proc/embedded_object_changed(obj/item/embedded_source)
@@ -1242,8 +1247,8 @@
if(generic_bleedstacks > 0)
cached_bleed_rate += 0.5
- for(var/obj/item/embeddies in embedded_objects)
- if(!embeddies.isEmbedHarmless())
+ for(var/obj/item/embeddies as anything in embedded_objects)
+ if(!embeddies.get_embed().is_harmless())
cached_bleed_rate += 0.25
for(var/datum/wound/iter_wound as anything in wounds)
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index c57f576ce309b..0be3bfb0a2aba 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -111,8 +111,6 @@
qdel(surgery)
break
- for(var/obj/item/embedded in embedded_objects)
- embedded.forceMove(src) // It'll self remove via signal reaction, just need to move it
if(!phantom_owner.has_embedded_objects())
phantom_owner.clear_alert(ALERT_EMBEDDED_OBJECT)
phantom_owner.clear_mood_event("embedded")
diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm
index 34ca38d430d10..71fa500592f69 100644
--- a/code/modules/surgery/bodyparts/helpers.dm
+++ b/code/modules/surgery/bodyparts/helpers.dm
@@ -136,18 +136,20 @@
///Remove a specific embedded item from the carbon mob
/mob/living/carbon/proc/remove_embedded_object(obj/item/embedded)
- SEND_SIGNAL(src, COMSIG_CARBON_EMBED_REMOVAL, embedded)
+ if (embedded.get_embed()?.owner != src)
+ return
+ embedded.get_embed().remove_embedding()
///Remove all embedded objects from all limbs on the carbon mob
/mob/living/carbon/proc/remove_all_embedded_objects()
for(var/obj/item/bodypart/bodypart as anything in bodyparts)
- for(var/obj/item/embedded in bodypart.embedded_objects)
+ for(var/obj/item/embedded as anything in bodypart.embedded_objects)
remove_embedded_object(embedded)
-/mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE)
+/mob/living/carbon/proc/has_embedded_objects(include_harmless = FALSE)
for(var/obj/item/bodypart/bodypart as anything in bodyparts)
- for(var/obj/item/embedded in bodypart.embedded_objects)
- if(!include_harmless && embedded.isEmbedHarmless())
+ for(var/obj/item/embedded as anything in bodypart.embedded_objects)
+ if(!include_harmless && embedded.get_embed().is_harmless())
continue
return TRUE
diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm
index 760201118e696..9e01c2aa3e45b 100644
--- a/code/modules/uplink/uplink_items/nukeops.dm
+++ b/code/modules/uplink/uplink_items/nukeops.dm
@@ -412,14 +412,14 @@
/datum/uplink_item/ammo/rocket/basic
name = "84mm HE Rocket"
desc = "A low-yield anti-personnel HE rocket. Gonna take you out in style!"
- item = /obj/item/ammo_casing/caseless/rocket
+ item = /obj/item/ammo_casing/rocket
cost = 4
/datum/uplink_item/ammo/rocket/heap
name = "84mm HEAP Rocket"
desc = "A high-yield HEAP rocket; extremely effective against literally everything and anything near that thing that doesn't exist anymore. \
Strike fear into the hearts of your enemies."
- item = /obj/item/ammo_casing/caseless/rocket/heap
+ item = /obj/item/ammo_casing/rocket/heap
cost = 6
/datum/uplink_item/ammo/surplus_smg
diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm
index b23bd8504f171..50bdcb889b75f 100644
--- a/code/modules/vehicles/atv.dm
+++ b/code/modules/vehicles/atv.dm
@@ -117,6 +117,13 @@
smoke.set_up(0, holder = src, location = src)
smoke.start()
+/obj/vehicle/ridden/atv/bullet_act(obj/projectile/proj)
+ if(prob(50) || !LAZYLEN(buckled_mobs))
+ return ..()
+ for(var/mob/buckled_mob as anything in buckled_mobs)
+ return buckled_mob.projectile_hit(proj)
+ return ..()
+
/obj/vehicle/ridden/atv/atom_destruction()
explosion(src, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 4)
return ..()
diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm
index 62619de60b9b0..a6d77d4df770d 100644
--- a/code/modules/vehicles/mecha/combat/durand.dm
+++ b/code/modules/vehicles/mecha/combat/durand.dm
@@ -35,8 +35,6 @@
. = ..()
shield = new /obj/durand_shield(loc, src, plane, layer, dir)
RegisterSignal(src, COMSIG_MECHA_ACTION_TRIGGER, PROC_REF(relay))
- RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, PROC_REF(prehit))
-
/obj/vehicle/sealed/mecha/durand/Destroy()
if(shield)
@@ -85,10 +83,11 @@
SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_TRIGGER, owner, signal_args)
//Redirects projectiles to the shield if defense_check decides they should be blocked and returns true.
-/obj/vehicle/sealed/mecha/durand/proc/prehit(obj/projectile/source, list/signal_args)
- SIGNAL_HANDLER
+/obj/vehicle/sealed/mecha/durand/bullet_act(obj/projectile/source, def_zone, mode)
if(defense_check(source.loc) && shield)
- signal_args[2] = shield
+ return shield.projectile_hit(source, def_zone, mode)
+ return ..()
+
/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield.
Expects a turf. Returns true if the attack should be blocked, false if not.*/
diff --git a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
index 6ef9d5fac209a..5333ecd54c3ba 100644
--- a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
@@ -142,17 +142,20 @@
else
target.investigate_log("has been gibbed by [src] (attached to [chassis]).", INVESTIGATE_DEATHS)
target.gib()
- else
- //drill makes a hole
- var/obj/item/bodypart/target_part = target.get_bodypart(target.get_random_valid_zone(BODY_ZONE_CHEST))
- target.apply_damage(10, BRUTE, BODY_ZONE_CHEST, target.run_armor_check(target_part, MELEE))
+ return
+
+ //drill makes a hole
+ var/def_zone = target.get_random_valid_zone(BODY_ZONE_CHEST)
+ var/obj/item/bodypart/target_part = target.get_bodypart(def_zone)
+ var/blocked = target.run_armor_check(def_zone, MELEE)
+ target.apply_damage(10, BRUTE, def_zone, blocked)
- //blood splatters
- target.do_splatter_effect(get_dir(chassis, target))
+ //blood splatters
+ target.create_splatter(get_dir(chassis, target))
- //organs go everywhere
- if(target_part && prob(10 * drill_level))
- target_part.dismember(BRUTE)
+ //organs go everywhere
+ if(target_part && blocked < 100 && prob(10 * drill_level))
+ target_part.dismember(BRUTE)
/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill
name = "diamond-tipped exosuit drill"
diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
index 3f9e13ae82a1f..0622bf84a73cb 100644
--- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
+++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
@@ -58,14 +58,13 @@
var/obj/projectile/projectile_obj = new projectile(get_turf(src))
projectile_obj.log_override = TRUE //we log being fired ourselves a little further down.
projectile_obj.firer = chassis
- projectile_obj.fired_from = src
- projectile_obj.preparePixelProjectile(target, source, modifiers, spread)
- if(source.client && isliving(source)) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways
+ projectile_obj.aim_projectile(target, source, modifiers, spread)
+ if(isliving(source) && source.client) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways
var/mob/living/shooter = source
projectile_obj.hit_prone_targets = (shooter.istate & ISTATE_HARM)
projectile_obj.fire()
if(!projectile_obj.suppressed && firing_effect_type)
- new firing_effect_type(get_turf(src), chassis.dir)
+ new firing_effect_type(chassis || get_turf(src), chassis.dir)
playsound(chassis, fire_sound, 50, TRUE)
log_combat(source, target, "fired [projectile_obj] at", src, "from [chassis] at [get_area_name(src, TRUE)]")
@@ -740,7 +739,7 @@
mech_flags = EXOSUIT_MODULE_TRASHTANK
/obj/projectile/bullet/pellet/shotgun_improvised/tank
- tile_dropoff = 0 //its a peashooter that fires with bullets the size of pellets, but don't do this now...
+ damage_falloff_tile = 0
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/peashooter
name = "peashooter breech"
diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm
index ed17c623f8549..4acedfbad48ac 100644
--- a/code/modules/vehicles/mecha/mecha_defense.dm
+++ b/code/modules/vehicles/mecha/mecha_defense.dm
@@ -124,7 +124,7 @@
&& !(mecha_flags & SILICON_PILOT) \
&& (def_zone == BODY_ZONE_HEAD || def_zone == BODY_ZONE_CHEST))
for(var/mob/living/hitmob as anything in occupants)
- hitmob.bullet_act(hitting_projectile, def_zone, piercing_hit) //If the sides are open, the occupant can be hit
+ hitmob.projectile_hit(hitting_projectile, def_zone, piercing_hit) //If the sides are open, the occupant can be hit
return BULLET_ACT_HIT
log_message("Hit by projectile. Type: [hitting_projectile]([hitting_projectile.damage_type]).", LOG_MECHA, color="red")
diff --git a/code/modules/vehicles/secway.dm b/code/modules/vehicles/secway.dm
index e6e307cbc0567..e1e2c5c603ce8 100644
--- a/code/modules/vehicles/secway.dm
+++ b/code/modules/vehicles/secway.dm
@@ -96,3 +96,11 @@
/obj/vehicle/ridden/secway/Destroy()
STOP_PROCESSING(SSobj,src)
return ..()
+
+//bullets will have a 60% chance to hit any riders
+/obj/vehicle/ridden/secway/bullet_act(obj/projectile/proj)
+ if(!buckled_mobs || prob(40))
+ return ..()
+ for(var/mob/rider as anything in buckled_mobs)
+ return rider.projectile_hit(proj)
+ return ..()
diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm
index e8930e3da72df..fb4330936ae0d 100644
--- a/code/modules/vehicles/vehicle_key.dm
+++ b/code/modules/vehicles/vehicle_key.dm
@@ -39,10 +39,15 @@
attack_verb_continuous = list("stubs", "pokes")
attack_verb_simple = list("stub", "poke")
sharpness = SHARP_EDGED
- embedding = list("pain_mult" = 1, "embed_chance" = 30, "fall_chance" = 70)
+ embed_type = /datum/embedding/janicart_key
wound_bonus = -1
bare_wound_bonus = 2
+/datum/embedding/janicart_key
+ pain_mult = 1
+ embed_chance = 30
+ fall_chance = 70
+
/obj/item/key/janitor/suicide_act(mob/living/carbon/user)
switch(user.mind?.get_skill_level(/datum/skill/cleaning))
if(SKILL_LEVEL_NONE to SKILL_LEVEL_NOVICE) //Their mind is too weak to ascend as a janny
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index bc2a70e7fc6b5..ed4ac998d6a6d 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -745,11 +745,9 @@
var/mob/living/carbon/carbon_target = atom_target
for(var/i in 1 to num_shards)
var/obj/item/shard/shard = new /obj/item/shard(get_turf(carbon_target))
- shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult = 1, pain_chance = 5)
- shard.updateEmbedding()
+ shard.set_embed(/datum/embedding/glass_candy)
carbon_target.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
- shard.embedding = list()
- shard.updateEmbedding()
+ shard.set_embed(initial(shard.embed_type))
return TRUE
if (VENDOR_CRUSH_CRIT_PIN) // pin them beneath the machine until someone untilts it
if (!isliving(atom_target))
@@ -979,11 +977,9 @@
var/mob/living/carbon/carbon_target = atom_target
for(var/i in 1 to num_shards)
var/obj/item/shard/shard = new /obj/item/shard(get_turf(carbon_target))
- shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult = 1, pain_chance = 5)
- shard.updateEmbedding()
+ shard.set_embed(/datum/embedding/glass_candy)
carbon_target.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
- shard.embedding = list()
- shard.updateEmbedding()
+ shard.set_embed(initial(shard.embed_type))
return TRUE
if (VENDOR_CRUSH_CRIT_PIN) // pin them beneath the machine until someone untilts it
if (!isliving(atom_target))
diff --git a/monkestation/code/datums/mutations/acid_spit.dm b/monkestation/code/datums/mutations/acid_spit.dm
index 5de8a3637d617..2e48350455923 100644
--- a/monkestation/code/datums/mutations/acid_spit.dm
+++ b/monkestation/code/datums/mutations/acid_spit.dm
@@ -142,7 +142,7 @@
speed = 1.8 // spit is not very fast
var/multiplier = 1
-/obj/projectile/bullet/acid_spit/preparePixelProjectile(atom/target, atom/source, list/modifiers = null, deviation = 0)
+/obj/projectile/bullet/acid_spit/aim_projectile(atom/target, atom/source, list/modifiers = null, deviation = 0)
if(fired_from)
var/datum/action/cooldown/spell/pointed/projectile/acid_spit/ability = fired_from
if(istype(ability))
diff --git a/monkestation/code/datums/mutations/cold.dm b/monkestation/code/datums/mutations/cold.dm
index a54a7038d6014..9f0fb08444627 100644
--- a/monkestation/code/datums/mutations/cold.dm
+++ b/monkestation/code/datums/mutations/cold.dm
@@ -39,4 +39,4 @@
. = ..()
if(projectiles_per_fire > 1)
var/current_angle = iteration * 30
- to_fire.preparePixelProjectile(target, user, null, current_angle - 45)
+ to_fire.aim_projectile(target, user, null, current_angle - 45)
diff --git a/monkestation/code/game/objects/items/mothlet_grenade.dm b/monkestation/code/game/objects/items/mothlet_grenade.dm
index 6a51961b8cdce..a0435c8cef455 100644
--- a/monkestation/code/game/objects/items/mothlet_grenade.dm
+++ b/monkestation/code/game/objects/items/mothlet_grenade.dm
@@ -23,8 +23,12 @@
ricochet_incidence_leeway = 0 //They are living moths, buzzing around
hit_prone_targets = TRUE //You cant duck under a creature intent on pantsing you infront of everyone
sharpness = SHARP_POINTY
- embedding = list(embed_chance=0, ignore_throwspeed_threshold=TRUE, fall_chance=1)
+ embed_type = /datum/embedding/mothlet
+/datum/embedding/mothlet
+ embed_chance = 0
+ ignore_throwspeed_threshold = TRUE
+ fall_chance = 1
/obj/projectile/bullet/shrapnel/mothlet/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
diff --git a/monkestation/code/modules/antagonists/clock_cult/items/weaponry.dm b/monkestation/code/modules/antagonists/clock_cult/items/weaponry.dm
index 345bdb52632d1..6e520422173a9 100644
--- a/monkestation/code/modules/antagonists/clock_cult/items/weaponry.dm
+++ b/monkestation/code/modules/antagonists/clock_cult/items/weaponry.dm
@@ -70,7 +70,7 @@
desc = "A razor-sharp spear made of brass. It thrums with barely-contained energy."
base_icon_state = "ratvarian_spear"
icon_state = "ratvarian_spear0"
- embedding = list("max_damage_mult" = 15, "armour_block" = 80)
+ embed_type = /datum/embedding/brass_spear
throwforce = 40
force = 7
armour_penetration = 40
@@ -81,6 +81,9 @@
///weakref to our current holder
var/datum/weakref/current_holder
+/datum/embedding/brass_spear
+ impact_pain_mult = 15
+
/obj/item/clockwork/weapon/brass_spear/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, \
@@ -309,7 +312,7 @@
/// Recharges a bolt, done after the delay in shoot_live_shot
/obj/item/gun/ballistic/bow/clockwork/proc/recharge_bolt()
- var/obj/item/ammo_casing/caseless/arrow/clockbolt/bolt = new
+ var/obj/item/ammo_casing/arrow/clockbolt/bolt = new
magazine.give_round(bolt)
chambered = bolt
update_icon()
@@ -322,10 +325,10 @@
icon_state = "[base_icon_state]_[chambered ? "chambered" : "unchambered"]_[drawn ? "drawn" : "undrawn"]"
/obj/item/ammo_box/magazine/internal/bow/clockwork
- ammo_type = /obj/item/ammo_casing/caseless/arrow/clockbolt
+ ammo_type = /obj/item/ammo_casing/arrow/clockbolt
start_empty = FALSE
-/obj/item/ammo_casing/caseless/arrow/clockbolt
+/obj/item/ammo_casing/arrow/clockbolt
name = "energy bolt"
desc = "An arrow made from a strange energy."
icon = 'monkestation/icons/obj/clock_cult/ammo.dmi'
diff --git a/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_equipment.dm b/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_equipment.dm
index f24924fbb31a3..097e22bcbc66f 100644
--- a/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_equipment.dm
+++ b/monkestation/code/modules/antagonists/clock_cult/mechas/mecha_equipment.dm
@@ -62,7 +62,7 @@
return
new /obj/effect/temp_visual/steam(current_turf)
- var/turf/throw_at_turf = get_turf_in_angle(Angle, current_turf, 7)
+ var/turf/throw_at_turf = get_turf_in_angle(angle, current_turf, 7)
//basic tracker vars, anti lag to make sure we dont try and throw 100 things at the same time
var/thrown_items = 0
var/thrown_mobs = 0
diff --git a/monkestation/code/modules/antagonists/clock_cult/portal.dm b/monkestation/code/modules/antagonists/clock_cult/portal.dm
index e0fa4f0bc9329..08adb79d17c9c 100644
--- a/monkestation/code/modules/antagonists/clock_cult/portal.dm
+++ b/monkestation/code/modules/antagonists/clock_cult/portal.dm
@@ -29,7 +29,7 @@
. = ..()
teleport(bumper)
-/obj/effect/portal/clockcult/teleport(atom/movable/teleported_atom, pull_loop = FALSE)
+/obj/effect/portal/clockcult/teleport(atom/movable/teleported_atom, force = FALSE, pull_loop = FALSE)
if(isliving(teleported_atom))
if(pull_loop)
return
diff --git a/monkestation/code/modules/antagonists/wizard/equipment/mirror_shield.dm b/monkestation/code/modules/antagonists/wizard/equipment/mirror_shield.dm
index 59cee2c7e6927..da9e75bbf8b87 100644
--- a/monkestation/code/modules/antagonists/wizard/equipment/mirror_shield.dm
+++ b/monkestation/code/modules/antagonists/wizard/equipment/mirror_shield.dm
@@ -80,7 +80,7 @@
/obj/item/gun/magic/mirror_shield/proc/absorb_projectile(obj/projectile/absorbed)
STOP_PROCESSING(SSprojectiles, absorbed)
absorbed.fired = FALSE
- QDEL_NULL(absorbed.trajectory)
+ QDEL_NULL(absorbed.movement_vector)
if(!chambered.loaded_projectile)
absorbed.forceMove(chambered)
chambered.loaded_projectile = absorbed
@@ -98,7 +98,7 @@
hit_by.impacted = list()
var/turf/firer_turf = get_turf(hit_by.firer)
if(hit_by.firer && get_dist(firer_turf, get_turf(src)) <= 1) //this is due to some jank I cant figure out, if you want to go ahead
- hit_by.process_hit(firer_turf, hit_by.firer)
+ hit_by.impact(firer_turf)
else if(reaction_mode == REACTION_MODE_ABSORB && length(stored_projectiles) <= max_stored_projectiles && !(hit_by.type in blacklisted_projectile_types))
absorb_projectile(hit_by)
else
diff --git a/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm b/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm
index 6507804dcf994..7b4529f2f8fa5 100644
--- a/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm
+++ b/monkestation/code/modules/antagonists/zombies/zombie_types/spitter.dm
@@ -64,7 +64,7 @@
span_alert("You spit."),
)
var/obj/projectile/neurotoxin/zombie/spit = new(user.loc)
- spit.preparePixelProjectile(target, user, modifiers)
+ spit.aim_projectile(target, user, modifiers)
spit.firer = user
spit.fire()
user.newtonian_move(get_dir(target, user))
diff --git a/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgy.dm b/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgy.dm
index 8d7b53f6332d3..47ec11afd64b7 100644
--- a/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgy.dm
+++ b/monkestation/code/modules/bloodsuckers/powers/tremere/thaumaturgy.dm
@@ -39,8 +39,7 @@
var/shot_cooldown = 0
var/datum/weakref/blood_shield
var/obj/projectile/magic/arcane_barrage/bloodsucker/magic_9ball
- var/speed = 1
- var/pixel_speed = 0.3
+ var/speed = 0.3
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/Grant()
charges = get_max_charges()
@@ -189,12 +188,11 @@
/datum/action/cooldown/bloodsucker/targeted/tremere/thaumaturgy/proc/handle_shot(mob/user, atom/target)
magic_9ball = new(get_turf(user))
magic_9ball.speed = speed
- magic_9ball.pixel_speed_multiplier = pixel_speed
magic_9ball.firer = user
magic_9ball.power_ref = WEAKREF(src)
magic_9ball.damage = get_blood_bolt_damage()
magic_9ball.def_zone = ran_zone(user.zone_selected, min(level_current * 10, 90))
- magic_9ball.preparePixelProjectile(target, user)
+ magic_9ball.aim_projectile(target, user)
// autotarget if we aim at a turf
if(isturf(target))
var/list/targets = list()
@@ -229,6 +227,8 @@
impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser
range = 30
armor_flag = LASER
+ //for cases where homing would act weird with prone targets
+ hit_prone_targets = TRUE
var/datum/weakref/power_ref
/obj/projectile/magic/arcane_barrage/bloodsucker/on_hit(target, blocked = 0, pierce_hit)
diff --git a/monkestation/code/modules/bloodsuckers/structures/bloodsucker_objects.dm b/monkestation/code/modules/bloodsuckers/structures/bloodsucker_objects.dm
index 1186bbe2dcd7f..8a2f129e7a446 100644
--- a/monkestation/code/modules/bloodsuckers/structures/bloodsucker_objects.dm
+++ b/monkestation/code/modules/bloodsuckers/structures/bloodsucker_objects.dm
@@ -111,7 +111,7 @@
attack_verb_continuous = list("staked", "stabbed", "tore into")
attack_verb_simple = list("staked", "stabbed", "tore into")
sharpness = SHARP_EDGED
- embedding = list("embed_chance" = 20)
+ embed_type = /datum/embedding/stake
force = 6
throwforce = 10
max_integrity = 30
@@ -119,6 +119,9 @@
///Time it takes to embed the stake into someone's chest.
var/staketime = 12 SECONDS
+/datum/embedding/stake
+ embed_chance = 20
+
/obj/item/stake/attack(mob/living/target, mob/living/user, params)
. = ..()
if(.)
@@ -145,7 +148,7 @@
span_danger("You drive the [src] into [target]'s chest!"),
)
playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
- if(tryEmbed(target.get_bodypart(BODY_ZONE_CHEST), TRUE, TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
+ if(force_embed(target, target.get_bodypart(BODY_ZONE_CHEST))) //and if it embeds successfully in their chest, cause a lot of pain
target.apply_damage(max(10, force * 1.2), BRUTE, BODY_ZONE_CHEST, wound_bonus = 0, sharpness = TRUE)
if(QDELETED(src)) // in case trying to embed it caused its deletion (say, if it's DROPDEL)
return
@@ -177,9 +180,12 @@
force = 8
throwforce = 12
armour_penetration = 10
- embedding = list("embed_chance" = 35)
+ embed_type = /datum/embedding/stake/hardened
staketime = 80
+/datum/embedding/stake/hardened
+ embed_chance = 35
+
/obj/item/stake/hardened/silver
name = "silver stake"
desc = "Polished and sharp at the end. For when some mofo is always trying to iceskate uphill."
@@ -188,9 +194,12 @@
siemens_coefficient = 1 //flags = CONDUCT // var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit)
force = 9
armour_penetration = 25
- embedding = list("embed_chance" = 65)
+ embed_type = /datum/embedding/stake/silver
staketime = 60
+/datum/embedding/stake/silver
+ embed_chance = 65
+
/obj/item/stake/hardened/silver/Initialize(mapload)
. = ..()
AddComponent(/datum/component/bane_inducing, /datum/material/silver)
diff --git a/monkestation/code/modules/blueshift/benos/beno_types/sentinel.dm b/monkestation/code/modules/blueshift/benos/beno_types/sentinel.dm
index da4e91932a885..69c650ddcbaff 100644
--- a/monkestation/code/modules/blueshift/benos/beno_types/sentinel.dm
+++ b/monkestation/code/modules/blueshift/benos/beno_types/sentinel.dm
@@ -85,7 +85,7 @@
if(acid_projectile)
var/obj/projectile/spit_projectile = new acid_projectile(user.loc)
- spit_projectile.preparePixelProjectile(target, user, modifiers)
+ spit_projectile.aim_projectile(target, user, modifiers)
spit_projectile.firer = user
spit_projectile.fire()
playsound(user, spit_sound, 100, TRUE, 5, 0.9)
diff --git a/monkestation/code/modules/blueshift/items/field_medic.dm b/monkestation/code/modules/blueshift/items/field_medic.dm
index e45430669d59a..4ee29536980da 100644
--- a/monkestation/code/modules/blueshift/items/field_medic.dm
+++ b/monkestation/code/modules/blueshift/items/field_medic.dm
@@ -115,7 +115,7 @@
tool_behaviors = list(TOOL_WELDER)
//Marksman's throwing knife and a pouch for it
-/obj/item/knife/combat/throwing
+/obj/item/knife/throwing
name = "throwing knife"
desc = "While very well weighted for throwing, the distribution of mass makes it unwieldy for use in melee."
icon = 'monkestation/code/modules/blueshift/icons/throwing.dmi'
@@ -123,8 +123,12 @@
force = 12 // don't stab with this
throwforce = 30 // 38 force on embed? compare contrast with throwing stars.
throw_speed = 4
- embedding = list("pain_mult" = 4, "embed_chance" = 75, "fall_chance" = 10) // +10 embed chance up from combat knife's 65
- bayonet = FALSE // throwing knives probably aren't made for use as bayonets
+ embed_type = /datum/embedding/throwing_knife // +10 embed chance up from combat knife's 65
+
+/datum/embedding/throwing_knife
+ embed_chance = 75
+ pain_mult = 4
+ fall_chance = 10
/obj/item/storage/pouch/ammo/marksman
name = "marksman's knife pouch"
@@ -145,8 +149,8 @@
can_hold = typecacheof(list(/obj/item/knife/combat))
/obj/item/storage/pouch/ammo/marksman/PopulateContents() //can kill most basic enemies with 5 knives, though marksmen shouldn't be soloing enemies anyways
- new /obj/item/knife/combat/throwing(src)
- new /obj/item/knife/combat/throwing(src)
- new /obj/item/knife/combat/throwing(src)
- new /obj/item/knife/combat/throwing(src)
- new /obj/item/knife/combat/throwing(src)
+ new /obj/item/knife/throwing(src)
+ new /obj/item/knife/throwing(src)
+ new /obj/item/knife/throwing(src)
+ new /obj/item/knife/throwing(src)
+ new /obj/item/knife/throwing(src)
diff --git a/monkestation/code/modules/blueshift/items/pepperbell.dm b/monkestation/code/modules/blueshift/items/pepperbell.dm
index b65f844de8c9e..88b77fa082f01 100644
--- a/monkestation/code/modules/blueshift/items/pepperbell.dm
+++ b/monkestation/code/modules/blueshift/items/pepperbell.dm
@@ -43,7 +43,7 @@
stamina = 2.5
shrapnel_type = null
sharpness = NONE
- embedding = null
+ embed_type = null
impact_effect_type = /obj/effect/temp_visual/impact_effect
var/contained_reagent = /datum/reagent/consumable/condensedcapsaicin
var/reagent_volume = 5
diff --git a/monkestation/code/modules/blueshift/structures/flipped_table.dm b/monkestation/code/modules/blueshift/structures/flipped_table.dm
index 7e2ea366d20e0..53a1c38f240ed 100644
--- a/monkestation/code/modules/blueshift/structures/flipped_table.dm
+++ b/monkestation/code/modules/blueshift/structures/flipped_table.dm
@@ -46,7 +46,7 @@
if(istype(mover, /obj/projectile))
var/obj/projectile/projectile = mover
//Lets through bullets shot from behind the cover of the table
- if(projectile.trajectory && angle2dir_cardinal(projectile.trajectory.angle) == dir)
+ if(projectile.movement_vector && angle2dir_cardinal(projectile.movement_vector.angle) == dir)
return TRUE
return FALSE
if(border_dir == dir)
diff --git a/monkestation/code/modules/cybernetics/components/slowing_field.dm b/monkestation/code/modules/cybernetics/components/slowing_field.dm
index 2a04e97e2aa7a..25eda4947af43 100644
--- a/monkestation/code/modules/cybernetics/components/slowing_field.dm
+++ b/monkestation/code/modules/cybernetics/components/slowing_field.dm
@@ -59,7 +59,7 @@
if(isprojectile(arrived))
var/obj/projectile/arrived_proj = arrived
- arrived_proj.pixel_speed_multiplier = bullet_speed_multiplier
+ arrived_proj.speed *= bullet_speed_multiplier
else if(isliving(arrived))
var/mob/living/arrived_movable = arrived
arrived_movable.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/status_effect/slowing_field, TRUE, atom_speed_multiplier)
@@ -75,7 +75,7 @@
if(isprojectile(gone))
var/obj/projectile/arrived_proj = gone
- arrived_proj.pixel_speed_multiplier = 1
+ arrived_proj.speed = initial(arrived_proj.speed)
else if(isliving(gone))
var/mob/living/arrived_movable = gone
arrived_movable.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/slowing_field)
diff --git a/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm b/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm
index 09e260c1b01a3..821e00a39a825 100644
--- a/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm
+++ b/monkestation/code/modules/microfusion/code/microfusion_energy_master.dm
@@ -9,7 +9,6 @@
inhand_icon_state = "mcr01"
lefthand_file = 'monkestation/code/modules/microfusion/icons/guns_lefthand.dmi'
righthand_file = 'monkestation/code/modules/microfusion/icons/guns_righthand.dmi'
- can_bayonet = FALSE
weapon_weight = WEAPON_HEAVY
w_class = WEIGHT_CLASS_BULKY
obj_flags = UNIQUE_RENAME
diff --git a/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm b/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm
index 3a9c3a487cf94..205d1007ebcde 100644
--- a/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm
+++ b/monkestation/code/modules/microfusion/code/microfusion_gun_attachments.dm
@@ -429,18 +429,15 @@ Allows for flashlights bayonets and adds 1 slot to equipment.
microfusion_gun.AddComponent(/datum/component/seclite_attachable, \
light_overlay_icon = 'monkestation/code/modules/microfusion/icons/microfusion_gun40x32.dmi', \
light_overlay = "flight")
- microfusion_gun.can_bayonet = TRUE
+ microfusion_gun.AddComponent(/datum/component/bayonet_attachable, offset_x = 20, offset_y = 12)
/obj/item/microfusion_gun_attachment/rail/remove_attachment(obj/item/gun/microfusion/microfusion_gun)
. = ..()
- var/component_to_delete = microfusion_gun.GetComponent(/datum/component/seclite_attachable)
- if(component_to_delete)
- qdel(component_to_delete)
- microfusion_gun.can_bayonet = initial(microfusion_gun.can_bayonet)
- if(microfusion_gun.bayonet)
- microfusion_gun.bayonet.forceMove(get_turf(microfusion_gun))
- microfusion_gun.bayonet = null
- microfusion_gun.update_appearance()
+ var/list/components_to_delete = list()
+ components_to_delete += microfusion_gun.GetComponent(/datum/component/seclite_attachable)
+ components_to_delete += microfusion_gun.GetComponent(/datum/component/bayonet_attachable)
+ for (var/component in components_to_delete)
+ qdel(component)
microfusion_gun.remove_all_attachments()
/*
diff --git a/monkestation/code/modules/microfusion/code/projectiles.dm b/monkestation/code/modules/microfusion/code/projectiles.dm
index 477d79468d001..5ee30d47a796a 100644
--- a/monkestation/code/modules/microfusion/code/projectiles.dm
+++ b/monkestation/code/modules/microfusion/code/projectiles.dm
@@ -53,7 +53,7 @@
icon_state = "laser_greyscale"
wound_bonus = 0
damage = 20 // You are trading damage for a significant wound bonus and speed increase
- speed = 0.6
+ speed = 1.6
color = LIGHT_COLOR_FLARE
light_color = LIGHT_COLOR_FLARE
@@ -77,7 +77,7 @@
tracer_type = /obj/effect/projectile/tracer/heavy_laser
muzzle_type = /obj/effect/projectile/muzzle/heavy_laser
impact_type = /obj/effect/projectile/impact/heavy_laser
- speed = 0.4
+ speed = 2.5
/obj/projectile/beam/laser/microfusion/xray
name = "x-ray microfusion laser"
diff --git a/monkestation/code/modules/mob/living/carbon/emote.dm b/monkestation/code/modules/mob/living/carbon/emote.dm
index 78fda475d454e..246ac1bd28166 100644
--- a/monkestation/code/modules/mob/living/carbon/emote.dm
+++ b/monkestation/code/modules/mob/living/carbon/emote.dm
@@ -29,7 +29,7 @@
qdel(N)
to_chat(user, span_warning("You're incapable of readying a finger gun in your current state."))
-/obj/item/ammo_casing/caseless/fingergun_bullet
+/obj/item/ammo_casing/fingergun_bullet
name = "imaginary bullet"
desc = "Bullets are not real idiot."
projectile_type = /obj/projectile/bullet/fingergun_bullet
@@ -38,6 +38,10 @@
custom_materials = list()
harmful = FALSE
+/obj/item/ammo_casing/fingergun_bullet/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/caseless)
+
/obj/projectile/bullet/fingergun_bullet
name = "imaginary bullet"
desc = "Bullets are not real idiot."
@@ -46,12 +50,12 @@
damage = 0
hitsound_wall = ""
impact_effect_type = null
- embedding = list(embed_chance=0)
+ embed_type = null
/obj/item/ammo_box/magazine/fingergun_emote
name = "finger gun magazine"
desc = "You should not be seeing this..."
- ammo_type = /obj/item/ammo_casing/caseless/fingergun_bullet
+ ammo_type = /obj/item/ammo_casing/fingergun_bullet
caliber = "bulletsarenotrealyouidiot"
max_ammo = 8
diff --git a/monkestation/code/modules/mob/living/simple_animal/megafauna/wendigo.dm b/monkestation/code/modules/mob/living/simple_animal/megafauna/wendigo.dm
index 6cd736df4b141..ec7919ee9ceb5 100644
--- a/monkestation/code/modules/mob/living/simple_animal/megafauna/wendigo.dm
+++ b/monkestation/code/modules/mob/living/simple_animal/megafauna/wendigo.dm
@@ -1,8 +1,3 @@
-#define WENDIGO_ENRAGED (health <= maxHealth*0.5)
-#define WENDIGO_CIRCLE_SHOTCOUNT 24
-#define WENDIGO_CIRCLE_REPEATCOUNT 8
-#define WENDIGO_SPIRAL_SHOTCOUNT 40
-
/*
Difficulty: Medium
@@ -18,39 +13,4 @@ This is a monkestation override for wendigo
stomp_range = 0
scream_cooldown_time = 5 SECONDS
-
-// Overriding this proc so we can remove the "wave" attack without touching any original code
-/mob/living/simple_animal/hostile/megafauna/wendigo/monkestation_override/spiral_attack()
- var/list/choices = list("Alternating Circle", "Spiral")
- var/spiral_type = pick(choices)
- switch(spiral_type)
- if("Alternating Circle")
- var/shots_per = WENDIGO_CIRCLE_SHOTCOUNT
- for(var/shoot_times in 1 to WENDIGO_CIRCLE_REPEATCOUNT)
- var/offset = shoot_times % 2
- for(var/shot in 1 to shots_per)
- var/angle = shot * 360 / shots_per + (offset * 360 / shots_per) * 0.5
- var/obj/projectile/colossus/wendigo_shockwave/shockwave = new /obj/projectile/colossus/wendigo_shockwave(loc)
- shockwave.firer = src
- shockwave.speed = 3 - WENDIGO_ENRAGED
- shockwave.fire(angle)
- SLEEP_CHECK_DEATH(6 - WENDIGO_ENRAGED * 2, src)
- if("Spiral")
- var/shots_spiral = WENDIGO_SPIRAL_SHOTCOUNT
- var/angle_to_target = get_angle(src, target)
- var/spiral_direction = pick(-1, 1)
- for(var/shot in 1 to shots_spiral)
- var/shots_per_tick = 5 - WENDIGO_ENRAGED * 3
- var/angle_change = (5 + WENDIGO_ENRAGED * shot / 6) * spiral_direction
- for(var/count in 1 to shots_per_tick)
- var/angle = angle_to_target + shot * angle_change + count * 360 / shots_per_tick
- var/obj/projectile/colossus/wendigo_shockwave/shockwave = new /obj/projectile/colossus/wendigo_shockwave(loc)
- shockwave.firer = src
- shockwave.damage = 15
- shockwave.fire(angle)
- SLEEP_CHECK_DEATH(1, src)
-
-#undef WENDIGO_ENRAGED
-#undef WENDIGO_CIRCLE_SHOTCOUNT
-#undef WENDIGO_CIRCLE_REPEATCOUNT
-#undef WENDIGO_SPIRAL_SHOTCOUNT
+ weakened = TRUE
diff --git a/monkestation/code/modules/modular_bartending/garnishes/garnish_items.dm b/monkestation/code/modules/modular_bartending/garnishes/garnish_items.dm
index bfe8accfb8ec2..2358c2df6f432 100644
--- a/monkestation/code/modules/modular_bartending/garnishes/garnish_items.dm
+++ b/monkestation/code/modules/modular_bartending/garnishes/garnish_items.dm
@@ -100,7 +100,12 @@
sharpness = SHARP_POINTY
throwforce = 3
throw_speed = 1
- embedding = EMBED_HARMLESS
+ embed_type = /datum/embedding/olives
+
+/datum/embedding/olives
+ pain_mult = 0
+ jostle_pain_mult = 0
+ ignore_throwspeed_threshold = 0
/obj/item/garnish/umbrellared
name = "red drink umbrella"
diff --git a/monkestation/code/modules/smithing/anvil/smithed_parts/axe_blade.dm b/monkestation/code/modules/smithing/anvil/smithed_parts/axe_blade.dm
index 441864170b896..458c22d79a85e 100644
--- a/monkestation/code/modules/smithing/anvil/smithed_parts/axe_blade.dm
+++ b/monkestation/code/modules/smithing/anvil/smithed_parts/axe_blade.dm
@@ -9,7 +9,7 @@
/obj/item/smithed_part/weapon_part/axe_blade/finish_weapon()
sharpness = SHARP_EDGED
- embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
+ embed_type = /datum/embedding/axe_blade
armour_penetration = 50 * (smithed_quality / 100)
tool_behaviour = TOOL_SAW
@@ -18,3 +18,9 @@
throwforce = force * 1.75
w_class = WEIGHT_CLASS_SMALL
..()
+
+/datum/embedding/axe_blade
+ embed_chance = 35
+ pain_mult = 4
+ fall_chance = 10
+ ignore_throwspeed_threshold = TRUE
diff --git a/monkestation/code/modules/smithing/anvil/smithed_parts/dagger_blade.dm b/monkestation/code/modules/smithing/anvil/smithed_parts/dagger_blade.dm
index b4ce794c9f696..cb3c91356d04d 100644
--- a/monkestation/code/modules/smithing/anvil/smithed_parts/dagger_blade.dm
+++ b/monkestation/code/modules/smithing/anvil/smithed_parts/dagger_blade.dm
@@ -10,7 +10,7 @@
/obj/item/smithed_part/weapon_part/dagger_blade/finish_weapon()
tool_behaviour = TOOL_KNIFE
sharpness = SHARP_POINTY
- embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE)
+ embed_type = /datum/embedding/dagger_blade
armour_penetration = 35 * (smithed_quality / 100)
attack_speed = CLICK_CD_FAST_MELEE
@@ -20,3 +20,8 @@
w_class = WEIGHT_CLASS_SMALL
..()
+/datum/embedding/dagger_blade
+ embed_chance = 65
+ pain_mult = 4
+ fall_chance = 10
+ ignore_throwspeed_threshold = TRUE
diff --git a/monkestation/code/modules/smithing/anvil/smithed_parts/spear_blade.dm b/monkestation/code/modules/smithing/anvil/smithed_parts/spear_blade.dm
index 4d98dd98991d5..00b3324a808e5 100644
--- a/monkestation/code/modules/smithing/anvil/smithed_parts/spear_blade.dm
+++ b/monkestation/code/modules/smithing/anvil/smithed_parts/spear_blade.dm
@@ -11,7 +11,7 @@
/obj/item/smithed_part/weapon_part/spear_blade/finish_weapon()
sharpness = SHARP_POINTY
- embedding = list("impact_pain_mult" = 2, "remove_pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE)
+ embed_type = /datum/embedding/spear_blade
armour_penetration = 60 * (smithed_quality / 100)
reach = 2
AddComponent(/datum/component/multi_hit, icon_state = "stab", height = 2)
@@ -22,3 +22,11 @@
throwforce = force * 1.25
w_class = WEIGHT_CLASS_BULKY
..()
+
+/datum/embedding/spear_blade
+ embed_chance = 65
+ impact_pain_mult = 2
+ remove_pain_mult = 4
+ fall_chance = 10
+ ignore_throwspeed_threshold = TRUE
+
diff --git a/monkestation/code/modules/surgery/organs/internal/butts.dm b/monkestation/code/modules/surgery/organs/internal/butts.dm
index d3f8c13ad53f9..9db584c2de5ea 100644
--- a/monkestation/code/modules/surgery/organs/internal/butts.dm
+++ b/monkestation/code/modules/surgery/organs/internal/butts.dm
@@ -8,7 +8,7 @@
slot = ORGAN_SLOT_BUTT
throw_speed = 1
force = 4
- embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 20)
+ embed_type = /datum/embedding/butt
hitsound = 'sound/misc/fart1.ogg'
body_parts_covered = HEAD
slot_flags = ITEM_SLOT_HEAD
@@ -18,6 +18,12 @@
var/cooling_down = FALSE
var/superfart_armed = FALSE
+/datum/embedding/butt
+ pain_mult = 0
+ jostle_pain_mult = 0
+ ignore_throwspeed_threshold = TRUE
+ embed_chance = 20
+
//ADMIN ONLY ATOMIC ASS
/obj/item/organ/internal/butt/atomic
name = "Atomic Ass"
diff --git a/monkestation/code/modules/veth_misc_items/bingle/bingle_pit.dm b/monkestation/code/modules/veth_misc_items/bingle/bingle_pit.dm
index 475faf4415178..62348607607d7 100644
--- a/monkestation/code/modules/veth_misc_items/bingle/bingle_pit.dm
+++ b/monkestation/code/modules/veth_misc_items/bingle/bingle_pit.dm
@@ -168,6 +168,8 @@
if(QDELETED(content) || HAS_TRAIT(content, TRAIT_FALLING_INTO_BINGLE_HOLE) || isbrain(content))
continue
if(isliving(content) || is_type_in_typecache(content, swallow_blacklist))
+ if(istype(content, /obj/projectile))
+ continue
content.forceMove(content.drop_location())
else if(isobj(content))
item_value_consumed += get_item_value(content)
@@ -424,7 +426,7 @@
if(isbingle(projectile.firer))
return BULLET_ACT_FORCE_PIERCE // Projectiles from bingles pass through
if(parent_pit)
- return parent_pit.bullet_act(projectile)
+ return parent_pit.projectile_hit(projectile)
else
return ..()
diff --git a/monkestation/code/modules/viking/viking_axes.dm b/monkestation/code/modules/viking/viking_axes.dm
index 12aadfc3b6cb1..812f5d5e6ddfd 100644
--- a/monkestation/code/modules/viking/viking_axes.dm
+++ b/monkestation/code/modules/viking/viking_axes.dm
@@ -9,6 +9,9 @@
righthand_file = 'monkestation/icons/viking/axes_righthand.dmi'
worn_icon = 'monkestation/icons/viking/viking_armor.dmi'
+/datum/embedding/viking_axe
+ embed_chance = 50
+
/obj/item/melee/viking/tenja
name = "boarding axe"
icon_state = "hand_axe"
@@ -17,7 +20,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
force = 20
throwforce = 45
- embedding = 50
+ embed_type = /datum/embedding/viking_axe
wound_bonus = 25
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
@@ -30,12 +33,15 @@
hitsound = 'sound/weapons/bladeslice.ogg'
force = 25
throwforce = 65
- embedding = 75
+ embed_type = /datum/embedding/viking_axe/godly
sharpness = SHARP_EDGED
wound_bonus = 30
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
+/datum/embedding/viking_axe/godly
+ embed_chance = 75
+
/obj/item/melee/viking/godly_tenja/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
if(iscarbon(target))
@@ -43,6 +49,7 @@
carbon_target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 4)
carbon_target.reagents.add_reagent(/datum/reagent/consumable/ice, 4)
carbon_target.reagents.add_reagent(/datum/reagent/medicine/c2/hercuri, 4)
+
/obj/item/melee/viking/genja
name = "battle axe"
icon_state = "battleaxe0"
@@ -51,7 +58,7 @@
desc = "A large 2 handed axe used for raiding."
force = 15
throwforce = 60
- embedding = 50
+ embed_type = /datum/embedding/viking_axe
sharpness = SHARP_EDGED
hitsound = 'sound/weapons/bladeslice.ogg'
wound_bonus = 30
@@ -70,6 +77,7 @@
/obj/item/melee/viking/genja/update_icon_state()
icon_state = "[base_icon_state]0"
return ..()
+
/obj/item/melee/viking/skeggox
name = "grappling axe"
icon_state = "hooking_axe_item"
@@ -80,13 +88,12 @@
hitsound = 'sound/weapons/bladeslice.ogg'
force = 18
throwforce = 40
- embedding = 50
+ embed_type = /datum/embedding/viking_axe
sharpness = SHARP_EDGED
wound_bonus = 20
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
-
/obj/item/melee/viking/skeggox/afterattack(target, mob/user, proximity_flag)
. = ..()
if(ishuman(target) && proximity_flag)
@@ -102,7 +109,7 @@
desc = "A massive two handed axe gilded and inscribed with runes."
force = 20
throwforce = 60
- embedding = 50
+ embed_type = /datum/embedding/viking_axe
sharpness = SHARP_EDGED
hitsound = 'sound/weapons/bladeslice.ogg'
wound_bonus = 50
@@ -110,7 +117,7 @@
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
slot_flags = ITEM_SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
-/// How much damage to do unwielded
+ /// How much damage to do unwielded
force_unwielded = 20
/// How much damage to do wielded
force_wielded = 35
diff --git a/monkestation/code/modules/visual_changes/debris.dm b/monkestation/code/modules/visual_changes/debris.dm
index b39e73c89199b..8f63cc37aaf21 100644
--- a/monkestation/code/modules/visual_changes/debris.dm
+++ b/monkestation/code/modules/visual_changes/debris.dm
@@ -57,7 +57,7 @@
INVOKE_ASYNC(src, PROC_REF(on_impact), source, proj)
/datum/element/debris/proc/on_impact(datum/source, obj/projectile/P)
- var/angle = !isnull(P.Angle) ? P.Angle : round(get_angle(P.starting, source), 1)
+ var/angle = !isnull(P.angle) ? P.angle : round(get_angle(P.starting, source), 1)
var/x_component = sin(angle) * debris_velocity
var/y_component = cos(angle) * debris_velocity
var/x_component_smoke = sin(angle) * -15
diff --git a/tgstation.dme b/tgstation.dme
index 982fc140ea784..1b1e1bbc1c105 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -921,6 +921,7 @@
#include "code\datums\dog_fashion.dm"
#include "code\datums\ductnet.dm"
#include "code\datums\eigenstate.dm"
+#include "code\datums\embedding.dm"
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\forced_movement.dm"
@@ -1000,6 +1001,7 @@
#include "code\datums\actions\mobs\dash.dm"
#include "code\datums\actions\mobs\defensive_mode.dm"
#include "code\datums\actions\mobs\fire_breath.dm"
+#include "code\datums\actions\mobs\ground_slam.dm"
#include "code\datums\actions\mobs\lava_swoop.dm"
#include "code\datums\actions\mobs\meteors.dm"
#include "code\datums\actions\mobs\mobcooldown.dm"
@@ -1008,6 +1010,7 @@
#include "code\datums\actions\mobs\projectileattack.dm"
#include "code\datums\actions\mobs\sign_language.dm"
#include "code\datums\actions\mobs\sneak.dm"
+#include "code\datums\actions\mobs\teleport.dm"
#include "code\datums\actions\mobs\transform_weapon.dm"
#include "code\datums\actions\mobs\sequences\dash_attack.dm"
#include "code\datums\actions\mobs\sequences\projectile.dm"
@@ -1170,6 +1173,7 @@
#include "code\datums\components\basic_inhands.dm"
#include "code\datums\components\basic_mob_attack_telegraph.dm"
#include "code\datums\components\basic_ranged_ready_overlay.dm"
+#include "code\datums\components\bayonet_attachable.dm"
#include "code\datums\components\beetlejuice.dm"
#include "code\datums\components\blob_minion.dm"
#include "code\datums\components\blood_walk.dm"
@@ -1219,7 +1223,6 @@
#include "code\datums\components\effect_remover.dm"
#include "code\datums\components\egg_layer.dm"
#include "code\datums\components\electrified_buckle.dm"
-#include "code\datums\components\embedded.dm"
#include "code\datums\components\engraved.dm"
#include "code\datums\components\evolutionary_leap.dm"
#include "code\datums\components\explodable.dm"
@@ -1510,7 +1513,6 @@
#include "code\datums\elements\easily_fragmented.dm"
#include "code\datums\elements\effect_trail.dm"
#include "code\datums\elements\elevation.dm"
-#include "code\datums\elements\embed.dm"
#include "code\datums\elements\empprotection.dm"
#include "code\datums\elements\envenomable_casing.dm"
#include "code\datums\elements\eyestab.dm"
@@ -5556,12 +5558,12 @@
#include "code\modules\power\lighting\light_items.dm"
#include "code\modules\power\lighting\light_mapping_helpers.dm"
#include "code\modules\power\lighting\light_wallframes.dm"
-#include "code\modules\power\singularity\boh_tear.dm"
#include "code\modules\power\singularity\containment_field.dm"
#include "code\modules\power\singularity\dark_matter_singularity.dm"
#include "code\modules\power\singularity\emitter.dm"
#include "code\modules\power\singularity\field_generator.dm"
#include "code\modules\power\singularity\narsie.dm"
+#include "code\modules\power\singularity\reality_tear.dm"
#include "code\modules\power\singularity\singularity.dm"
#include "code\modules\power\supermatter\supermatter.dm"
#include "code\modules\power\supermatter\supermatter_extra_effects.dm"
@@ -5597,19 +5599,17 @@
#include "code\modules\projectiles\ammunition\_ammunition.dm"
#include "code\modules\projectiles\ammunition\_firing.dm"
#include "code\modules\projectiles\ammunition\ballistic\crossbow.dm"
+#include "code\modules\projectiles\ammunition\ballistic\foam.dm"
#include "code\modules\projectiles\ammunition\ballistic\grenade.dm"
+#include "code\modules\projectiles\ammunition\ballistic\harpoon.dm"
#include "code\modules\projectiles\ammunition\ballistic\lmg.dm"
#include "code\modules\projectiles\ammunition\ballistic\pistol.dm"
#include "code\modules\projectiles\ammunition\ballistic\revolver.dm"
#include "code\modules\projectiles\ammunition\ballistic\rifle.dm"
+#include "code\modules\projectiles\ammunition\ballistic\rocket.dm"
#include "code\modules\projectiles\ammunition\ballistic\shotgun.dm"
#include "code\modules\projectiles\ammunition\ballistic\smg.dm"
#include "code\modules\projectiles\ammunition\ballistic\sniper.dm"
-#include "code\modules\projectiles\ammunition\caseless\_caseless.dm"
-#include "code\modules\projectiles\ammunition\caseless\energy.dm"
-#include "code\modules\projectiles\ammunition\caseless\foam.dm"
-#include "code\modules\projectiles\ammunition\caseless\harpoon.dm"
-#include "code\modules\projectiles\ammunition\caseless\rocket.dm"
#include "code\modules\projectiles\ammunition\energy\_energy.dm"
#include "code\modules\projectiles\ammunition\energy\ebow.dm"
#include "code\modules\projectiles\ammunition\energy\gravity.dm"
@@ -5686,6 +5686,7 @@
#include "code\modules\projectiles\projectile\bullets\crossbow.dm"
#include "code\modules\projectiles\projectile\bullets\dart_syringe.dm"
#include "code\modules\projectiles\projectile\bullets\dnainjector.dm"
+#include "code\modules\projectiles\projectile\bullets\foam_dart.dm"
#include "code\modules\projectiles\projectile\bullets\grenade.dm"
#include "code\modules\projectiles\projectile\bullets\lmg.dm"
#include "code\modules\projectiles\projectile\bullets\pistol.dm"
@@ -5706,8 +5707,6 @@
#include "code\modules\projectiles\projectile\energy\stun.dm"
#include "code\modules\projectiles\projectile\energy\tesla.dm"
#include "code\modules\projectiles\projectile\energy\thermal.dm"
-#include "code\modules\projectiles\projectile\reusable\_reusable.dm"
-#include "code\modules\projectiles\projectile\reusable\foam_dart.dm"
#include "code\modules\projectiles\projectile\special\curse.dm"
#include "code\modules\projectiles\projectile\special\floral.dm"
#include "code\modules\projectiles\projectile\special\gravity.dm"