diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 72b56b178be..14bc65811fa 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -680,6 +680,8 @@ ///called in /obj/item/gun/process_fire (user, target, params, zone_override) #define COMSIG_MOB_FIRED_GUN "mob_fired_gun" +///called in /obj/item/gun/process_fire (user, target, params, zone_override) +#define COMSIG_GUN_FIRED "gun_fired" // /obj/item/grenade signals diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index 1cb081058cf..417caf28ca3 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -181,8 +181,8 @@ while(pixel_y < -16) pixel_y += 32 new_y-- - new_x = clamp(new_x, 0, world.maxx) - new_y = clamp(new_y, 0, world.maxy) + new_x = clamp(new_x, 1, world.maxx) + new_y = clamp(new_y, 1, world.maxy) return locate(new_x, new_y, starting.z) // Returns a list where [1] is all x values and [2] is all y values that overlap between the given pair of rectangles diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index 4e1258cc758..b50e82dcc98 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -139,19 +139,19 @@ * * O- Our parent, the thing making the shrapnel obviously (grenade or landmine) * * punishable_triggerer- For grenade lances or people who step on the landmines (if we shred the triggerer), we spawn extra shrapnel for them in addition to the normal spread */ -/datum/component/pellet_cloud/proc/create_blast_pellets(obj/O, mob/living/punishable_triggerer) - SIGNAL_HANDLER_DOES_SLEEP +/datum/component/pellet_cloud/proc/create_blast_pellets(obj/O, mob/living/triggerer) + SIGNAL_HANDLER var/atom/A = parent if(isgrenade(parent)) // handle_martyrs can reduce the radius and thus the number of pellets we produce if someone dives on top of a frag grenade - handle_martyrs(punishable_triggerer) // note that we can modify radius in this proc + INVOKE_ASYNC(src, .proc/handle_martyrs, triggerer) // note that we can modify radius in this proc else if(islandmine(parent)) var/obj/effect/mine/shrapnel/triggered_mine = parent - if(triggered_mine.shred_triggerer && istype(punishable_triggerer)) // free shrapnel for the idiot who stepped on it if we're a mine that shreds the triggerer + if(triggered_mine.shred_triggerer && istype(triggerer)) // free shrapnel for the idiot who stepped on it if we're a mine that shreds the triggerer pellet_delta += radius // so they don't count against the later total for(var/i in 1 to radius) - pew(punishable_triggerer, TRUE) + INVOKE_ASYNC(src, .proc/pew, triggerer, TRUE) if(radius < 1) return @@ -161,7 +161,7 @@ for(var/T in all_the_turfs_were_gonna_lacerate) var/turf/shootat_turf = T - pew(shootat_turf) + INVOKE_ASYNC(src, .proc/pew, shootat_turf) /** * handle_martyrs() is used for grenades that shoot shrapnel to check if anyone threw themselves/were thrown on top of the grenade, thus absorbing a good chunk of the shrapnel diff --git a/code/datums/elements/backblast.dm b/code/datums/elements/backblast.dm new file mode 100644 index 00000000000..22a3c434599 --- /dev/null +++ b/code/datums/elements/backblast.dm @@ -0,0 +1,74 @@ +/** + * When attached to a gun and the gun is successfully fired, this element creates a "backblast" of fire and pain, like you'd find in a rocket launcher or recoilless rifle + * + * The backblast is simulated by a number of fire plumes, or invisible incendiary rounds that will torch anything they come across for a short distance, as well as knocking + * back nearby items. + */ +/datum/element/backblast + element_flags = ELEMENT_BESPOKE + id_arg_index = 2 + + /// How many "pellets" of backblast we're shooting backwards, spread between the angle defined in angle_spread + var/plumes + /// Assuming we don't just have 1 plume, this is the total angle we'll cover with the plumes, split down the middle directly behind the angle we fired at + var/angle_spread + /// How far each plume of fire will fly, assuming it doesn't hit a mob + var/range + +/datum/element/backblast/Attach(datum/target, plumes = 4, angle_spread = 48, range = 6) + . = ..() + if(!isgun(target) || plumes < 1 || angle_spread < 1 || range < 1) + return ELEMENT_INCOMPATIBLE + + src.plumes = plumes + src.angle_spread = angle_spread + src.range = range + + if(plumes == 1) + RegisterSignal(target, COMSIG_GUN_FIRED, .proc/gun_fired_simple) + else + RegisterSignal(target, COMSIG_GUN_FIRED, .proc/gun_fired) + +/datum/element/backblast/Detach(datum/source, force) + if(source) + UnregisterSignal(source, COMSIG_GUN_FIRED) + return ..() + +/// For firing multiple plumes behind us, we evenly spread out our projectiles based on the [angle_spread][/datum/element/backblast/var/angle_spread] and [number of plumes][/datum/element/backblast/var/plumes] +/datum/element/backblast/proc/gun_fired(obj/item/gun/weapon, mob/living/user, atom/target, params, zone_override) + SIGNAL_HANDLER + + if(!weapon.chambered || HAS_TRAIT(user, TRAIT_PACIFISM)) + return + + var/backwards_angle = Get_Angle(target, user) + var/starting_angle = SIMPLIFY_DEGREES(backwards_angle-(angle_spread * 0.5)) + var/iter_offset = angle_spread / plumes // how much we increment the angle for each plume + + for(var/i in 1 to plumes) + var/this_angle = SIMPLIFY_DEGREES(starting_angle + ((i - 1) * iter_offset)) + var/turf/target_turf = get_turf_in_angle(this_angle, get_turf(user), 10) + INVOKE_ASYNC(src, .proc/pew, target_turf, weapon, user) + +/// If we're only firing one plume directly behind us, we don't need to bother with the loop or angles or anything +/datum/element/backblast/proc/gun_fired_simple(obj/item/gun/weapon, mob/living/user, atom/target, params, zone_override) + SIGNAL_HANDLER + + if(!weapon.chambered || HAS_TRAIT(user, TRAIT_PACIFISM)) + return + + var/backwards_angle = Get_Angle(target, user) + var/turf/target_turf = get_turf_in_angle(backwards_angle, get_turf(user), 10) + INVOKE_ASYNC(src, .proc/pew, target_turf, weapon, user) + +/// For firing an actual backblast pellet +/datum/element/backblast/proc/pew(turf/target_turf, obj/item/gun/weapon, mob/living/user) + //Shooting Code: + var/obj/projectile/bullet/incendiary/backblast/P = new (get_turf(user)) + P.original = target_turf + P.range = range + 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.fire() diff --git a/code/modules/projectiles/ammunition/caseless/rocket.dm b/code/modules/projectiles/ammunition/caseless/rocket.dm index 8a6d28120df..8fbca0f58c2 100644 --- a/code/modules/projectiles/ammunition/caseless/rocket.dm +++ b/code/modules/projectiles/ammunition/caseless/rocket.dm @@ -12,6 +12,11 @@ icon_state = "84mm-hedp" projectile_type = /obj/projectile/bullet/a84mm +/obj/item/ammo_casing/caseless/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/a84mm_weak + /obj/item/ammo_casing/caseless/a75 desc = "A .75 bullet casing." caliber = "75" diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index e7026a529e0..84c9dbb520a 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -322,6 +322,8 @@ if(user) SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override) + SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override) + add_fingerprint(user) if(semicd) diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm index 2da35374522..5777dd3fc23 100644 --- a/code/modules/projectiles/guns/ballistic/launchers.dm +++ b/code/modules/projectiles/guns/ballistic/launchers.dm @@ -44,7 +44,8 @@ /obj/item/gun/ballistic/rocketlauncher name = "\improper PML-9" - desc = "A reusable rocket propelled grenade launcher. The words \"NT this way\" and an arrow have been written near the barrel." + desc = "A reusable rocket propelled grenade launcher. The words \"NT this way\" and an arrow have been written near the barrel. \ + A sticker near the cheek rest reads, \"ENSURE AREA BEHIND IS CLEAR BEFORE FIRING\"" icon_state = "rocketlauncher" inhand_icon_state = "rocketlauncher" mag_type = /obj/item/ammo_box/magazine/internal/rocketlauncher @@ -61,10 +62,22 @@ cartridge_wording = "rocket" empty_indicator = TRUE tac_reloads = FALSE + /// Do we shit flames behind us when we fire? + var/backblast = TRUE + +/obj/item/gun/ballistic/rocketlauncher/Initialize() + . = ..() + if(backblast) + AddElement(/datum/element/backblast) /obj/item/gun/ballistic/rocketlauncher/unrestricted pin = /obj/item/firing_pin +/obj/item/gun/ballistic/rocketlauncher/nobackblast + name = "flameless PML-11" + desc = "A reusable rocket propelled grenade launcher. This one has been fitted with a special coolant loop to avoid embarassing teamkill 'accidents' from backblast." + backblast = FALSE + /obj/item/gun/ballistic/rocketlauncher/afterattack() . = ..() magazine.get_round(FALSE) //Hack to clear the mag after it's fired diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index 59baefa6ba7..e0a9ccbce1e 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -15,3 +15,58 @@ if(location) new /obj/effect/hotspot(location) location.hotspot_expose(700, 50, 1) + +/// Used in [the backblast element][/datum/element/backblast] +/obj/projectile/bullet/incendiary/backblast + damage = 15 + range = 10 // actually overwritten in the backblast element + alpha = 0 + pass_flags = PASSTABLE | PASSMOB + sharpness = SHARP_NONE + shrapnel_type = null + embedding = null + ricochet_chance = 10000 + ricochets_max = 4 + ricochet_incidence_leeway = 0 + suppressed = SUPPRESSED_VERY + damage_type = BURN + flag = BOMB + speed = 1.2 + wound_bonus = 30 + bare_wound_bonus = 30 + wound_falloff_tile = -4 + fire_stacks = 3 + + /// Lazy attempt at knockback, any items this plume hits will be knocked back this far. Decrements with each tile passed. + var/knockback_range = 7 + /// A lazylist of all the items we've already knocked back, so we don't do it again + var/list/launched_items + +/// we only try to knock back the first 6 items per tile +#define BACKBLAST_MAX_ITEM_KNOCKBACK 6 + +/obj/projectile/bullet/incendiary/backblast/Move() + . = ..() + if(knockback_range <= 0) + return + knockback_range-- + var/turf/current_turf = get_turf(src) + var/turf/throw_at_turf = get_turf_in_angle(Angle, current_turf, 7) + var/thrown_items = 0 + + for(var/iter in current_turf.contents) + if(thrown_items > BACKBLAST_MAX_ITEM_KNOCKBACK) + break + if(isitem(iter)) + var/obj/item/iter_item = iter + if(iter_item.anchored || LAZYFIND(launched_items, iter_item) || iter_item.throwing) + continue + thrown_items++ + iter_item.throw_at(throw_at_turf, knockback_range, knockback_range) + LAZYADD(launched_items, iter_item) + else if(isliving(iter)) + var/mob/living/incineratee = iter + incineratee.take_bodypart_damage(0, damage, wound_bonus=wound_bonus, bare_wound_bonus=bare_wound_bonus) + incineratee.adjust_fire_stacks(fire_stacks) + +#undef BACKBLAST_MAX_ITEM_KNOCKBACK diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm index 3a925ea556e..472da1aeb71 100644 --- a/code/modules/projectiles/projectile/special/rocket.dm +++ b/code/modules/projectiles/projectile/special/rocket.dm @@ -10,6 +10,7 @@ explosion(target, -1, 0, 2) return BULLET_ACT_HIT +/// PM9 HEDP rocket /obj/projectile/bullet/a84mm name ="\improper HEDP rocket" desc = "USE A WEEL GUN" @@ -33,11 +34,12 @@ S.take_overall_damage(anti_armour_damage*0.75, anti_armour_damage*0.25) return BULLET_ACT_HIT +/// PM9 standard rocket /obj/projectile/bullet/a84mm_he name ="\improper HE missile" desc = "Boom." icon_state = "missile" - damage = 30 + damage = 50 ricochets_max = 0 //it's a MISSILE embedding = null shrapnel_type = null @@ -45,11 +47,30 @@ /obj/projectile/bullet/a84mm_he/on_hit(atom/target, blocked=0) ..() if(!isliving(target)) //if the target isn't alive, so is a wall or something - explosion(target, 0, 1, 2, 4) + explosion(target, 0, 1, 2, 4, flame_range = 3) else - explosion(target, 0, 0, 2, 4) + explosion(target, 0, 0, 2, 4, flame_range = 3) return BULLET_ACT_HIT +/// PM9 weak rocket +/obj/projectile/bullet/a84mm_weak + name ="low-yield HE missile" + desc = "Boom, but less so." + icon_state = "missile" + damage = 30 + ricochets_max = 0 //it's a MISSILE + embedding = null + shrapnel_type = null + +/obj/projectile/bullet/a84mm_weak/on_hit(atom/target, blocked=0) + ..() + if(!isliving(target)) //if the target isn't alive, so is a wall or something + explosion(target, 0, 1, 2, 4, flame_range = 3) + else + explosion(target, 0, 0, 2, 4, flame_range = 3) + return BULLET_ACT_HIT + +/// Mech BRM-6 missile /obj/projectile/bullet/a84mm_br name ="\improper HE missile" desc = "Boom." diff --git a/tgstation.dme b/tgstation.dme index 5bfca3a00f0..ddb272dcec9 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -589,6 +589,7 @@ #include "code\datums\elements\_element.dm" #include "code\datums\elements\art.dm" #include "code\datums\elements\atmos_sensitive.dm" +#include "code\datums\elements\backblast.dm" #include "code\datums\elements\bed_tucking.dm" #include "code\datums\elements\bsa_blocker.dm" #include "code\datums\elements\cleaning.dm"