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"