Skip to content

Commit

Permalink
Revolver Tweaks... more like additions, Gunslinger quirk, and more (#…
Browse files Browse the repository at this point in the history
…2761)

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

Oh god. What started as a simple project to fix a bug where ammo boxes
acted like speedloaders and revolver safties turned into. A lot.
In short:
- Single action revolvers now have the hammer as a saftey. Techinically
they still have none, but decocking the hammer works as one.
- Double actions get a proper safety. Yayy
- Cleans up some behavior related to ammo boxes. I need to fucking clean
up that whole file someday.
- Revolvers... should feel better to use. They no longer fast load from
everything now, and are slightly faster to load than magazines
- You probably saw the number of changes in revolver.dm. Yes, along with
being less janky, revolvers support gate loading revolvers. This is
mostly for the shadow and ashhand. Other revolvers still drop everything
on unload.
- You have new actions to choose from when using revovlers. Alt click to
access.
- Adds support to remove energy gun cells like magazines. Leftover from
the branch i poached gun huds from
- OH YEAH, GUN HUDS! Stolen from skyrat
Skyrat-SS13/Skyrat-tg#4333 but made much less
shittier and with sprites made by azlan. This technically isn't used
yet, maybe in the future. It was mostly modified to be used with
revolvers, to see which chambers are loaded
- The gunslinger quirk! See below. 0 points.
- This "resprites" the shadow, but i'd just call it making it better
proportioned compared to others. Also adds several more skins.
- This fixes a fuckup with the flaming arrow as well.

To elaborate on gunslinger:

It gives you more recoil and spread when using SMGs, assault rifles, and
LMGs, it increases accuracy with revolvers, shotguns, and lever actions,
and no changes to anything else.

Also, lets you fan SINGLE ACTION revolvers (HP Shadow, HP Montague, and
HP Ashhand) by one handing it with nothing else in your offhand.
*Slightly* faster than pistols to boot.

Sound effect for moving the cyllinder is
https://freesound.org/people/shelbyshark/sounds/501560/ by the way. Not
from TGMC this time, suprisingly

## Why It's Good For The Game

Should make friendly fire incidents involving revolvers less common, and
make revolvers feel better to use.
Gunslinger should be funny and encourage cowboy blorbos to be more
common.


![image](https://github.com/shiptest-ss13/Shiptest/assets/58402542/86657b45-4d70-4de4-98d7-ea2c6a3dfb3e)


![image](https://github.com/shiptest-ss13/Shiptest/assets/58402542/e98249f7-3245-42de-9ce0-221a51a14a51)

## Changelog

:cl:
add: Gunslinger quirk! Lets you fling around a revolver like an old
western! Good luck shooting anything newer, though.
add: 'New' HP Shadow sprites.
add: Double action revolvers have safeties now!
tweak: Many revolver tweaks
balance: Shadow and Ashhand are gate loaded now, and as such take longer
to load.
fix: Non-speedloaders now load revolvers slowly again.
/:cl:
  • Loading branch information
rye-rice authored Mar 9, 2024
1 parent 8f702ad commit 33ca9c7
Show file tree
Hide file tree
Showing 25 changed files with 915 additions and 105 deletions.
3 changes: 3 additions & 0 deletions code/__DEFINES/dcs/signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,6 @@
/// send when enabling/diabling an autofire component
#define COMSIG_GUN_DISABLE_AUTOFIRE "disable_autofire"
#define COMSIG_GUN_ENABLE_AUTOFIRE "enable_autofire"

///called in /obj/item/gun/process_chamber (src)
#define COMSIG_GUN_CHAMBER_PROCESSED "gun_chamber_processed"
2 changes: 1 addition & 1 deletion code/__DEFINES/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_XENO_IMMUNE "xeno_immune"//prevents xeno huggies implanting skeletons
#define TRAIT_NAIVE "naive"
#define TRAIT_PRIMITIVE "primitive"
#define TRAIT_GUNFLIP "gunflip"
#define TRAIT_GUNSLINGER "gunslinger"
#define TRAIT_SPECIAL_TRAUMA_BOOST "special_trauma_boost" ///Increases chance of getting special traumas, makes them harder to cure
#define TRAIT_BLOODCRAWL_EAT "bloodcrawl_eat"
#define TRAIT_SPACEWALK "spacewalk"
Expand Down
2 changes: 1 addition & 1 deletion code/_globalvars/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_XENO_IMMUNE" = TRAIT_XENO_IMMUNE,
"TRAIT_NAIVE" = TRAIT_NAIVE,
"TRAIT_PRIMITIVE" = TRAIT_PRIMITIVE, //unable to use mechs. Given to Ash Walkers
"TRAIT_GUNFLIP" = TRAIT_GUNFLIP,
"TRAIT_GUNSLINGER" = TRAIT_GUNSLINGER,
"TRAIT_SPECIAL_TRAUMA_BOOST" = TRAIT_SPECIAL_TRAUMA_BOOST,
"TRAIT_BLOODCRAWL_EAT" = TRAIT_BLOODCRAWL_EAT,
"TRAIT_SPACEWALK" = TRAIT_SPACEWALK,
Expand Down
3 changes: 3 additions & 0 deletions code/_onclick/hud/human.dm
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@
combo_display = new /atom/movable/screen/combo()
infodisplay += combo_display

ammo_counter = new /atom/movable/screen/ammo_counter(null, src)
infodisplay += ammo_counter

for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory))
if(inv.slot_id)
inv.hud = src
Expand Down
8 changes: 8 additions & 0 deletions code/datums/traits/neutral.dm
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,11 @@

SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "bad_hair_day", /datum/mood_event/bald)

/datum/quirk/gunslinger
name = "Gunslinger"
desc = "You are one of the fastest guns in the frontier. Those new-fangled and complicated firearms don't suit you; pistols and semi-automatic rifles suit you better, but revolvers in particular were made for you. You can fan single action revolvers, flip any revolver, and have mastery of the greatest handgun ever made. NOT RECOMENDED FOR BEGINNERS. ADVANCED PLAYERS ONLY."
value = 0
gain_text = "<span class='notice'>The HP Shadow is greatest handgun ever made.</span>"
lose_text = "<span class='notice'>...Who the hell would use such antiquated weapons in this year?</span>"
medical_record_text = "Patient always has their hand around their holster."
mob_traits = list(TRAIT_GUNSLINGER)
2 changes: 1 addition & 1 deletion code/modules/cargo/packs/ammo.dm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

/datum/supply_pack/ammo/m45_speedloader
name = ".45 ACP Speedloader Crate"
desc = "Contains a .45 ACP speedloader for revolvers, containing six rounds."
desc = "Contains a .45 ACP speedloader for the HP Montagne, containing six rounds."
contains = list(/obj/item/ammo_box/c45_speedloader)
cost = 400

Expand Down
32 changes: 17 additions & 15 deletions code/modules/projectiles/boxes_magazines/_box_magazine.dm
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//TODO: make this code more readable. weird var names, convoluted logic, etc

//Boxes of ammo
/obj/item/ammo_box
name = "ammo box (null_reference_exception)"
Expand Down Expand Up @@ -61,7 +63,7 @@
return b

///puts a round into the magazine
/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0)
/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = FALSE)
// Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type.
if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type))
return FALSE
Expand All @@ -87,31 +89,31 @@
/obj/item/ammo_box/proc/can_load(mob/user)
return TRUE

/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0)
/obj/item/ammo_box/attackby(obj/item/attacking_obj, mob/user, params, silent = FALSE, replace_spent = FALSE)
var/num_loaded = 0
if(!can_load(user))
return
if(istype(A, /obj/item/ammo_box))
var/obj/item/ammo_box/AM = A
for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
if(!((instant_load && AM.instant_load) || (stored_ammo.len >= max_ammo) || do_after_mob(user, list(AM), 1 SECONDS,)))
if(istype(attacking_obj, /obj/item/ammo_box))
var/obj/item/ammo_box/attacking_box = attacking_obj
for(var/obj/item/ammo_casing/casing_to_insert in attacking_box.stored_ammo)
if(!((instant_load && attacking_box.instant_load) || (stored_ammo.len >= max_ammo) || do_after_mob(user, list(attacking_box), 1 SECONDS)))
break
var/did_load = give_round(AC, replace_spent)
var/did_load = give_round(casing_to_insert, replace_spent)
if(!did_load)
break
AM.stored_ammo -= AC
attacking_box.stored_ammo -= casing_to_insert
if(!silent)
playsound(get_turf(AM), 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE) //src is nullspaced, which means internal magazines won't properly play sound, thus we use AM
playsound(get_turf(attacking_box), 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE) //src is nullspaced, which means internal magazines won't properly play sound, thus we use attacking_box
num_loaded++
A.update_appearance()
attacking_obj.update_appearance()
update_appearance()

if(istype(A, /obj/item/ammo_casing))
var/obj/item/ammo_casing/AC = A
if(give_round(AC, replace_spent))
user.transferItemToLoc(AC, src, TRUE)
if(istype(attacking_obj, /obj/item/ammo_casing))
var/obj/item/ammo_casing/casing_to_insert = attacking_obj
if(give_round(casing_to_insert, replace_spent))
user.transferItemToLoc(casing_to_insert, src, TRUE)
if(!silent)
playsound(AC, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE)
playsound(casing_to_insert, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE)
num_loaded++
update_appearance()

Expand Down
58 changes: 52 additions & 6 deletions code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@
max_ammo = 7
instant_load = TRUE

/obj/item/ammo_box/magazine/internal/cylinder/get_round(keep = 0)
rotate()
/obj/item/ammo_box/magazine/internal/cylinder/get_round(keep = FALSE, counter_clockwise = FALSE)
rotate(counter_clockwise)

var/b = stored_ammo[1]
if(!keep)
stored_ammo[1] = null

return b

/obj/item/ammo_box/magazine/internal/cylinder/proc/rotate()
var/b = stored_ammo[1]
stored_ammo.Cut(1,2)
stored_ammo.Insert(0, b)
/obj/item/ammo_box/magazine/internal/cylinder/proc/rotate(counter_clockwise = FALSE)
var/b
if(!counter_clockwise)
b = stored_ammo[1]
stored_ammo.Cut(1,2)
stored_ammo.Insert(0, b)
else
b = stored_ammo[max_ammo]
stored_ammo.Cut(max_ammo,max_ammo+1)
stored_ammo.Insert(1, b)

/obj/item/ammo_box/magazine/internal/cylinder/proc/spin()
for(var/i in 1 to rand(0, max_ammo*2))
Expand All @@ -31,6 +37,8 @@
L.Add(bullet)
if(drop_list)//We have to maintain the list size, to emulate a cylinder
stored_ammo[i] = null
else
L.Add(null)
return L

/obj/item/ammo_box/magazine/internal/cylinder/give_round(obj/item/ammo_casing/R, replace_spent = 0)
Expand All @@ -48,3 +56,41 @@
return TRUE

return FALSE

/obj/item/ammo_box/magazine/internal/cylinder/attackby(obj/item/attacking_obj, mob/user, params, silent = FALSE, replace_spent = FALSE)
var/num_loaded = 0
if(!can_load(user))
return
if(istype(attacking_obj, /obj/item/ammo_box))
var/obj/item/ammo_box/attacking_box = attacking_obj
var/list/ammo_list_no_empty = ammo_list(FALSE)
listclearnulls(ammo_list_no_empty)
for(var/obj/item/ammo_casing/casing_to_insert in attacking_box.stored_ammo)
if(!((instant_load && attacking_box.instant_load) || (ammo_list_no_empty.len >= max_ammo) || do_after_mob(user, list(attacking_box), 1 SECONDS))) //stupid work around for revolvers
break
var/did_load = give_round(casing_to_insert, replace_spent)
if(!did_load)
break
attacking_box.stored_ammo -= casing_to_insert
if(!silent)
playsound(get_turf(attacking_box), 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE) //src is nullspaced, which means internal magazines won't properly play sound, thus we use attacking_box
num_loaded++
ammo_list_no_empty = ammo_list(FALSE)
listclearnulls(ammo_list_no_empty)
attacking_obj.update_appearance()
update_appearance()

if(istype(attacking_obj, /obj/item/ammo_casing))
var/obj/item/ammo_casing/casing_to_insert = attacking_obj
if(give_round(casing_to_insert, replace_spent))
user.transferItemToLoc(casing_to_insert, src, TRUE)
if(!silent)
playsound(casing_to_insert, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE)
num_loaded++
update_appearance()


if(num_loaded)
if(!silent)
to_chat(user, "<span class='notice'>You load [num_loaded] cartridge\s into \the [src]!</span>")
return num_loaded
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,3 @@
desc = "Oh god, this shouldn't be here"
flags_1 = CONDUCT_1
item_flags = ABSTRACT

//internals magazines are accessible, so replace spent ammo if full when trying to put a live one in
/obj/item/ammo_box/magazine/internal/give_round(obj/item/ammo_casing/R)
return ..(R,1)
4 changes: 4 additions & 0 deletions code/modules/projectiles/boxes_magazines/internal/revolver.dm
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@
ammo_type = /obj/item/ammo_casing/c45
caliber = ".45"
max_ammo = 6
instant_load = FALSE

/obj/item/ammo_box/magazine/internal/cylinder/rev45/montagne
name = "montagne revolver cylinder"
instant_load = TRUE
16 changes: 10 additions & 6 deletions code/modules/projectiles/gun.dm
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@
///If the saftey on? If so, we can't fire the weapon
var/safety = FALSE

///The wording of safety. Useful for guns that have a non-standard safety system, like a revolver
var/safety_wording = "safety"

/obj/item/gun/Initialize()
. = ..()
RegisterSignal(src, COMSIG_TWOHANDED_WIELD, PROC_REF(on_wield))
Expand Down Expand Up @@ -237,6 +240,7 @@

//called after the gun has successfully fired its chambered ammo.
/obj/item/gun/proc/process_chamber()
SEND_SIGNAL(src, COMSIG_GUN_CHAMBER_PROCESSED)
return FALSE

//check if there's enough ammo/energy/whatever to shoot one time
Expand All @@ -260,9 +264,9 @@
if(muzzle_flash && !muzzle_flash.applied)
handle_muzzle_flash(user, muzzle_angle)

if(wielded_fully && recoil)
if(wielded_fully)
simulate_recoil(user, recoil, actual_angle)
else if(!wielded_fully && recoil_unwielded)
else if(!wielded_fully)
simulate_recoil(user, recoil_unwielded, actual_angle)

if(suppressed)
Expand Down Expand Up @@ -540,8 +544,8 @@
if(!silent)
playsound(user, 'sound/weapons/gun/general/selector.ogg', 100, TRUE)
user.visible_message(
span_notice("[user] turns the safety on [src] [safety ? "<span class='green'>ON</span>" : "<span class='red'>OFF</span>"]."),
span_notice("You turn the safety on [src] [safety ? "<span class='green'>ON</span>" : "<span class='red'>OFF</span>"]."),
span_notice("[user] turns the [safety_wording] on [src] [safety ? "<span class='green'>ON</span>" : "<span class='red'>OFF</span>"]."),
span_notice("You turn the [safety_wording] on [src] [safety ? "<span class='green'>ON</span>" : "<span class='red'>OFF</span>"]."),
)

update_appearance()
Expand Down Expand Up @@ -735,9 +739,9 @@
var/mutable_appearance/safety_overlay
safety_overlay = mutable_appearance('icons/obj/guns/safety.dmi')
if(safety)
safety_overlay.icon_state = "safety-on"
safety_overlay.icon_state = "[safety_wording]-on"
else
safety_overlay.icon_state = "safety-off"
safety_overlay.icon_state = "[safety_wording]-off"
. += safety_overlay

/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer)
Expand Down
17 changes: 2 additions & 15 deletions code/modules/projectiles/guns/ballistic.dm
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
var/recent_rack = 0
///Whether the gun can be sawn off by sawing tools
var/can_be_sawn_off = FALSE
var/flip_cooldown = 0

///Whether the gun can be tacloaded by slapping a fresh magazine directly on it
var/tac_reloads = TRUE //Snowflake mechanic no more.
Expand Down Expand Up @@ -157,6 +156,7 @@
chambered = null
if (chamber_next_round && (magazine?.max_ammo > 1))
chamber_round()
SEND_SIGNAL(src, COMSIG_GUN_CHAMBER_PROCESSED)

///Used to chamber a new round and eject the old one
/obj/item/gun/ballistic/proc/chamber_round(keep_bullet = FALSE)
Expand Down Expand Up @@ -269,7 +269,7 @@
if (chambered && !chambered.BB)
chambered.on_eject()
chambered = null
var/num_loaded = magazine.attackby(A, user, params, TRUE)
var/num_loaded = magazine.attackby(A, user, params)
if (num_loaded)
to_chat(user, "<span class='notice'>You load [num_loaded] [cartridge_wording]\s into \the [src].</span>")
playsound(src, load_sound, load_sound_volume, load_sound_vary)
Expand Down Expand Up @@ -359,19 +359,6 @@
return ..()

/obj/item/gun/ballistic/unique_action(mob/living/user)
if(HAS_TRAIT(user, TRAIT_GUNFLIP))
if(flip_cooldown <= world.time)
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
to_chat(user, "<span class='userdanger'>While trying to flip the [src] you pull the trigger and accidently shoot yourself!</span>")
var/flip_mistake = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_CHEST)
process_fire(user, user, FALSE, flip_mistake)
user.dropItemToGround(src, TRUE)
return
flip_cooldown = (world.time + 30)
SpinAnimation(7,1)
user.visible_message("<span class='notice'>[user] spins the [src] around their finger by the trigger. That’s pretty badass.</span>")
playsound(src, 'sound/items/handling/ammobox_pickup.ogg', 20, FALSE)
return
if(bolt_type == BOLT_TYPE_NO_BOLT)
chambered = null
var/num_unloaded = 0
Expand Down
16 changes: 16 additions & 0 deletions code/modules/projectiles/guns/ballistic/assault.dm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@
rack_sound = 'sound/weapons/gun/rifle/ar_cock.ogg'
spread_unwielded = 20

/obj/item/gun/ballistic/automatic/assault/calculate_recoil(mob/user, recoil_bonus = 0)
var/gunslinger_bonus = 2
var/total_recoil = recoil_bonus
if(HAS_TRAIT(user, TRAIT_GUNSLINGER)) //gunslinger penalty
total_recoil += gunslinger_bonus
total_recoil = clamp(total_recoil,0,INFINITY)
return total_recoil

/obj/item/gun/ballistic/automatic/assault/calculate_spread(mob/user, bonus_spread)
var/gunslinger_bonus = 8
var/total_spread = bonus_spread
if(HAS_TRAIT(user, TRAIT_GUNSLINGER)) //gunslinger penalty
total_spread += gunslinger_bonus
total_spread = clamp(total_spread,0,INFINITY)
return total_spread

/obj/item/gun/ballistic/automatic/assault/skm
name = "\improper SKM-24"
desc = "An obsolete model of assault rifle once used by CLIP. Legendary for its durability and low cost, surplus rifles are commonplace on the Frontier, and the design has been widely copied. Chambered in 7.62x40mm CLIP."
Expand Down
10 changes: 8 additions & 2 deletions code/modules/projectiles/guns/ballistic/hmg.dm
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,23 @@
retract_bipod(user=user)

/obj/item/gun/ballistic/automatic/hmg/calculate_recoil(mob/user, recoil_bonus = 0)
var/gunslinger_bonus = 1
var/total_recoil = recoil_bonus
if(bipod_deployed)
total_recoil += deploy_recoil_bonus
total_recoil = clamp(total_recoil,0,INFINITY)
if(HAS_TRAIT(user, TRAIT_GUNSLINGER)) //gunslinger penalty
total_recoil += gunslinger_bonus
total_recoil = clamp(total_recoil,0,INFINITY)
return total_recoil

/obj/item/gun/ballistic/automatic/hmg/calculate_spread(mob/user, bonus_spread)
var/gunslinger_bonus = 4
var/total_spread = bonus_spread
if(bipod_deployed)
total_spread += deploy_spread_bonus
total_spread = clamp(total_spread,0,INFINITY)
if(HAS_TRAIT(user, TRAIT_GUNSLINGER)) //gunslinger penalty
total_spread += gunslinger_bonus
total_spread = clamp(total_spread,0,INFINITY)
return total_spread


Expand Down
Loading

0 comments on commit 33ca9c7

Please sign in to comment.