Frag grenades now explode into proper projectiles (#24581)

* holy fuck

* oops

* IPA

* no badmins

* wow

* yeah

* no funny
This commit is contained in:
Contrabang
2024-04-07 09:40:16 +00:00
committed by GitHub
parent 05e484e794
commit b75bfd0e52
7 changed files with 153 additions and 42 deletions
+7
View File
@@ -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)
+36
View File
@@ -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)
+38
View File
@@ -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
@@ -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, "<span class='warning'>Shrapnel bounces off your armor!</span>")
/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, "<span class='warning'>Shrapnel bounces off your armor!</span>")
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
@@ -82,7 +82,7 @@
if(!in_range(src, user))
if(icon == src)
. += "<span class='notice'>It's \a [bicon(icon)][src]! If you want any more information you'll need to get closer.</span>"
. += "<span class='notice'>It's [p_a()] [bicon(icon)] [name]! If you want any more information you'll need to get closer.</span>"
return
var/celsius_temperature = air_contents.temperature - T0C
@@ -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("<span class='danger'>[I] embeds itself in [src]'s [L.name]!</span>","<span class='userdanger'>[I] embeds itself in your [L.name]!</span>")
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("<span class='danger'>[I] embeds itself in [src]'s [L.name]!</span>","<span class='userdanger'>[I] embeds itself in your [L.name]!</span>")
return TRUE
/mob/living/carbon/human/proc/bloody_hands(mob/living/source, amount = 2)
if(gloves)
@@ -140,7 +140,8 @@
msg += "<b>[p_their(TRUE)] [ignore_limb_branding(E.limb_name)] has an open incision!</b>\n"
for(var/obj/item/I in E.embedded_objects)
msg += "<b>[p_they(TRUE)] [p_have()] \a [bicon(I)] [I] embedded in [p_their()] [E.name]!</b>\n"
// we cant just use \a here, as we want it to appear before the bicon
msg += "<b>[p_they(TRUE)] [p_have()] [I.p_a()] [bicon(I)] [I.name] embedded in [p_their()] [E.name]!</b>\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.