From 438faac253abe4f2d0b2d59097866596ce92ca0c Mon Sep 17 00:00:00 2001 From: chuga-git <98280110+chuga-git@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:08:57 -0500 Subject: [PATCH] Refactors projectile ricochets (#25764) * same taste, twice the speed * pulls proc/handle_ricochet() to the atom level. adds var/flags_ricochet and related defines. * space indentation sneaks its way in yet again --------- Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> --- code/__DEFINES/flags.dm | 12 +++-- code/game/atoms.dm | 23 +++++++++- code/game/machinery/machinery.dm | 2 + code/game/objects/objs.dm | 8 +++- code/game/objects/structures.dm | 2 + code/game/objects/structures/window.dm | 2 + code/game/turfs/simulated/walls.dm | 12 +---- code/game/turfs/simulated/walls_mineral.dm | 2 +- .../blob/blob_structures/strong_blob.dm | 14 +----- code/modules/projectiles/projectile_base.dm | 46 +++++++++++++++++-- 10 files changed, 86 insertions(+), 37 deletions(-) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 01bf016d00e..94028b193bd 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -63,12 +63,10 @@ #define OVERLAY_QUEUED_2 (1<<12) -#define CHECK_RICOCHET_2 (1<<13) - /// should the contents of this atom be acted upon -#define RAD_PROTECT_CONTENTS_2 (1<<14) +#define RAD_PROTECT_CONTENTS_2 (1<<14) /// should this object be allowed to be contaminated -#define RAD_NO_CONTAMINATE_2 (1<<15) +#define RAD_NO_CONTAMINATE_2 (1<<15) /// Prevents shuttles from deleting the item #define IMMUNE_TO_SHUTTLECRUSH_2 (1<<16) /// Prevents malf AI animate + overload ability @@ -80,6 +78,12 @@ /// This flag allows for wearing of a belt item, even if you're not wearing a jumpsuit #define ALLOW_BELT_NO_JUMPSUIT_2 (1<<20) +// /atom ricochet flags +/// If the thing can reflect light (lasers/energy) +#define RICOCHET_SHINY (1<<0) +/// If the thing can reflect matter (bullets/bomb shrapnel) +#define RICOCHET_HARD (1<<1) + //Reagent flags #define REAGENT_NOREACT 1 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 365e65123dd..56e4c70e202 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -4,6 +4,8 @@ var/level = 2 var/flags = NONE var/flags_2 = NONE + /// how the atom should handle ricochet behavior + var/flags_ricochet = NONE var/list/fingerprints var/list/fingerprintshidden var/fingerprintslast = null @@ -107,6 +109,11 @@ /// Contains the world.time of when we start dragging something with our mouse. Used to prevent weird situations where you fail to click on something var/drag_start = 0 + ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this + var/receive_ricochet_chance_mod = 1 + ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom + var/receive_ricochet_damage_coeff = 0.33 + /atom/New(loc, ...) SHOULD_CALL_PARENT(TRUE) if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() @@ -1107,8 +1114,20 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) /atom/proc/zap_act(power, zap_flags) return -/atom/proc/handle_ricochet(obj/item/projectile/P) - return +/atom/proc/handle_ricochet(obj/item/projectile/ricocheting_projectile) + 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/a_incidence_s = abs(incidence_s) + if(a_incidence_s > 90 && a_incidence_s < 270) + return FALSE + if((ricocheting_projectile.flag in list(BULLET, BOMB)) && ricocheting_projectile.ricochet_incidence_leeway) + if((a_incidence_s < 90 && a_incidence_s < 90 - ricocheting_projectile.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > ricocheting_projectile.ricochet_incidence_leeway)) + return FALSE + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + ricocheting_projectile.set_angle(new_angle_s) + return TRUE //This proc is called on the location of an atom when the atom is Destroy()'d /atom/proc/handle_atom_del(atom/A) diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 158e245fccc..2acb73839c1 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -7,6 +7,8 @@ layer = BELOW_OBJ_LAYER armor = list(melee = 25, bullet = 10, laser = 10, energy = 0, bomb = 0, rad = 0, fire = 50, acid = 70) atom_say_verb = "beeps" + flags_ricochet = RICOCHET_HARD + receive_ricochet_chance_mod = 0.3 var/stat = 0 /// How is this machine currently passively consuming power? diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index ccb88ca23a3..78cb8a28cfd 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -115,7 +115,7 @@ air = environment if(isnull(air)) return - + var/breath_percentage = BREATH_VOLUME / air.return_volume() return air.remove(air.total_moles() * breath_percentage) else @@ -303,6 +303,12 @@ else C.KnockDown(3 SECONDS) +/obj/handle_ricochet(obj/item/projectile/P) + . = ..() + if(. && receive_ricochet_damage_coeff) + // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet + take_damage(P.damage * receive_ricochet_damage_coeff, P.damage_type, P.flag, 0, REVERSE_DIR(P.dir), P.armour_penetration_flat, P.armour_penetration_percentage) + /obj/proc/return_obj_air() RETURN_TYPE(/datum/gas_mixture) if(isobj(loc)) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 587724cdf1e..84de5e7c8bd 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -3,6 +3,8 @@ pressure_resistance = 8 max_integrity = 300 face_while_pulling = TRUE + flags_ricochet = RICOCHET_HARD + receive_ricochet_chance_mod = 0.6 var/climbable /// Determines if a structure adds the TRAIT_TURF_COVERED to its turf. var/creates_cover = FALSE diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 0a509b5f4b1..d4d5181faea 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -8,6 +8,8 @@ anchored = TRUE flags = ON_BORDER flags_2 = RAD_PROTECT_CONTENTS_2 + flags_ricochet = RICOCHET_HARD + receive_ricochet_chance_mod = 0.5 can_be_unanchored = TRUE max_integrity = 25 resistance_flags = ACID_PROOF diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index ba6a3a1d15a..2306dc356c0 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -24,6 +24,7 @@ explosion_block = 1 flags_2 = RAD_PROTECT_CONTENTS_2 | RAD_NO_CONTAMINATE_2 + flags_ricochet = RICOCHET_HARD rad_insulation = RAD_MEDIUM_INSULATION thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT @@ -154,17 +155,6 @@ else update_icon() -/turf/simulated/wall/handle_ricochet(obj/item/projectile/P) //A huge pile of shitcode! - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.set_angle(new_angle_s) - return TRUE - /turf/simulated/wall/dismantle_wall(devastated = FALSE, explode = FALSE) if(devastated) devastate_wall() diff --git a/code/game/turfs/simulated/walls_mineral.dm b/code/game/turfs/simulated/walls_mineral.dm index fd8502e8ef1..c3a0a3ee97c 100644 --- a/code/game/turfs/simulated/walls_mineral.dm +++ b/code/game/turfs/simulated/walls_mineral.dm @@ -220,7 +220,7 @@ icon_state = "plastinum_wall-0" base_icon_state = "plastinum_wall" explosion_block = 3 - flags_2 = CHECK_RICOCHET_2 + flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD sheet_type = /obj/item/stack/sheet/mineral/titanium smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS smoothing_groups = list(SMOOTH_GROUP_TITANIUM_WALLS, SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE) diff --git a/code/modules/events/blob/blob_structures/strong_blob.dm b/code/modules/events/blob/blob_structures/strong_blob.dm index 86ae0a984f3..be45e257194 100644 --- a/code/modules/events/blob/blob_structures/strong_blob.dm +++ b/code/modules/events/blob/blob_structures/strong_blob.dm @@ -61,16 +61,4 @@ brute_resist = 0.5 explosion_block = 2 point_return = 9 - flags_2 = CHECK_RICOCHET_2 - -/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P) - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.set_angle(new_angle_s) - visible_message("[P] reflects off [src]!") - return TRUE + flags_ricochet = RICOCHET_SHINY diff --git a/code/modules/projectiles/projectile_base.dm b/code/modules/projectiles/projectile_base.dm index 805f1aa785f..140ee2683ac 100644 --- a/code/modules/projectiles/projectile_base.dm +++ b/code/modules/projectiles/projectile_base.dm @@ -66,9 +66,6 @@ var/immolate = 0 var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. var/impact_effect_type //what type of impact effect to show when hitting something - var/ricochets = 0 - var/ricochets_max = 2 - var/ricochet_chance = 30 var/log_override = FALSE //whether print to admin attack logs or just keep it in the diary @@ -114,6 +111,27 @@ var/impact_light_color_override var/hitscan_duration = 0.3 SECONDS + /// how many times we've ricochet'd so far (instance variable, not a stat) + var/ricochets = 0 + /// 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 + var/ricochet_decay_chance = 0.7 + /// 0-1 (or more, I guess) multiplier, the projectile's damage is modified by multiplying this after each ricochet + var/ricochet_decay_damage = 0.7 + /// On ricochet, if nonzero, we consider all mobs within this range of our projectile at the time of ricochet to home in on like Revolver Ocelot, as governed by ricochet_auto_aim_angle + var/ricochet_auto_aim_range = 0 + /// On ricochet, if ricochet_auto_aim_range is nonzero, we'll consider any mobs within this range of the normal angle of incidence to home in on, higher = more auto aim + var/ricochet_auto_aim_angle = 30 + /// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle + var/ricochet_incidence_leeway = 40 + /// Can our ricochet autoaim hit our firer? + var/ricochet_shoots_firer = TRUE + /obj/item/projectile/New() return ..() @@ -429,7 +447,21 @@ /obj/item/projectile/proc/on_ricochet(atom/A) - return + if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range) + return + + var/mob/living/unlucky_sob + var/best_angle = ricochet_auto_aim_angle + for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc)) + if(L.stat == DEAD || !isInSight(src, L) || (!ricochet_shoots_firer && L == firer)) + continue + var/our_angle = abs(closer_angle_difference(Angle, get_angle(src.loc, L.loc))) + if(our_angle < best_angle) + best_angle = our_angle + unlucky_sob = L + + if(unlucky_sob) + set_angle(get_angle(src, unlucky_sob.loc)) /obj/item/projectile/proc/check_ricochet() if(prob(ricochet_chance)) @@ -437,8 +469,12 @@ return FALSE /obj/item/projectile/proc/check_ricochet_flag(atom/A) - if(A.flags_2 & CHECK_RICOCHET_2) + if((flag in list(ENERGY, LASER)) && (A.flags_ricochet & RICOCHET_SHINY)) return TRUE + + if((flag in list(BOMB, BULLET)) && (A.flags_ricochet & RICOCHET_HARD)) + return TRUE + return FALSE /obj/item/projectile/set_angle(new_angle)