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