Fixes Russian Roulette by refactoring the gun to not be an affront to god (#90426)

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
Co-authored-by: ATH1909 <42606352+ATH1909@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
This commit is contained in:
MrMelbert
2025-04-15 13:00:43 -05:00
committed by GitHub
parent c144fd7d5c
commit d1d848d83f
7 changed files with 193 additions and 95 deletions

View File

@@ -53,6 +53,9 @@
var/dual_wield_spread = 24 //additional spread when dual wielding
///Can we hold up our target with this? Default to yes
var/can_hold_up = TRUE
/// If TRUE, and we aim at ourselves, it will initiate a do after to fire at ourselves.
/// If FALSE it will just try to fire at ourselves straight up.
var/doafter_self_shoot = TRUE
/// Just 'slightly' snowflakey way to modify projectile damage for projectiles fired from this gun.
var/projectile_damage_multiplier = 1
@@ -209,27 +212,41 @@
return FALSE
if(tk_firing(user))
visible_message(
span_danger("[src] fires itself[pointblank ? " point blank at [pbtarget]!" : "!"]"),
blind_message = span_hear("You hear a gunshot!"),
vision_distance = COMBAT_MESSAGE_RANGE
span_danger("[src] fires itself[pointblank ? " point blank at [pbtarget]!" : "!"]"),
blind_message = span_hear("You hear a gunshot!"),
vision_distance = COMBAT_MESSAGE_RANGE
)
else if(pointblank)
user.visible_message(
if(user == pbtarget)
user.visible_message(
span_danger("[user] fires [src] point blank at [user.p_them()]self!"),
span_userdanger("You fire [src] point blank at yourself!"),
span_hear("You hear a gunshot!"),
vision_distance = COMBAT_MESSAGE_RANGE,
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
user.visible_message(
span_danger("[user] fires [src] point blank at [pbtarget]!"),
span_danger("You fire [src] point blank at [pbtarget]!"),
span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget
)
to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!"))
span_hear("You hear a gunshot!"),
vision_distance = COMBAT_MESSAGE_RANGE,
ignored_mobs = pbtarget,
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!"))
if(pb_knockback > 0 && ismob(pbtarget))
var/mob/PBT = pbtarget
var/atom/throw_target = get_edge_target_turf(PBT, user.dir)
PBT.throw_at(throw_target, pb_knockback, 2)
else if(!tk_firing(user))
user.visible_message(
span_danger("[user] fires [src]!"),
blind_message = span_hear("You hear a gunshot!"),
vision_distance = COMBAT_MESSAGE_RANGE,
ignored_mobs = user
span_danger("[user] fires [src]!"),
span_danger("You fire [src]!"),
span_hear("You hear a gunshot!"),
vision_distance = COMBAT_MESSAGE_RANGE,
ignored_mobs = user,
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
if(chambered?.integrity_damage)
@@ -338,7 +355,7 @@
return
if(!ismob(target)) //melee attack
return
if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected)
if(target == user && (user.zone_selected != BODY_ZONE_PRECISE_MOUTH && doafter_self_shoot)) //so we can't shoot ourselves (unless mouth selected)
return
if(iscarbon(target))
var/mob/living/carbon/C = target
@@ -352,10 +369,9 @@
if(!can_trigger_gun(L))
return
if(flag)
if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH)
handle_suicide(user, target, params)
return
if(flag && doafter_self_shoot && user.zone_selected == BODY_ZONE_PRECISE_MOUTH)
handle_suicide(user, target, params)
return
if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can shoot.
shoot_with_empty_chamber(user)

View File

@@ -17,8 +17,9 @@
var/last_fire = 0
/obj/item/gun/ballistic/revolver/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread)
..()
last_fire = world.time
. = ..()
if(.)
last_fire = world.time
/obj/item/gun/ballistic/revolver/chamber_round(spin_cylinder = TRUE, replace_new_round)
@@ -99,7 +100,7 @@
var/live_ammo = get_ammo(FALSE, FALSE)
. += "[live_ammo ? live_ammo : "None"] of those are live rounds."
if (current_skin)
. += "It can be spun with <b>alt+click</b>"
. += span_notice("It can be spun with [EXAMINE_HINT("alt-click")].")
/obj/item/gun/ballistic/revolver/ignition_effect(atom/A, mob/user)
if(last_fire && last_fire + 15 SECONDS > world.time)
@@ -185,22 +186,47 @@
desc = "A Russian-made revolver for drinking games. Uses .357 ammo, and has a mechanism requiring you to spin the chamber before each trigger pull."
icon_state = "russianrevolver"
accepted_magazine_type = /obj/item/ammo_box/magazine/internal/cylinder/rus357
var/spun = FALSE
hidden_chambered = TRUE //Cheater.
gun_flags = NOT_A_REAL_GUN
can_hold_up = FALSE // for obvious reasons
doafter_self_shoot = FALSE // snowflake
/// If we've been spun before firing
var/spun = FALSE
/// Do after for trying to fire the gun
var/aim_time = 4 SECONDS
/obj/item/gun/ballistic/revolver/russian/examine(mob/user)
. = ..()
. += span_notice("You can change length of your pause before pulling the trigger with [EXAMINE_HINT("alt-right-click")].")
/obj/item/gun/ballistic/revolver/russian/click_alt_secondary(mob/user)
if(loc != user)
to_chat(user, span_warning("You need to be holding the gun to determine how long you are going to pause!"))
return CLICK_ACTION_BLOCKING
var/new_aim_time = tgui_input_number(user, "How long will you pause before pulling the trigger (seconds)?", "Do you feel lucky?", (aim_time / (1 SECONDS)), 10, 0)
if(loc != user || user.incapacitated)
return CLICK_ACTION_BLOCKING
aim_time = new_aim_time * (1 SECONDS)
to_chat(user, span_warning("You're going to pause [aim_time] second\s before pulling the trigger[aim_time == 0 ? "... Good luck" : ""]."))
return CLICK_ACTION_SUCCESS
/obj/item/gun/ballistic/revolver/russian/dropped(mob/user, silent)
. = ..()
aim_time = initial(aim_time) // next person chooses their own time
/obj/item/gun/ballistic/revolver/russian/do_spin()
. = ..()
if(.)
spun = TRUE
/obj/item/gun/ballistic/revolver/russian/attackby(obj/item/A, mob/user, params)
..()
if(get_ammo() > 0)
spin()
update_appearance()
A.update_appearance()
return
/obj/item/gun/ballistic/revolver/russian/can_shoot()
return TRUE // we ALWAYS want to shoot. even if we don't have a chambered round, even if our chambered round has no bullet
/obj/item/gun/ballistic/revolver/russian/load_gun(obj/item/ammo, mob/living/user)
. = ..()
if(!.)
return
do_spin()
/obj/item/gun/ballistic/revolver/russian/can_trigger_gun(mob/living/user, akimbo_usage)
if(akimbo_usage)
@@ -210,94 +236,140 @@
/obj/item/gun/ballistic/revolver/russian/attack_self(mob/user)
if(!spun)
spin()
spun = TRUE
return
..()
return TRUE
return ..()
/obj/item/gun/ballistic/revolver/russian/fire_gun(atom/target, mob/living/user, flag, params)
. = ..(null, user, flag, params)
/obj/item/gun/ballistic/revolver/russian/handle_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
from_firing = FALSE // never eject casings from firing the gun
return ..()
var/tk_controlled = FALSE
if(flag)
if(!(target in user.contents) && ismob(target))
if(user.combat_mode) // Flogging action
return
else if (HAS_TRAIT_FROM_ONLY(src, TRAIT_TELEKINESIS_CONTROLLED, REF(user))) // if we're far away, you can still fire it at yourself if you have TK.
tk_controlled = TRUE
if(isliving(user))
if(!can_trigger_gun(user))
return
/obj/item/gun/ballistic/revolver/russian/try_fire_gun(atom/target, mob/living/user, params)
if(user.combat_mode)
return FALSE // melee attack
if(target != user)
playsound(src, dry_fire_sound, 30, TRUE)
shoot_with_empty_chamber(user)
spun = FALSE
user.visible_message(
span_danger("[user.name] tries to fire \the [src] at the same time, but only succeeds at looking like an idiot."),
span_danger("[user] tries to fire \the [src] aimed at something else, but only succeeds at looking like an idiot."),
span_danger("\The [src]'s anti-combat mechanism prevents you from firing it at anyone but yourself!"),
)
return
return TRUE // no melee attack
if(!spun)
to_chat(user, span_warning("You need to spin \the [src]'s chamber first!"))
return TRUE // no melee attack
if(HAS_TRAIT(user, TRAIT_CURSED)) // I cannot live, I cannot die, trapped in myself, body my holding cell.
to_chat(user, span_warning("What a horrible night... To have a curse!"))
return TRUE // no melee attack
if(loc != user)
if(tk_firing(user))
to_chat(user, span_warning("Russian roulette is stressful enough without trying to focus on telekinesis!"))
else
to_chat(user, span_warning("You need to be holding the gun to fire it!"))
return TRUE // no melee attack
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(!spun)
to_chat(user, span_warning("You need to spin \the [src]'s chamber first!"))
return
return ..() // try to shoot the gun
spun = FALSE
var/zone = check_zone(user.zone_selected)
var/obj/item/bodypart/affecting = H.get_bodypart(zone)
var/is_target_face = zone == BODY_ZONE_HEAD || zone == BODY_ZONE_PRECISE_EYES || zone == BODY_ZONE_PRECISE_MOUTH
var/loaded_rounds = get_ammo(FALSE, FALSE) // check before it is fired
if(HAS_TRAIT(user, TRAIT_CURSED)) // I cannot live, I cannot die, trapped in myself, body my holding cell.
to_chat(user, span_warning("What a horrible night... To have a curse!"))
return
if(loaded_rounds && is_target_face)
add_memory_in_range(user, 7, /datum/memory/witnessed_russian_roulette, \
protagonist = user, \
antagonist = src, \
rounds_loaded = loaded_rounds, \
aimed_at = affecting.name, \
result = (chambered ? "lost" : "won"), \
// Replaces clumsy check with a do after
/obj/item/gun/ballistic/revolver/russian/check_botched(mob/living/user, atom/target)
if(aim_time <= 0)
return FALSE
user.visible_message(
span_danger("[user] aims \the [src] at [user.p_their()] [parse_zone(user.zone_selected)]..."),
span_userdanger("You aim \the [src] at your [parse_zone(user.zone_selected)]..."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
if(prob(10) && !HAS_TRAIT(user, TRAIT_FEARLESS))
user.adjust_jitter(aim_time)
if(!do_after(user, aim_time, target))
if(!user.incapacitated)
user.visible_message(
span_danger("[user] loses [user.p_their()] nerve and puts \the [src] down."),
span_userdanger("You lose your nerve and put \the [src] down."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
return TRUE
return FALSE
if(chambered)
var/obj/item/ammo_casing/AC = chambered
if(AC.fire_casing(user, user, params, distro = 0, quiet = 0, zone_override = null, spread = 0, fired_from = src))
playsound(user, fire_sound, fire_sound_volume, vary_fire_sound)
if(is_target_face)
shoot_self(user, affecting)
else if(tk_controlled) // the consequence of you doing the telekinesis stuff
to_chat(user, span_userdanger("As your mind concentrates on the revolver, you realize that it's pointing towards your head a little too late!"))
shoot_self(user, BODY_ZONE_HEAD)
else
user.visible_message(span_danger("[user.name] cowardly fires [src] at [user.p_their()] [affecting.name]!"), span_userdanger("You cowardly fire [src] at your [affecting.name]!"), span_hear("You hear a gunshot!"))
chambered = null
user.add_mood_event("russian_roulette_lose", /datum/mood_event/russian_roulette_lose)
return
/obj/item/gun/ballistic/revolver/russian/before_firing(atom/target, mob/user)
if(target != user)
CRASH("Russian revolver somehow got to before_firing with a target that isn't the user!")
// we will definitely have a chambered round, but not always projectile
if(check_zone(user.zone_selected) == BODY_ZONE_HEAD)
chambered.loaded_projectile?.damage = 300
chambered.loaded_projectile?.wound_bonus = 100
else
chambered.loaded_projectile?.damage = 80
chambered.loaded_projectile?.wound_bonus = 10
/obj/item/gun/ballistic/revolver/russian/fire_gun(atom/target, mob/living/user, flag, params)
// . = false = no shot fired
. = ..()
spun = FALSE
var/is_target_face = check_zone(user.zone_selected) == BODY_ZONE_HEAD
var/aimed_at_readable = parse_zone(user.zone_selected)
var/loaded_rounds = get_ammo(FALSE, FALSE) // check before it is fired
if(loaded_rounds && is_target_face)
add_memory_in_range(user, 7, /datum/memory/witnessed_russian_roulette, \
protagonist = user, \
antagonist = src, \
rounds_loaded = loaded_rounds, \
aimed_at = aimed_at_readable, \
result = (. ? "lost" : "won"), \
)
if(!.)
if(loaded_rounds && is_target_face)
user.add_mood_event("russian_roulette_win", /datum/mood_event/russian_roulette_win, loaded_rounds)
user.visible_message(
span_danger("[user][is_target_face ? "": " cowardly"] points \the [src] at [user.p_their()] [aimed_at_readable], pulls the trigger, and... nothing happens!"),
span_danger("You[is_target_face ? "": " cowardly"] point \the [src] at your [aimed_at_readable], pull the trigger, and... nothing happens!"),
span_hear("You hear a click!"),
vision_distance = COMBAT_MESSAGE_RANGE,
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
return TRUE // so they don't hit themselves in the forehead. because returning FALSE translates to "do melee attack" for whatever reason
user.visible_message(span_danger("*click*"))
playsound(src, dry_fire_sound, 30, TRUE)
user.visible_message(
span_danger("[user][is_target_face ? "": " cowardly"] aims \the [src] at [user.p_their()] [aimed_at_readable] as it goes off!"),
span_danger("You[is_target_face ? "": " cowardly"] aim \the [src] at your [aimed_at_readable] as it goes off![user.stat >= HARD_CRIT ? " <b>Everything suddenly goes black.</b>" : ""]"),
span_hear("You hear a grunt[user.stat == CONSCIOUS ? "" : ", followed by a thud"]!"),
vision_distance = COMBAT_MESSAGE_RANGE,
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
shoot_self(user, check_zone(user.zone_selected))
return .
/// Called after successfully(if you can call it that) shooting ourselves
/obj/item/gun/ballistic/revolver/russian/proc/shoot_self(mob/living/carbon/human/user, affecting = BODY_ZONE_HEAD)
user.apply_damage(300, BRUTE, affecting)
user.visible_message(span_danger("[user.name] fires [src] at [user.p_their()] head!"), span_userdanger("You fire [src] at your head!"), span_hear("You hear a gunshot!"))
user.add_mood_event(
"russian_roulette_lose",
affecting == BODY_ZONE_HEAD ? /datum/mood_event/russian_roulette_lose : /datum/mood_event/russian_roulette_lose_cheater,
)
/obj/item/gun/ballistic/revolver/russian/soul
name = "cursed Russian revolver"
desc = "To play with this revolver requires wagering your very soul."
/obj/item/gun/ballistic/revolver/russian/soul/shoot_self(mob/living/user)
/obj/item/gun/ballistic/revolver/russian/soul/shoot_self(mob/living/user, affecting = BODY_ZONE_HEAD)
. = ..()
var/obj/item/soulstone/anybody/revolver/stone = new /obj/item/soulstone/anybody/revolver(get_turf(src))
if(!stone.capture_soul(user, forced = TRUE)) //Something went wrong
qdel(stone)
if(affecting == BODY_ZONE_HEAD)
var/obj/item/soulstone/anybody/revolver/stone = new(user.drop_location())
if(!stone.capture_soul(user, forced = TRUE)) //Something went wrong
qdel(stone)
return
user.visible_message(
span_danger("[user]'s soul is captured by \the [src]!"),
span_userdanger("You've lost the gamble! Your soul is forfeit!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
return
user.visible_message(span_danger("[user.name]'s soul is captured by \the [src]!"), span_userdanger("You've lost the gamble! Your soul is forfeit!"))
user.visible_message(
span_danger("[user] is punished for trying to cheat the game!"),
span_userdanger("You've lost the gamble! Not only is your soul forfeit, but it is whisked away for attempting to cheat death!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
user.dust(drop_items = TRUE)
/obj/item/gun/ballistic/revolver/reverse //Fires directly at its user... unless the user is a clown, of course.
clumsy_check = FALSE

View File

@@ -135,7 +135,9 @@
if(ammo_pack && ammo_pack.overheat >= ammo_pack.overheat_max)
to_chat(user, span_warning("The gun's heat sensor locked the trigger to prevent lens damage!"))
return
..()
. = ..()
if(!.)
return
ammo_pack.overheat++
if(ammo_pack.battery)
var/transferred = ammo_pack.battery.use(cell.maxcharge - cell.charge, force = TRUE)

View File

@@ -54,3 +54,4 @@
F.icon_state = initial(F.icon_state) + "_active"
playsound(user.loc, 'sound/items/weapons/armbomb.ogg', 75, TRUE, -3)
addtimer(CALLBACK(F, TYPE_PROC_REF(/obj/item/grenade, detonate)), 1.5 SECONDS)
return TRUE

View File

@@ -68,6 +68,7 @@
START_PROCESSING(SSobj, src)
SSblackbox.record_feedback("tally", "gun_fired", 1, type)
return TRUE
/obj/item/gun/medbeam/process()
if(!mounted && !isliving(loc))

View File

@@ -225,8 +225,9 @@
trigger_guard = TRIGGER_GUARD_ALLOW_ALL
/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
. = ..()
if(!.)
return
visible_message(span_danger("[user] shoots the blowgun!"))
user.adjustStaminaLoss(20, updating_stamina = FALSE)
user.adjustOxyLoss(20)
return ..()