From b75bfd0e52508be7cfc9041a5d8cc8f1a5a4f7d3 Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sun, 7 Apr 2024 05:40:16 -0400 Subject: [PATCH] Frag grenades now explode into proper projectiles (#24581) * holy fuck * oops * IPA * no badmins * wow * yeah * no funny --- code/__HELPERS/pronouns.dm | 7 ++ code/__HELPERS/unsorted.dm | 36 ++++++++ code/game/objects/explosion.dm | 38 +++++++++ .../objects/items/weapons/grenades/frag.dm | 85 ++++++++++++------- .../game/objects/items/weapons/tanks/tanks.dm | 2 +- .../mob/living/carbon/human/human_defense.dm | 24 ++++-- .../mob/living/carbon/human/human_examine.dm | 3 +- 7 files changed, 153 insertions(+), 42 deletions(-) diff --git a/code/__HELPERS/pronouns.dm b/code/__HELPERS/pronouns.dm index 12095e171e7..55a26a9e1b9 100644 --- a/code/__HELPERS/pronouns.dm +++ b/code/__HELPERS/pronouns.dm @@ -45,6 +45,13 @@ if(.) . = "e[.]" +// Functionally the \a macro, for the cases where you put a bicon between "some [bicon] pop corn" +/datum/proc/p_a(temp_gender) + var/backslash_a = "\a [src]" + backslash_a = splittext_char(backslash_a, " ") + if(length(backslash_a) >= 2) // ["some", "pop", "corn"], but we dont want "\a ["Thing"]" which is just ["Thing"] + . = backslash_a[1] + //like clients, which do have gender. /client/p_they(capitalized, temp_gender) if(!temp_gender) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 33db8019b97..646d2ded722 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -579,6 +579,42 @@ Returns 1 if the chain up to the area contains the given typepath return starting_turf +// returns turf relative to A for a given clockwise angle at set range +// result is bounded to map size +/proc/get_angle_target_turf(atom/A, angle, range) + if(!istype(A)) + return null + var/x = A.x + var/y = A.y + + x += range * sin(angle) + y += range * cos(angle) + + //Restricts to map boundaries while keeping the final angle the same + var/dx = A.x - x + var/dy = A.y - y + var/ratio + if(dy == 0) //prevents divide-by-zero errors + ratio = INFINITY + else + ratio = dx / dy + + if(x < 1) + y += (1 - x) / ratio + x = 1 + else if(x > world.maxx) + y += (world.maxx - x) / ratio + x = world.maxx + + if(y < 1) + x += (1 - y) * ratio + y = 1 + else if(y > world.maxy) + x += (world.maxy - y) * ratio + y = world.maxy + + return locate(round(x, 1), round(y, 1), A.z) + // returns turf relative to A offset in dx and dy tiles // bound to map limits /proc/get_offset_target_turf(atom/A, dx, dy) diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 8fc02767591..f549f5f8cd3 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -276,6 +276,44 @@ T.color = null T.maptext = "" +/** + * Creates an explosion of shrapnel at a turf. + * - /turf/epicenter - where the explosion occurs + * - shrapnel_number - the amount of shrapnel to create + * - /obj/item/projectile/shrapnel_type - the type of shrapnel bullets to shoot + * - chance_to_hit_same_turf - the probability to hit someone on the same turf, doubled for someone lying down + */ +/proc/create_shrapnel(turf/epicenter, shrapnel_number = 10, obj/item/projectile/shrapnel_type = /obj/item/projectile/bullet/shrapnel, chance_to_hit_same_turf = 50) + epicenter = get_turf(epicenter) + if(!epicenter || !shrapnel_number || !shrapnel_type) + return + shrapnel_number = min(shrapnel_number, 200) // calm down badmins, no crashing the server + + var/angle_increment = 360 / shrapnel_number + var/mob/living/mob_standing_on_turf + var/mob/living/mob_lying_on_turf + + for(var/mob/living/M in epicenter) //find a mob at the epicenter. Non-prone mobs take priority + if(!IS_HORIZONTAL(M) && !mob_standing_on_turf) + mob_standing_on_turf = M + else if(!mob_lying_on_turf) + mob_lying_on_turf = M + + for(var/i in 1 to shrapnel_number) + var/obj/item/projectile/Shrapnel = new shrapnel_type(epicenter) + + // You can't just stand over a shrapnel explosion to avoid it + if(mob_standing_on_turf && prob(chance_to_hit_same_turf)) + Shrapnel.Bump(mob_standing_on_turf, TRUE) + continue + // If you dive on it, you're even more likely to get hit + if(mob_lying_on_turf && prob(2 * chance_to_hit_same_turf)) + Shrapnel.Bump(mob_lying_on_turf, TRUE) + continue + + var/angle = i * angle_increment + rand(-angle_increment / 2, angle_increment / 2) + Shrapnel.fire(angle) + #undef CREAK_DELAY #undef DEVASTATION_PROB #undef HEAVY_IMPACT_PROB diff --git a/code/game/objects/items/weapons/grenades/frag.dm b/code/game/objects/items/weapons/grenades/frag.dm index f264619c335..e93c923df9c 100644 --- a/code/game/objects/items/weapons/grenades/frag.dm +++ b/code/game/objects/items/weapons/grenades/frag.dm @@ -1,51 +1,74 @@ +#define DEFAULT_SHRAPNEL_RANGE 5 + /obj/item/grenade/frag name = "frag grenade" desc = "Fire in the hole." icon_state = "frag" item_state = "grenade" origin_tech = "materials=3;magnets=4" - var/range = 5 - var/max_shrapnel = 4 - var/embed_prob = 100 //reduced by armor - var/embedded_type = /obj/item/embedded/shrapnel + var/shrapnel_contained = 20 + var/embedded_type = /obj/item/projectile/bullet/shrapnel /obj/item/grenade/frag/prime() update_mob() - var/turf/epicenter = get_turf(src) - for(var/mob/living/carbon/human/H in epicenter) - if(IS_HORIZONTAL(H)) //grenade is jumped on but get real fucked up - embed_shrapnel(H, max_shrapnel) - range = 1 - explosion(loc, 0, 1, range, breach = FALSE) - for(var/turf/T in view(range, loc)) - for(var/mob/living/carbon/human/H in T) - var/shrapnel_amount = max_shrapnel - T.Distance(epicenter) - if(shrapnel_amount > 0) - embed_shrapnel(H, shrapnel_amount) + explosion(loc, 0, 1, DEFAULT_SHRAPNEL_RANGE, breach = FALSE) + create_shrapnel(loc, shrapnel_contained, shrapnel_type = embedded_type) qdel(src) -/obj/item/grenade/frag/proc/embed_shrapnel(mob/living/carbon/human/H, amount) - for(var/i = 0, i < amount, i++) - if(prob(embed_prob - ARMOUR_VALUE_TO_PERCENTAGE(H.getarmor(null, BOMB)))) - var/obj/item/embedded/S = new embedded_type(src) - H.hitby(S, skipcatch = 1) - S.throwforce = 1 - S.throw_speed = 1 - S.sharp = FALSE - else - to_chat(H, "Shrapnel bounces off your armor!") - -/obj/item/embedded/shrapnel +/** + * Shrapnel that flies through the air and hits you + */ +/obj/item/projectile/bullet/shrapnel name = "shrapnel" + icon_state = "magspear" + gender = PLURAL + range = DEFAULT_SHRAPNEL_RANGE + damage = 1 // 1 damage, to trigger stuff that reacts to damage. Rest of the damage is done through the physical shrapnel + var/embed_prob = 100 //reduced by armor + var/embedded_type = /obj/item/shrapnel + +/obj/item/projectile/bullet/shrapnel/on_hit(atom/target, blocked) + . = ..() + var/obj/item/new_possible_embed = new embedded_type(get_turf(src)) // drop it on the floor if we hit somethig non-living + if(!.) + return + if(!ishuman(target)) + return + + var/mob/living/carbon/human/H = target + if(!prob(embed_prob - ARMOUR_VALUE_TO_PERCENTAGE(H.getarmor(null, BOMB)))) + to_chat(H, "Shrapnel bounces off your armor!") + return + H.try_embed_object(new_possible_embed) + +/obj/item/projectile/bullet/shrapnel/on_range() + var/obj/item/we_missed = new embedded_type(get_turf(src)) // we missed, lets toss the shrapnel + var/range = gaussian(4, 2) + if(range > 0) + var/atom/i_wasnt_aiming_for_the_truck = get_angle_target_turf(get_turf(src), Angle, range) + we_missed.throw_at(i_wasnt_aiming_for_the_truck, 16, 3) + return ..() + +/** + * Shrapnel projectiles turn into this after trying to embed + */ +/obj/item/shrapnel + name = "shrapnel" + desc = "Metal shards at high velocity, a classic method of blowing your enemies up." icon = 'icons/obj/shards.dmi' - throwforce = 10 - throw_speed = EMBED_THROWSPEED_THRESHOLD + icon_state = "shrapnel1" + force = 8 // its a sharp piece of metal, but still not very effective + gender = PLURAL embed_chance = 100 embedded_fall_chance = 0 w_class = WEIGHT_CLASS_SMALL sharp = TRUE hitsound = 'sound/weapons/pierce.ogg' -/obj/item/embedded/shrapnel/New() - ..() +/obj/item/shrapnel/Initialize(mapload) + . = ..() icon_state = pick("shrapnel1", "shrapnel2", "shrapnel3") + pixel_x = rand(-8, 8) + pixel_y = rand(-8, 8) + +#undef DEFAULT_SHRAPNEL_RANGE diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm index 6e11a28b0a6..1e5cdf84f3a 100644 --- a/code/game/objects/items/weapons/tanks/tanks.dm +++ b/code/game/objects/items/weapons/tanks/tanks.dm @@ -82,7 +82,7 @@ if(!in_range(src, user)) if(icon == src) - . += "It's \a [bicon(icon)][src]! If you want any more information you'll need to get closer." + . += "It's [p_a()] [bicon(icon)] [name]! If you want any more information you'll need to get closer." return var/celsius_temperature = air_contents.temperature - T0C diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index e028d9c57cd..fe394afcaef 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -596,17 +596,23 @@ emp_act else if(I) if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedded_ignore_throwspeed_threshold) - if(can_embed(I)) - if(prob(I.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) - var/obj/item/organ/external/L = pick(bodyparts) - L.add_embedded_object(I) - I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! - L.receive_damage(I.w_class*I.embedded_impact_pain_multiplier) - visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item + if(try_embed_object(I)) + hitpush = FALSE + skipcatch = TRUE //can't catch the now embedded item return ..() +/mob/living/carbon/human/proc/try_embed_object(obj/item/I) + if(!can_embed(I)) + return FALSE + if(!prob(I.embed_chance) || HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) + return FALSE + var/obj/item/organ/external/L = pick(bodyparts) + L.add_embedded_object(I) + I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! + L.receive_damage(I.w_class * I.embedded_impact_pain_multiplier) + visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") + return TRUE + /mob/living/carbon/human/proc/bloody_hands(mob/living/source, amount = 2) if(gloves) diff --git a/code/modules/mob/living/carbon/human/human_examine.dm b/code/modules/mob/living/carbon/human/human_examine.dm index b8ecb090471..ff929ce611b 100644 --- a/code/modules/mob/living/carbon/human/human_examine.dm +++ b/code/modules/mob/living/carbon/human/human_examine.dm @@ -140,7 +140,8 @@ msg += "[p_their(TRUE)] [ignore_limb_branding(E.limb_name)] has an open incision!\n" for(var/obj/item/I in E.embedded_objects) - msg += "[p_they(TRUE)] [p_have()] \a [bicon(I)] [I] embedded in [p_their()] [E.name]!\n" + // we cant just use \a here, as we want it to appear before the bicon + msg += "[p_they(TRUE)] [p_have()] [I.p_a()] [bicon(I)] [I.name] embedded in [p_their()] [E.name]!\n" //Handles the text strings being added to the actual description. //If they have something that covers the limb, and it is not missing, put flavortext. If it is covered but bleeding, add other flavortext.