From 93cb4cddc476cb86eab7407cf339d342f7c3b831 Mon Sep 17 00:00:00 2001
From: Ryll Ryll <3589655+Ryll-Ryll@users.noreply.github.com>
Date: Wed, 26 Aug 2020 20:22:59 -0400
Subject: [PATCH] Execution/gunpoint shots work properly with pellet clouds,
buffs piercing wounds a bit (#52995)
Made a few changes to gunpoints and some related mechanics while tidying up and adding docs. Here's a quick list
-Execution (point blank help intent shot aimed at the mouth) and gunpoint shots now apply their bonuses to every pellet fired rather than only the first, generally making them gushier. They also buff wound power as well as damage
-You can no longer punch yourself while holding someone up to trigger the charged shot
-You can no longer purposely fail executions and gunpoints with pax or whatever to endlessly spike the damage in a loaded round
-Attacks with extremely high wounding power can now outright dismember limbs regardless of mangled status. The threshold is high enough that it mostly applies for admin edited weapons or execution shots with shotguns (or people with the frail quirk!)
-Piercing wounds make further wounds a bit easier to apply to give them a bit more power
-Hellguns now cost 2250 credits instead of 2000 to make them a bit harder for random crew to get
-Adds special bouncy L6 rounds for admins to use to bounce off anything and everything en-masse
Also as a minor note
Projectiles with no trajectory (meaning they were likely spawned in manually) now qdel themselves on bumping something, rather than runtiming helplessly
---
code/__DEFINES/wounds.dm | 2 +
code/datums/components/gunpoint.dm | 65 ++++++++++++++-----
code/datums/components/pellet_cloud.dm | 8 +++
code/datums/wounds/loss.dm | 33 ++++++----
code/datums/wounds/pierce.dm | 6 +-
code/game/atoms.dm | 4 +-
code/game/machinery/_machinery.dm | 2 +-
code/game/machinery/doors/door.dm | 2 +-
code/game/objects/items/sharpener.dm | 3 +-
code/game/objects/objs.dm | 4 +-
code/game/objects/structures.dm | 2 +-
code/game/objects/structures/window.dm | 6 +-
code/modules/cargo/goodies.dm | 2 +-
.../projectiles/ammunition/ballistic/lmg.dm | 5 ++
.../boxes_magazines/_box_magazine.dm | 23 ++++++-
.../boxes_magazines/external/lmg.dm | 8 +++
.../boxes_magazines/internal/_cylinder.dm | 12 ++++
code/modules/projectiles/gun.dm | 8 ++-
code/modules/projectiles/projectile.dm | 7 +-
.../projectiles/projectile/bullets/lmg.dm | 11 +++-
code/modules/surgery/bodyparts/_bodyparts.dm | 5 ++
21 files changed, 168 insertions(+), 50 deletions(-)
diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm
index 763bdb4405a..5d060da2443 100644
--- a/code/__DEFINES/wounds.dm
+++ b/code/__DEFINES/wounds.dm
@@ -8,6 +8,8 @@
#define WOUND_MINIMUM_DAMAGE 5
/// an attack must do this much damage after armor in order to be eliigible to dismember a suitably mushed bodypart
#define DISMEMBER_MINIMUM_DAMAGE 10
+/// If an attack rolls this high with their wound (including mods), we try to outright dismember the limb. Note 250 is high enough that with a perfect max roll of 145 (see max cons'd damage), you'd need +100 in mods to do this
+#define WOUND_DISMEMBER_OUTRIGHT_THRESH 250
/// set wound_bonus on an item or attack to this to disable checking wounding for the attack
#define CANT_WOUND -100
diff --git a/code/datums/components/gunpoint.dm b/code/datums/components/gunpoint.dm
index 512cc202870..4c55d00c734 100644
--- a/code/datums/components/gunpoint.dm
+++ b/code/datums/components/gunpoint.dm
@@ -1,8 +1,16 @@
+/// How many tiles around the target the shooter can roam without losing their shot
#define GUNPOINT_SHOOTER_STRAY_RANGE 2
-#define GUNPOINT_DELAY_STAGE_2 25
-#define GUNPOINT_DELAY_STAGE_3 75 // cumulative with past stages, so 100 deciseconds
+/// How long it takes from the gunpoint is initiated to reach stage 2
+#define GUNPOINT_DELAY_STAGE_2 2.5 SECONDS
+/// How long it takes from stage 2 starting to move up to stage 3
+#define GUNPOINT_DELAY_STAGE_3 7.5 SECONDS
+/// If the projectile doesn't have a wound_bonus of CANT_WOUND, we add (this * the stage mult) to their wound_bonus and bare_wound_bonus upon triggering
+#define GUNPOINT_BASE_WOUND_BONUS 5
+/// How much the damage and wound bonus mod is multiplied when you're on stage 1
#define GUNPOINT_MULT_STAGE_1 1
+/// As above, for stage 2
#define GUNPOINT_MULT_STAGE_2 2
+/// As above, for stage 3
#define GUNPOINT_MULT_STAGE_3 2.5
@@ -64,27 +72,30 @@
UnregisterSignal(parent, COMSIG_MOB_ATTACK_HAND)
UnregisterSignal(parent, list(COMSIG_LIVING_START_PULL, COMSIG_MOVABLE_BUMP))
+///If the shooter bumps the target, cancel the holdup to avoid cheesing and forcing the charged shot
/datum/component/gunpoint/proc/check_bump(atom/B, atom/A)
SIGNAL_HANDLER
- var/mob/living/T = A
- if(T && T == target)
- var/mob/living/shooter = parent
- shooter.visible_message("[shooter] bumps into [target] and fumbles [shooter.p_their()] aim!", \
- "You bump into [target] and fumble your aim!", target)
- to_chat(target, "[shooter] bumps into you and fumbles [shooter.p_their()] aim!")
- qdel(src)
+ if(A != target)
+ return
+ var/mob/living/shooter = parent
+ shooter.visible_message("[shooter] bumps into [target] and fumbles [shooter.p_their()] aim!", \
+ "You bump into [target] and fumble your aim!", target)
+ to_chat(target, "[shooter] bumps into you and fumbles [shooter.p_their()] aim!")
+ qdel(src)
+///If the shooter shoves or grabs the target, cancel the holdup to avoid cheesing and forcing the charged shot
/datum/component/gunpoint/proc/check_shove(mob/living/carbon/shooter, mob/shooter_again, mob/living/T)
SIGNAL_HANDLER
- if(T == target && (shooter.a_intent == INTENT_DISARM || shooter.a_intent == INTENT_GRAB))
- shooter.visible_message("[shooter] bumps into [target] and fumbles [shooter.p_their()] aim!", \
- "You bump into [target] and fumble your aim!", target)
- to_chat(target, "[shooter] bumps into you and fumbles [shooter.p_their()] aim!")
- qdel(src)
+ if(T != target || shooter.a_intent == INTENT_DISARM || shooter.a_intent == INTENT_GRAB)
+ return
+ shooter.visible_message("[shooter] bumps into [target] and fumbles [shooter.p_their()] aim!", \
+ "You bump into [target] and fumble your aim!", target)
+ to_chat(target, "[shooter] bumps into you and fumbles [shooter.p_their()] aim!")
+ qdel(src)
-// if you're gonna try to break away from a holdup, better to do it right away
+///Update the damage multiplier for whatever stage we're entering into
/datum/component/gunpoint/proc/update_stage(new_stage)
stage = new_stage
if(stage == 2)
@@ -97,22 +108,26 @@
to_chat(target, "[parent] has fully steadied [weapon] on you!")
damage_mult = GUNPOINT_MULT_STAGE_3
+///Cancel the holdup if the shooter moves out of sight or out of range of the target
/datum/component/gunpoint/proc/check_deescalate()
SIGNAL_HANDLER
if(!can_see(parent, target, GUNPOINT_SHOOTER_STRAY_RANGE - 1))
cancel()
+///Bang bang, we're firing a charged shot off
/datum/component/gunpoint/proc/trigger_reaction()
SIGNAL_HANDLER_DOES_SLEEP
+ var/mob/living/shooter = parent
+ shooter.remove_status_effect(STATUS_EFFECT_HOLDUP) // try doing these before the trigger gets pulled since the target (or shooter even) may not exist after pulling the trigger, dig?
+ target.remove_status_effect(STATUS_EFFECT_HELDUP)
SEND_SIGNAL(target, COMSIG_CLEAR_MOOD_EVENT, "gunpoint")
+
if(point_of_no_return)
return
point_of_no_return = TRUE
- var/mob/living/shooter = parent
-
if(!weapon.can_shoot() || !weapon.can_trigger_gun(shooter) || (weapon.weapon_weight == WEAPON_HEAVY && shooter.get_inactive_held_item()))
shooter.visible_message("[shooter] fumbles [weapon]!", \
"You fumble [weapon] and fail to fire at [target]!", target)
@@ -122,13 +137,23 @@
if(weapon.chambered && weapon.chambered.BB)
weapon.chambered.BB.damage *= damage_mult
+ if(weapon.chambered.BB.wound_bonus != CANT_WOUND)
+ weapon.chambered.BB.wound_bonus += damage_mult * GUNPOINT_BASE_WOUND_BONUS
+ weapon.chambered.BB.bare_wound_bonus += damage_mult * GUNPOINT_BASE_WOUND_BONUS
if(weapon.check_botched(shooter))
return
- weapon.process_fire(target, shooter)
+ var/fired = weapon.process_fire(target, shooter)
+ if(!fired && weapon.chambered?.BB)
+ weapon.chambered.BB.damage /= damage_mult
+ if(weapon.chambered.BB.wound_bonus != CANT_WOUND)
+ weapon.chambered.BB.wound_bonus -= damage_mult * GUNPOINT_BASE_WOUND_BONUS
+ weapon.chambered.BB.bare_wound_bonus -= damage_mult * GUNPOINT_BASE_WOUND_BONUS
+
qdel(src)
+///Shooter canceled their shot, either by dropping/equipping their weapon, leaving sight/range, or clicking on the alert
/datum/component/gunpoint/proc/cancel()
SIGNAL_HANDLER
@@ -139,10 +164,13 @@
SEND_SIGNAL(target, COMSIG_CLEAR_MOOD_EVENT, "gunpoint")
qdel(src)
+///If the shooter is hit by an attack, they have a 50% chance to flinch and fire. If it hit the arm holding the trigger, it's an 80% chance to fire instead
/datum/component/gunpoint/proc/flinch(attacker, damage, damagetype, def_zone)
SIGNAL_HANDLER_DOES_SLEEP
var/mob/living/shooter = parent
+ if(attacker == shooter)
+ return // somehow this wasn't checked for months but no one tried punching themselves to initiate the shot, amazing
var/flinch_chance = 50
var/gun_hand = LEFT_HANDS
@@ -161,6 +189,7 @@
#undef GUNPOINT_SHOOTER_STRAY_RANGE
#undef GUNPOINT_DELAY_STAGE_2
#undef GUNPOINT_DELAY_STAGE_3
+#undef GUNPOINT_BASE_WOUND_BONUS
#undef GUNPOINT_MULT_STAGE_1
#undef GUNPOINT_MULT_STAGE_2
#undef GUNPOINT_MULT_STAGE_3
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 254aa94f86c..1c6e2fcf9a6 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -105,6 +105,11 @@
if(!zone_override)
zone_override = shooter.zone_selected
+ // things like mouth executions and gunpoints can multiply the damage and wounds of projectiles, so this makes sure those effects are applied to each pellet instead of just one
+ var/original_damage = shell.BB.damage
+ var/original_wb = shell.BB.wound_bonus
+ var/original_bwb = shell.BB.bare_wound_bonus
+
for(var/i in 1 to num_pellets)
shell.ready_proj(target, user, SUPPRESSED_VERY, zone_override, fired_from)
if(distro)
@@ -115,6 +120,9 @@
RegisterSignal(shell.BB, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
RegisterSignal(shell.BB, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
+ shell.BB.damage = original_damage
+ shell.BB.wound_bonus = original_wb
+ shell.BB.bare_wound_bonus = original_bwb
pellets += shell.BB
if(!shell.throw_proj(target, targloc, shooter, params, spread))
return
diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm
index bc7e18a1831..1fd318cdd8b 100644
--- a/code/datums/wounds/loss.dm
+++ b/code/datums/wounds/loss.dm
@@ -5,28 +5,39 @@
sound_effect = 'sound/effects/dismember.ogg'
severity = WOUND_SEVERITY_LOSS
- threshold_minimum = 180
+ threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it
status_effect_type = null
scar_keyword = "dismember"
wound_flags = null
/// Our special proc for our special dismembering, the wounding type only matters for what text we have
-/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH)
+/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH, outright = FALSE)
if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in viable_zones) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember())
qdel(src)
return
already_scarred = TRUE // so we don't scar a limb we don't have. If I add different levels of amputation desc, do it here
- switch(wounding_type)
- if(WOUND_BLUNT)
- occur_text = "is shattered through the last bone holding it together, severing it completely!"
- if(WOUND_SLASH)
- occur_text = "is slashed through the last tissue holding it together, severing it completely!"
- if(WOUND_PIERCE)
- occur_text = "is pierced through the last tissue holding it together, severing it completely!"
- if(WOUND_BURN)
- occur_text = "is completely incinerated, falling to dust!"
+ if(outright)
+ switch(wounding_type)
+ if(WOUND_BLUNT)
+ occur_text = "is outright smashed to a gross pulp, severing it completely!"
+ if(WOUND_SLASH)
+ occur_text = "is outright slashed off, severing it completely!"
+ if(WOUND_PIERCE)
+ occur_text = "is outright blasted apart, severing it completely!"
+ if(WOUND_BURN)
+ occur_text = "is outright incinerated, falling to dust!"
+ else
+ switch(wounding_type)
+ if(WOUND_BLUNT)
+ occur_text = "is shattered through the last bone holding it together, severing it completely!"
+ if(WOUND_SLASH)
+ occur_text = "is slashed through the last tissue holding it together, severing it completely!"
+ if(WOUND_PIERCE)
+ occur_text = "is pierced through the last tissue holding it together, severing it completely!"
+ if(WOUND_BURN)
+ occur_text = "is completely incinerated, falling to dust!"
victim = dismembered_part.owner
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
index c307f9582de..baf7125aeb4 100644
--- a/code/datums/wounds/pierce.dm
+++ b/code/datums/wounds/pierce.dm
@@ -131,7 +131,7 @@
internal_bleeding_chance = 30
internal_bleeding_coefficient = 1.25
threshold_minimum = 30
- threshold_penalty = 15
+ threshold_penalty = 20
status_effect_type = /datum/status_effect/wound/pierce/moderate
scar_keyword = "piercemoderate"
@@ -148,7 +148,7 @@
internal_bleeding_chance = 60
internal_bleeding_coefficient = 1.5
threshold_minimum = 50
- threshold_penalty = 25
+ threshold_penalty = 35
status_effect_type = /datum/status_effect/wound/pierce/severe
scar_keyword = "piercesevere"
@@ -165,7 +165,7 @@
internal_bleeding_chance = 80
internal_bleeding_coefficient = 1.75
threshold_minimum = 100
- threshold_penalty = 40
+ threshold_penalty = 50
status_effect_type = /datum/status_effect/wound/pierce/critical
scar_keyword = "piercecritical"
wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 7e295e075ec..6fdfbaeac04 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -19,9 +19,9 @@
var/flags_ricochet = NONE
///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this
- var/ricochet_chance_mod = 1
+ var/receive_ricochet_chance_mod = 1
///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom
- var/ricochet_damage_mod = 0.33
+ var/receive_ricochet_damage_coeff = 0.33
///Reagents holder
var/datum/reagents/reagents = null
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index c9aafb38716..7bc293bf446 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -90,7 +90,7 @@ Class Procs:
max_integrity = 200
layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it.
flags_ricochet = RICOCHET_HARD
- ricochet_chance_mod = 0.3
+ receive_ricochet_chance_mod = 0.3
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 08aa0696e3f..b88aa3438e9 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -13,7 +13,7 @@
armor = list(MELEE = 30, BULLET = 30, LASER = 20, ENERGY = 20, BOMB = 10, BIO = 100, RAD = 100, FIRE = 80, ACID = 70)
CanAtmosPass = ATMOS_PASS_DENSITY
flags_1 = PREVENT_CLICK_UNDER_1
- ricochet_chance_mod = 0.8
+ receive_ricochet_chance_mod = 0.8
damage_deflection = 10
interaction_flags_atom = INTERACT_ATOM_UI_INTERACT
diff --git a/code/game/objects/items/sharpener.dm b/code/game/objects/items/sharpener.dm
index a2a3e6a0305..eb3551bf643 100644
--- a/code/game/objects/items/sharpener.dm
+++ b/code/game/objects/items/sharpener.dm
@@ -37,9 +37,10 @@
return
if(!(signal_out & COMPONENT_BLOCK_SHARPEN_APPLIED))
I.force = clamp(I.force + increment, 0, max)
+ I.wound_bonus = I.wound_bonus + increment
user.visible_message("[user] sharpens [I] with [src]!", "You sharpen [I], making it much more deadly than before.")
playsound(src, 'sound/items/unsheath.ogg', 25, TRUE)
- I.sharpness = SHARP_POINTY
+ I.sharpness = SHARP_EDGED
I.throwforce = clamp(I.throwforce + increment, 0, max)
I.name = "[prefix] [I.name]"
name = "worn out [name]"
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 4d1126983e8..4104a1424ba 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -350,8 +350,8 @@
/obj/handle_ricochet(obj/projectile/P)
. = ..()
- if(. && ricochet_damage_mod)
- take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet
+ if(. && receive_ricochet_damage_coeff)
+ take_damage(P.damage * receive_ricochet_damage_coeff, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet
/obj/update_overlays()
. = ..()
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index cb2bfc273ba..8f83559622c 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -5,7 +5,7 @@
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
layer = BELOW_OBJ_LAYER
flags_ricochet = RICOCHET_HARD
- ricochet_chance_mod = 0.5
+ receive_ricochet_chance_mod = 0.6
var/climb_time = 20
var/climb_stun = 20
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 9d5b3b76a52..092dcf2cbee 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -27,7 +27,7 @@
var/breaksound = "shatter"
var/hitsound = 'sound/effects/Glasshit.ogg'
flags_ricochet = RICOCHET_HARD
- ricochet_chance_mod = 0.4
+ receive_ricochet_chance_mod = 0.5
/obj/structure/window/examine(mob/user)
@@ -371,7 +371,7 @@
state = RWINDOW_SECURE
glass_type = /obj/item/stack/sheet/rglass
rad_insulation = RAD_HEAVY_INSULATION
- ricochet_chance_mod = 0.8
+ receive_ricochet_chance_mod = 1.1
//this is shitcode but all of construction is shitcode and needs a refactor, it works for now
//If you find this like 4 years later and construction still hasn't been refactored, I'm so sorry for this
@@ -683,7 +683,7 @@
explosion_block = 3
glass_type = /obj/item/stack/sheet/titaniumglass
glass_amount = 2
- ricochet_chance_mod = 0.9
+ receive_ricochet_chance_mod = 1.2
/obj/structure/window/shuttle/narsie_act()
add_atom_colour("#3C3434", FIXED_COLOUR_PRIORITY)
diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm
index c071cbb4753..086de036429 100644
--- a/code/modules/cargo/goodies.dm
+++ b/code/modules/cargo/goodies.dm
@@ -49,7 +49,7 @@
/datum/supply_pack/goody/hell_single
name = "Hellgun Single-Pack"
desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids."
- cost = 2000
+ cost = 2250
contains = list(/obj/item/gun/energy/laser/hellgun)
/datum/supply_pack/goody/wt550_single
diff --git a/code/modules/projectiles/ammunition/ballistic/lmg.dm b/code/modules/projectiles/ammunition/ballistic/lmg.dm
index 724c1f6d2b0..b727e5dd727 100644
--- a/code/modules/projectiles/ammunition/ballistic/lmg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm
@@ -26,3 +26,8 @@
name = "7.12x82mm match bullet casing"
desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this."
projectile_type = /obj/projectile/bullet/mm712x82_match
+
+/obj/item/ammo_casing/mm712x82/bouncy
+ name = "7.12x82mm rubber bullet casing"
+ desc = "A 7.12x82mm rubber bullet casing manufactured to unfailingly disastrous standards, you could piss off a lot of people spraying this down a hallway."
+ projectile_type = /obj/projectile/bullet/mm712x82_bouncy
diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
index 643b9af9c91..86431ec909d 100644
--- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm
+++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
@@ -43,8 +43,27 @@
material_amount /= max_ammo
LAZYSET(bullet_cost, material, material_amount)
if(!start_empty)
- for(var/i = 1, i <= max_ammo, i++)
- stored_ammo += new ammo_type(src)
+ top_off(starting=TRUE)
+ update_icon()
+
+/**
+ * top_off is used to refill the magazine to max, in case you want to increase the size of a magazine with VV then refill it at once
+ *
+ * Arguments:
+ * * load_type - if you want to specify a specific ammo casing type to load, enter the path here, otherwise it'll use the basic [/obj/item/ammo_box/var/ammo_type]. Must be a compatible round
+ * * starting - Relevant for revolver cylinders, if FALSE then we mind the nulls that represent the empty cylinders (since those nulls don't exist yet if we haven't initialized when this is TRUE)
+ */
+/obj/item/ammo_box/proc/top_off(load_type, starting=FALSE)
+ if(!load_type) //this check comes first so not defining an argument means we just go with default ammo
+ load_type = ammo_type
+
+ var/obj/item/ammo_casing/round_check = load_type
+ if(!starting && (caliber && initial(round_check.caliber) != caliber) || (!caliber && load_type != ammo_type))
+ stack_trace("Tried loading unsupported ammocasing type [load_type] into ammo box [type].")
+ return
+
+ for(var/i = max(1, stored_ammo.len), i <= max_ammo, i++)
+ stored_ammo += new round_check(src)
update_icon()
///gets a round from the magazine, if keep is TRUE the round will stay in the gun
diff --git a/code/modules/projectiles/boxes_magazines/external/lmg.dm b/code/modules/projectiles/boxes_magazines/external/lmg.dm
index f1e3a5740a6..793c4cf14c9 100644
--- a/code/modules/projectiles/boxes_magazines/external/lmg.dm
+++ b/code/modules/projectiles/boxes_magazines/external/lmg.dm
@@ -21,6 +21,14 @@
name = "box magazine (Match 7.12x82mm)"
ammo_type = /obj/item/ammo_casing/mm712x82/match
+/obj/item/ammo_box/magazine/mm712x82/bouncy
+ name = "box magazine (Rubber 7.12x82mm)"
+ ammo_type = /obj/item/ammo_casing/mm712x82/bouncy
+
+/obj/item/ammo_box/magazine/mm712x82/bouncy/hicap
+ name = "hi-cap box magazine (Rubber 7.12x82mm)"
+ max_ammo = 150
+
/obj/item/ammo_box/magazine/mm712x82/update_icon()
..()
icon_state = "a762-[round(ammo_count(),10)]"
diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
index 9af0b0e0877..3ce4190109c 100644
--- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
@@ -47,3 +47,15 @@
return TRUE
return FALSE
+
+/obj/item/ammo_box/magazine/internal/cylinder/top_off(load_type, starting=FALSE)
+ if(starting) // nulls don't exist when we're starting off
+ return ..()
+
+ if(!load_type)
+ load_type = ammo_type
+
+ for(var/i = 1, i <= max_ammo, i++)
+ if(!give_round(new load_type(src)))
+ break
+ update_icon()
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 3f6ae6059c3..8641a7c305d 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -620,8 +620,14 @@
if(chambered && chambered.BB)
chambered.BB.damage *= 5
+ if(chambered.BB.wound_bonus != CANT_WOUND)
+ chambered.BB.wound_bonus += 5 // much more dramatic on multiple pellet'd projectiles really
- process_fire(target, user, TRUE, params, BODY_ZONE_HEAD)
+ var/fired = process_fire(target, user, TRUE, params, BODY_ZONE_HEAD)
+ if(!fired && chambered?.BB)
+ chambered.BB.damage /= 5
+ if(chambered.BB.wound_bonus != CANT_WOUND)
+ chambered.BB.wound_bonus -= 5
/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins
if(pin)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 25e13c73db4..1d1c1ab9bb6 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -48,7 +48,7 @@
var/ricochets = 0
/// how many times we can ricochet max
var/ricochets_max = 0
- /// 0-100, the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
+ /// 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
@@ -290,6 +290,9 @@
beam_segments[beam_index] = null
/obj/projectile/Bump(atom/A)
+ if(!trajectory)
+ qdel(src)
+ return
var/datum/point/pcache = trajectory.copy_to()
var/turf/T = get_turf(A)
if(ricochets < ricochets_max && check_ricochet_flag(A) && check_ricochet(A))
@@ -377,7 +380,7 @@
//Returns null if nothing at all was found.
/obj/projectile/proc/check_ricochet(atom/A)
- var/chance = ricochet_chance * A.ricochet_chance_mod
+ var/chance = ricochet_chance * A.receive_ricochet_chance_mod
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
chance += NICE_SHOT_RICOCHET_BONUS
if(prob(chance))
diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm
index e05240a7369..8691cdf9664 100644
--- a/code/modules/projectiles/projectile/bullets/lmg.dm
+++ b/code/modules/projectiles/projectile/bullets/lmg.dm
@@ -55,5 +55,14 @@
ricochets_max = 2
ricochet_chance = 60
ricochet_auto_aim_range = 4
- ricochet_incidence_leeway = 35
+ ricochet_incidence_leeway = 55
wound_bonus = -50
+
+/obj/projectile/bullet/mm712x82_bouncy
+ name = "7.12x82mm rubber bullet"
+ damage = 25
+ ricochets_max = 40
+ ricochet_chance = 500 // will bounce off anything and everything, whether they like it or not
+ ricochet_auto_aim_range = 4
+ ricochet_incidence_leeway = 0
+ ricochet_decay_chance = 0.9
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 9e8406e0f85..e0ad366a4b3 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -367,6 +367,11 @@
injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus)
var/list/wounds_checking = GLOB.global_wound_types[woundtype]
+ if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && (get_damage() / max_damage * 50))
+ var/datum/wound/loss/dismembering = new
+ dismembering.apply_dismember(src, woundtype, outright=TRUE)
+ return
+
// quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow
if(ishuman(owner))
var/mob/living/carbon/human/human_wearer = owner