From 0b99107cc283f5e39c334e1cc5dc5305450ab84a Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:46:13 +0100 Subject: [PATCH] Guncode Agony 3: Projectile code cleanup (#87628) ## About The Pull Request Massive cleanup/pseudo-refactor of projectile and projectile-adjacent code. One letter variables, weird logic, some runtimes, all of that. Atomized in a separate PR from the actual refactor so we don't end up with a 5k line PR. ## Why It's Good For The Game Makes the code possible to work with before I nuke pixel_move and kevinz units:tm: --- code/datums/components/parry.dm | 4 +- code/datums/components/pellet_cloud.dm | 26 +- code/datums/status_effects/debuffs/debuffs.dm | 9 +- code/datums/wounds/bones.dm | 4 +- code/datums/wounds/pierce.dm | 4 +- code/game/atom/_atom.dm | 2 +- code/game/objects/items/hand_items.dm | 2 +- code/game/objects/items/toys.dm | 8 +- code/game/objects/obj_defense.dm | 1 - code/game/objects/structures/reflector.dm | 32 +- code/modules/fishing/fishing_rod.dm | 1 + code/modules/hydroponics/hydroponics.dm | 15 +- code/modules/hydroponics/seeds.dm | 24 +- code/modules/mob/living/basic/alien/_alien.dm | 3 + .../mob/living/carbon/alien/alien_defense.dm | 3 + .../mob/living/carbon/human/human_defense.dm | 5 +- code/modules/mob/living/living_defense.dm | 100 +++-- .../computers/machinery/modular_computer.dm | 4 +- code/modules/projectiles/guns/ballistic.dm | 2 +- code/modules/projectiles/projectile.dm | 412 ++++++++---------- .../projectile/bullets/_incendiary.dm | 2 +- .../projectiles/projectile/bullets/rifle.dm | 8 +- .../projectiles/projectile/bullets/special.dm | 2 +- code/modules/projectiles/projectile/magic.dm | 2 +- .../mecha/equipment/tools/mining_tools.dm | 6 +- 25 files changed, 343 insertions(+), 338 deletions(-) diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm index a2cfe789cba..98bc9e3a84d 100644 --- a/code/datums/components/parry.dm +++ b/code/datums/components/parry.dm @@ -101,8 +101,8 @@ 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)) < 15) + source.set_angle((source.angle + 180) % 360 + rand(-3, 3)) else source.set_angle(dir2angle(user) + 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!")) diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index c206af62f70..e9b7fad34fc 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -270,22 +270,22 @@ /// 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(WEAKREF(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.preparePixelProjectile(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.process_hit_loop(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() diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index 5dab783eb37..806bbc39d8b 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -386,11 +386,10 @@ /datum/status_effect/stacking/saw_bleed/threshold_cross_effect() owner.adjustBruteLoss(bleed_damage) - var/turf/T = get_turf(owner) - new /obj/effect/temp_visual/bleed/explode(T) - for(var/d in GLOB.alldirs) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d) - playsound(T, SFX_DESECRATION, 100, TRUE, -1) + 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 id = "bloodletting" diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 667684c0f9f..8209fb93fac 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -159,7 +159,7 @@ span_danger("You spit out a string of blood from the blow to your chest!"), vision_distance = COMBAT_MESSAGE_RANGE, ) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.create_splatter(victim.dir) victim.bleed(blood_bled) if(20 to INFINITY) victim.visible_message( @@ -168,7 +168,7 @@ vision_distance = COMBAT_MESSAGE_RANGE, ) victim.bleed(blood_bled) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.create_splatter(victim.dir) victim.add_splatter_floor(get_step(victim.loc, victim.dir)) /datum/wound/blunt/bone/modify_desc_before_span(desc) diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 01af1fa44d1..6c6eb74ec31 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -54,7 +54,7 @@ span_danger("You spit out a string of blood from the blow to your [limb.plaintext_zone]!"), vision_distance = COMBAT_MESSAGE_RANGE, ) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.create_splatter(victim.dir) victim.bleed(blood_bled) if(20 to INFINITY) victim.visible_message( @@ -63,7 +63,7 @@ vision_distance = COMBAT_MESSAGE_RANGE, ) victim.bleed(blood_bled) - new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.create_splatter(victim.dir) victim.add_splatter_floor(get_step(victim.loc, victim.dir)) /datum/wound/pierce/bleed/get_bleed_rate_of_change() diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 473336b1ccc..19dbc2de330 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -199,7 +199,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/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 9674a7f6d99..005bcff7677 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -561,7 +561,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/toys.dm b/code/game/objects/items/toys.dm index b622a761c42..632fb39a47b 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -192,11 +192,11 @@ else return ..() -/obj/item/toy/balloon/bullet_act(obj/projectile/P) - if((istype(P,/obj/projectile/bullet/p50) || istype(P,/obj/projectile/bullet/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_sfx/cartoon_pop.ogg', 50, vary = TRUE) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 748af49a694..77f670bf9fc 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -28,7 +28,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( diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 5cd3f58df05..532ecebde0e 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.decayedRange + proj.decayedRange = max(proj.decayedRange--, 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(new_angle) return ..() //DOUBLE @@ -217,10 +217,10 @@ 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.set_angle_centered(new_angle) return ..() //BOX diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index e876f4764f5..cf8d29e7664 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -411,6 +411,7 @@ casting = TRUE var/obj/projectile/fishing_cast/cast_projectile = new(get_turf(src)) cast_projectile.range = get_cast_range(user) + cast_projectile.decayedRange = get_cast_range(user) cast_projectile.owner = src cast_projectile.original = target cast_projectile.fired_from = src diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 3a1a6d83e7d..53ffae4aca0 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -285,20 +285,21 @@ // Plumbing pauses if reagents is full.. so let's cheat and make sure it ticks unless both trays are happy reagents = hydro_parent.waterlevel < hydro_parent.maxwater ? water_reagents : nutri_reagents -/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. +/obj/machinery/hydroponics/bullet_act(obj/projectile/proj) //Works with the Somatoray to modify plant variables. 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.bullet_act(proj) + if(istype(proj, /obj/projectile/energy/flora/evolution)) if(myseed) if(LAZYLEN(myseed.mutatelist)) myseed.set_instability(myseed.instability/2) mutatespecie() - else - return ..() + return BULLET_ACT_HIT + return ..() /obj/machinery/hydroponics/power_change() . = ..() diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 86847f01173..4589a3c4312 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -169,20 +169,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(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) - 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/mob/living/basic/alien/_alien.dm b/code/modules/mob/living/basic/alien/_alien.dm index 99b615fbf60..59aca1021d4 100644 --- a/code/modules/mob/living/basic/alien/_alien.dm +++ b/code/modules/mob/living/basic/alien/_alien.dm @@ -74,6 +74,9 @@ visible_message(span_alertalien("[src] plants some alien weeds!")) new /obj/structure/alien/weeds/node(loc) +/mob/living/basic/alien/create_splatter(splatter_dir) + new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(get_turf(src), splatter_dir) + ///Lays an egg on the turf the mob is currently standing on. /mob/living/basic/alien/proc/lay_alien_egg() if(!isturf(loc) || isspaceturf(loc)) diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index 751676a74ae..4aeed5c2b9d 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -66,6 +66,9 @@ In all, this is a lot like the monkey code. /N var/obj/item/bodypart/affecting = get_bodypart(get_random_valid_zone(user.zone_selected)) apply_damage(rand(1, 3), BRUTE, affecting) +/mob/living/carbon/alien/create_splatter(splatter_dir) + new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(get_turf(src), splatter_dir) + /mob/living/carbon/alien/ex_act(severity, target, origin) . = ..() if(!. || QDELETED(src)) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index caec7392a46..13700dd5aad 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -99,9 +99,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/living_defense.dm b/code/modules/mob/living/living_defense.dm index c40042958df..5eb78154373 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -91,44 +91,85 @@ /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) . = ..() - 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 = check_projectile_armor(def_zone, hitting_projectile, is_silent = TRUE) + var/blocked = check_projectile_armor(def_zone, proj, 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)]" + + 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(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 = min(ARMOR_MAX_BLOCK, armor_check), //cap damage reduction at 90% - wound_bonus = hitting_projectile.wound_bonus, - bare_wound_bonus = hitting_projectile.bare_wound_bonus, - sharpness = hitting_projectile.sharpness, - attack_direction = get_dir(hitting_projectile.starting, src), + wound_bonus = proj.wound_bonus, + bare_wound_bonus = proj.bare_wound_bonus, + sharpness = proj.sharpness, + attack_direction = get_dir(proj.starting, src), ) + 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, - stamina = hitting_projectile.stamina, - 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())) + create_splatter(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) return run_armor_check(def_zone, impacting_projectile.armor_flag, "","",impacting_projectile.armour_penetration, "", is_silent, impacting_projectile.weak_against_armour) @@ -210,6 +251,9 @@ hitpush = FALSE return ..() +/mob/living/proc/create_splatter(splatter_dir) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(get_turf(src), splatter_dir) + ///The core of catching thrown items, which non-carbons cannot without the help of items or abilities yet, as they've no throw mode. /mob/living/proc/try_catch_item(obj/item/item, skip_throw_mode_check = FALSE, try_offhand = FALSE) if(!can_catch_item(skip_throw_mode_check, try_offhand) || !isitem(item) || HAS_TRAIT(item, TRAIT_UNCATCHABLE) || !isturf(item.loc)) @@ -642,7 +686,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/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm index 48cbc57d745..2d81fb6c3b5 100644 --- a/code/modules/modular_computers/computers/machinery/modular_computer.dm +++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm @@ -186,7 +186,7 @@ // "Stun" weapons can cause minor damage to components (short-circuits?) // "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) || ..() +/obj/machinery/modular_computer/bullet_act(obj/projectile/proj) + return cpu?.bullet_act(proj) || ..() #undef CPU_INTERACTABLE diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 4a985507f49..45c1e9c65ea 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -464,7 +464,7 @@ return TRUE /obj/item/gun/ballistic/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - var/could_it_misfire = (can_misfire && chambered.can_misfire != FALSE) || chambered.can_misfire + var/could_it_misfire = chambered && chambered.can_misfire if(target != user && chambered.loaded_projectile && could_it_misfire && prob(misfire_probability) && blow_up(user)) to_chat(user, span_userdanger("[src] misfires!")) return diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index a94d7adabe1..0b03f412931 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -68,6 +68,8 @@ var/projectile_piercing = NONE /// 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 /// If objects are below this layer, we pass through them var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER @@ -86,7 +88,7 @@ var/pixel_speed_multiplier = 1 /// 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 + var/angle 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 @@ -112,7 +114,7 @@ /// Can our ricochet autoaim hit our firer? var/ricochet_shoots_firer = TRUE - ///If the object being hit can pass ths damage on to something else, it should not do it for this bullet + ///If the object being hit can pass the damage on to something else, it should not do it for this bullet var/force_hit = FALSE //Hitscan @@ -283,16 +285,17 @@ // 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/L = target - hit_limb_zone = L.check_hit_limb_zone_name(def_zone) + 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, hit_limb_zone, blocked) - SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb_zone, blocked) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, angle, hit_limb_zone, blocked) + SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, angle, hit_limb_zone, blocked) if(QDELETED(src)) // 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/turf/target_turf = get_turf(target) var/hitx var/hity if(target == original) @@ -303,10 +306,7 @@ hity = target.pixel_y + rand(-8, 8) if(isturf(target) && hitsound_wall) - var/volume = clamp(vol_by_damage() + 20, 0, 100) - if(suppressed) - volume = 5 - playsound(loc, hitsound_wall, volume, TRUE, -1) + playsound(src, hitsound_wall, clamp(vol_by_damage() + (suppressed ? 0 : 20), 0, 100), TRUE, -1) if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75)) var/turf/closed/wall/target_wall = target_turf @@ -314,58 +314,20 @@ new impact_effect_type(target_wall, hitx, hity) target_wall.add_dent(WALL_DENT_SHOT, hitx, hity) - return BULLET_ACT_HIT - if(!isliving(target)) + if (hitsound) + playsound(src, hitsound, vol_by_damage(), TRUE, -1) + + if (!isliving(target)) if(impact_effect_type && !hitscan) new impact_effect_type(target_turf, hitx, hity) - return BULLET_ACT_HIT + if((blocked >= 100 || (damage && damage_type != BRUTE)) && impact_effect_type && !hitscan) + new impact_effect_type(target_turf, hitx, hity) + var/mob/living/living_target = target - - if(blocked != 100) // not completely blocked - var/obj/item/bodypart/hit_bodypart = living_target.get_bodypart(hit_limb_zone) - if (damage && damage_type == BRUTE) - if (living_target.blood_volume && (isnull(hit_bodypart) || hit_bodypart.can_bleed())) - var/splatter_dir = dir - if(starting) - splatter_dir = get_dir(starting, target_turf) - if(isalien(living_target)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_turf, splatter_dir) - else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_turf, splatter_dir) - if(prob(33)) - living_target.add_splatter_floor(target_turf) - else 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 = ((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(hit_limb_zone) - organ_hit_text = " in \the [living_target.parse_zone_with_bodypart(hit_limb_zone)]" - if(suppressed == SUPPRESSED_VERY) - playsound(loc, hitsound, 5, TRUE, -1) - else if(suppressed) - playsound(loc, hitsound, 5, TRUE, -1) - to_chat(living_target, span_userdanger("You're shot by \a [src][organ_hit_text]!")) - else - if(hitsound) - var/volume = vol_by_damage() - playsound(src, hitsound, volume, TRUE, -1) - living_target.visible_message(span_danger("[living_target] is hit by \a [src][organ_hit_text]!"), \ - span_userdanger("You're hit by \a [src][organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE) - if(living_target.is_blind()) - to_chat(living_target, span_userdanger("You feel something hit you[organ_hit_text]!")) - var/reagent_note if(reagents?.reagent_list) reagent_note = "REAGENTS: [pretty_string_from_reagent_list(reagents.reagent_list)]" @@ -376,7 +338,6 @@ 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() @@ -384,17 +345,20 @@ for(var/mob/logged_mob as anything in logging_mobs) log_combat(logged_mob, living_target, "shot", src, "from inside [firing_vehicle][logging_mobs.len > 1 ? " with multiple occupants" : null][reagent_note ? " and contained [reagent_note]" : null]") return BULLET_ACT_HIT + if(!do_not_log) living_target.log_message("has been shot by [firer] with [src][reagent_note ? " containing [reagent_note]" : null]", LOG_ATTACK, color="orange") 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/on_ricochet(atom/A) +/obj/projectile/proc/on_ricochet(atom/target) + ricochets++ if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range) return @@ -402,27 +366,28 @@ 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)) +/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)) return - Impact(A) + impact(bumped_atom) /** * Called when the projectile hits something @@ -435,89 +400,82 @@ * 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.weak_reference]) // NEVER doublehit - 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 - - var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations. - def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * distance), 5, 100)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use. - - return process_hit(T, select_target(T, A, A), A) // SELECT TARGET FIRST! - -/** - * 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) +/obj/projectile/proc/impact(atom/target) + if(impacted[target.weak_reference]) // never doublehit, otherwise someone may end up running into a projectile from the back return - // 2. - impacted[WEAKREF(target)] = TRUE //hash lookup > in for performance in hit-checking - // 3. - var/mode = prehit_pierce(target) - if(mode == PROJECTILE_DELETE_WITHOUT_HITTING) - qdel(src) - return hit_something - else if(mode == PROJECTILE_PIERCE_PHASE) + + 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 + decayedRange = max(0, decayedRange - reflect_range_decrease) + ricochet_chance *= ricochet_decay_chance + damage *= ricochet_decay_damage + stamina *= ricochet_decay_damage + range = decayedRange + if(hitscan && trajectory) + store_hitscan_collision(trajectory.copy_to()) + return + + var/turf/target_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(target_turf, starting)), 5, 100)) + process_hit_loop(select_target(target_turf, target)) + +/* + * 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. + */ +/obj/projectile/proc/process_hit_loop(atom/target) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_NOT_OVERRIDE(TRUE) + + var/turf/target_turf = get_turf(target) + while (target && !QDELETED(src)) + // 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) + qdel(src) + return + + // 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 + + if (SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args, src) & PROJECTILE_INTERRUPT_HIT) + qdel(src) + return + + if (SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, args) & PROJECTILE_INTERRUPT_HIT) + qdel(src) + return + + 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.bullet_act(src, def_zone, mode == PROJECTILE_PIERCE_HIT) + if (result != BULLET_ACT_FORCE_PIERCE && max_pierces && pierces >= max_pierces) + qdel(src) + return + + // If we're not piercing or phasing, delete ourselves + if (result != BULLET_ACT_FORCE_PIERCE && mode != PROJECTILE_PIERCE_HIT && mode != PROJECTILE_PIERCE_PHASE) + qdel(src) + return + + // 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, hit_something) // try to hit something else - // at this point we are going to hit the thing - // in which case send signal to it - if ((SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args, src) & PROJECTILE_INTERRUPT_HIT) || (SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, args) & PROJECTILE_INTERRUPT_HIT)) - 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)) - 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) /** * Selects a target to hit from a turf @@ -539,7 +497,7 @@ * 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, TRUE, TRUE)) return bumped @@ -554,9 +512,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 @@ -581,9 +539,12 @@ living_target.block_projectile_effects() 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(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)) @@ -620,16 +581,16 @@ /** * 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 + * 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 + * 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) +/obj/projectile/proc/scan_crossed_hit(atom/movable/crossed_atom) + if(can_hit_target(crossed_atom, direct_target = (crossed_atom == original))) + impact(crossed_atom) /** * Scans if we should hit something on the turf we just moved to if we haven't already @@ -643,21 +604,22 @@ // 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) + scan_crossed_hit(entered_atom) /** * Projectile can pass through @@ -690,41 +652,41 @@ * 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) + 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(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 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)) + 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) + 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() @@ -759,7 +721,7 @@ 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) @@ -767,18 +729,19 @@ RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(firer_deleted)) SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original) if (original) - RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted)) + if (firer != original) + RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted)) if(!log_override && firer && original && !do_not_log) 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) + process_hit_loop(direct_target) if(QDELETED(src)) return var/turf/starting = get_turf(src) - if(isnum(angle)) - set_angle(angle) - else if(isnull(Angle)) //Try to resolve through offsets if there's no angle set. + 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) @@ -786,15 +749,15 @@ var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z) set_angle(get_angle(src, target)) if(spread) - set_angle(Angle + (rand() - 0.5) * spread) - original_angle = Angle + set_angle(angle + (rand() - 0.5) * spread) + original_angle = angle trajectory_ignore_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) + 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) process_hitscan() @@ -806,8 +769,8 @@ /obj/projectile/proc/set_angle(new_angle) //wrapper for overrides. if(!nondirectional_sprite) - transform = transform.TurnTo(Angle, new_angle) - Angle = new_angle + transform = transform.TurnTo(angle, new_angle) + angle = new_angle if(trajectory) trajectory.set_angle(new_angle) if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) @@ -819,6 +782,9 @@ /obj/projectile/proc/firer_deleted(datum/source) SIGNAL_HANDLER + // Shooting yourself point-blank + if (firer == original) + original = null firer = null /obj/projectile/proc/original_deleted(datum/source) @@ -828,8 +794,8 @@ /// 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) if(!nondirectional_sprite) - transform = transform.TurnTo(Angle, new_angle) - Angle = new_angle + transform = transform.TurnTo(angle, new_angle) + angle = new_angle if(trajectory) trajectory.set_angle(new_angle) @@ -843,8 +809,6 @@ store_hitscan_collision(point_cache) return TRUE - - /obj/projectile/forceMove(atom/target) if(!isloc(target) || !isloc(loc) || !z) return ..() @@ -865,12 +829,14 @@ after_z_change(old, target) /obj/projectile/proc/after_z_change(atom/olcloc, atom/newloc) + return /obj/projectile/proc/before_z_change(atom/oldloc, atom/newloc) + return /obj/projectile/vv_edit_var(var_name, var_value) switch(var_name) - if(NAMEOF(src, Angle)) + if(NAMEOF(src, angle)) set_angle(var_value) return TRUE else @@ -890,7 +856,7 @@ /obj/projectile/proc/process_hitscan() var/safety = range * 10 - record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, Angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1)) + record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1)) while(loc && !QDELETED(src)) if(paused) stoplag(1) @@ -917,23 +883,23 @@ if(QDELETED(src)) return trajectory.increment(trajectory_multiplier) - var/turf/T = trajectory.return_turf() - if(!istype(T)) + var/turf/cur_turf = trajectory.return_turf() + if(!istype(cur_turf)) // step back to the last valid turf before we Destroy trajectory.increment(-trajectory_multiplier) qdel(src) return - if (T == loc) + if (cur_turf == loc) continue - if (T.z == loc.z) - step_towards(src, T) + if (cur_turf.z == loc.z) + step_towards(src, cur_turf) hitscan_last = loc SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP) continue var/old = loc - before_z_change(loc, T) + before_z_change(loc, cur_turf) trajectory_ignore_forcemove = TRUE - forceMove(T) + forceMove(cur_turf) trajectory_ignore_forcemove = FALSE after_z_change(old, loc) if(!hitscanning) @@ -953,17 +919,17 @@ /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)) + var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target) + new_point.x += clamp(homing_offset_x, 1, world.maxx) + new_point.y += clamp(homing_offset_y, 1, world.maxy) + 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)) -/obj/projectile/proc/set_homing_target(atom/A) - if(!A || (!isturf(A) && !isturf(A.loc))) +/obj/projectile/proc/set_homing_target(atom/target) + if(!target || (!isturf(target) && !isturf(target.loc))) return FALSE homing = TRUE - homing_target = A + 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)) @@ -1097,12 +1063,12 @@ 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_range, hitscan_light_color_override, hitscan_light_intensity, tempref) + for(var/datum/point/beam_point in beam_segments) + generate_tracer_between_points(beam_point, beam_segments[beam_point], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) if(muzzle_type && duration > 0) - var/datum/point/p = beam_segments[1] + var/datum/point/beam_point = beam_segments[1] var/atom/movable/thing = new muzzle_type - p.move_atom_to_src(thing) + beam_point.move_atom_to_src(thing) var/matrix/matrix = new matrix.Turn(original_angle) thing.transform = matrix @@ -1110,11 +1076,11 @@ thing.set_light(muzzle_flash_range, muzzle_flash_intensity, 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/datum/point/beam_point = beam_segments[beam_segments[beam_segments.len]] var/atom/movable/thing = new impact_type - p.move_atom_to_src(thing) + beam_point.move_atom_to_src(thing) var/matrix/matrix = new - matrix.Turn(Angle) + matrix.Turn(angle) thing.transform = matrix thing.color = color thing.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) @@ -1160,7 +1126,7 @@ 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) diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index e151afa294d..2e526adb053 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -69,7 +69,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/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 2a80c366f03..4caec924625 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -144,6 +144,7 @@ speed = 0.6 projectile_piercing = PASSMOB|PASSVEHICLE projectile_phasing = ~(PASSMOB|PASSVEHICLE) + max_pierces = 3 phasing_ignore_direct_target = TRUE dismemberment = 0 //goes through clean. damage_type = BRUTE @@ -155,7 +156,7 @@ embed_falloff_tile = -3 accurate_range = 205 //15 tiles before falloff starts to kick in -/obj/projectile/bullet/rebar/hydrogen/Impact(atom/A) +/obj/projectile/bullet/rebar/hydrogen/impact(atom/A) . = ..() def_zone = ran_zone(def_zone, clamp(205-(7*get_dist(get_turf(A), starting)), 5, 100)) @@ -167,11 +168,6 @@ return BULLET_ACT_FORCE_PIERCE return ..() -/obj/projectile/bullet/rebar/hydrogen/process_hit(turf/T, atom/target, atom/bumped, hit_something) - . = ..() - if(pierces >= 3) - qdel(src) - /obj/projectile/bullet/rebar/healium name = "healium bolt" icon_state = "rebar_healium" diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index 34f6c1e0718..c1299b3bed0 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -73,7 +73,7 @@ return ..() coin_check.check_splitshot(firer, src) - Impact(coin_check) + impact(coin_check) /// Marksman Coin /obj/projectile/bullet/coin diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index dd9f419b7fd..5c050bdebb6 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -287,7 +287,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 diff --git a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm index bcf6d9a86ae..e08020a545f 100644 --- a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm @@ -185,11 +185,7 @@ target.apply_damage(10, BRUTE, def_zone, blocked) //blood splatters - var/splatter_dir = get_dir(chassis, target) - if(isalien(target)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target.drop_location(), splatter_dir) - else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target.drop_location(), splatter_dir) + target.create_splatter(get_dir(chassis, target)) //organs go everywhere if(target_part && blocked < 100 && prob(10 * drill_level))