mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> Co-authored-by: Kashargul <KashL@t-online.de>
353 lines
13 KiB
Plaintext
353 lines
13 KiB
Plaintext
/obj/item/gun/projectile
|
|
name = "gun"
|
|
desc = "A gun that fires bullets."
|
|
icon_state = "revolver"
|
|
origin_tech = list(TECH_COMBAT = 2, TECH_MATERIAL = 2)
|
|
w_class = ITEMSIZE_NORMAL
|
|
matter = list(MAT_STEEL = 1000)
|
|
recoil = 1
|
|
projectile_type = /obj/item/projectile/bullet/pistol/strong //Only used for chameleon guns
|
|
|
|
var/caliber = ".357" //determines which casings will fit
|
|
var/handle_casings = EJECT_CASINGS //determines how spent casings should be handled
|
|
var/load_method = SINGLE_CASING|SPEEDLOADER //1 = Single shells, 2 = box or quick loader, 3 = magazine
|
|
var/obj/item/ammo_casing/chambered = null
|
|
|
|
reload_time = 1 //Ballistics reload fast, but not instantly
|
|
|
|
//For SINGLE_CASING or SPEEDLOADER guns
|
|
var/max_shells = 0 //the number of casings that will fit inside
|
|
var/ammo_type = null //the type of ammo that the gun comes preloaded with
|
|
var/list/loaded = list() //stored ammo
|
|
|
|
//For MAGAZINE guns
|
|
var/magazine_type = null //the type of magazine that the gun comes preloaded with
|
|
var/obj/item/ammo_magazine/ammo_magazine = null //stored magazine
|
|
var/allowed_magazines //determines list of which magazines will fit in the gun
|
|
var/auto_eject = 0 //if the magazine should automatically eject itself when empty.
|
|
var/auto_eject_sound = null
|
|
//TODO generalize ammo icon states for guns
|
|
//var/magazine_states = 0
|
|
//var/list/icon_keys = list() //keys
|
|
//var/list/ammo_states = list() //values
|
|
|
|
var/random_start_ammo = FALSE //randomize amount of starting ammo
|
|
|
|
/obj/item/gun/projectile/New(loc, var/starts_loaded = 1)
|
|
..()
|
|
if(starts_loaded)
|
|
if(ispath(ammo_type) && (load_method & (SINGLE_CASING|SPEEDLOADER)))
|
|
for(var/i in 1 to max_shells)
|
|
loaded += new ammo_type(src)
|
|
if(random_start_ammo)
|
|
loaded.Cut(0,rand(0,max_shells))
|
|
if(ispath(magazine_type) && (load_method & MAGAZINE))
|
|
ammo_magazine = new magazine_type(src)
|
|
allowed_magazines += /obj/item/ammo_magazine/smart
|
|
if(random_start_ammo)
|
|
var/ammo_cut = rand(0,ammo_magazine.max_ammo)
|
|
ammo_magazine.contents.Cut(0,ammo_cut)
|
|
ammo_magazine.stored_ammo.Cut(0,ammo_cut)
|
|
update_icon()
|
|
|
|
/obj/item/gun/projectile/consume_next_projectile()
|
|
//get the next casing
|
|
if(loaded.len)
|
|
chambered = loaded[1] //load next casing.
|
|
if(handle_casings != HOLD_CASINGS)
|
|
loaded -= chambered
|
|
else if(ammo_magazine && ammo_magazine.stored_ammo.len)
|
|
chambered = ammo_magazine.stored_ammo[ammo_magazine.stored_ammo.len]
|
|
if(handle_casings != HOLD_CASINGS)
|
|
ammo_magazine.stored_ammo -= chambered
|
|
|
|
var/mob/living/M = loc // TGMC Ammo HUD
|
|
if(istype(M)) // TGMC Ammo HUD
|
|
M?.hud_used.update_ammo_hud(M, src)
|
|
|
|
if (chambered)
|
|
return chambered.BB
|
|
return null
|
|
|
|
/obj/item/gun/projectile/handle_post_fire()
|
|
..()
|
|
if(chambered)
|
|
chambered.expend()
|
|
process_chambered()
|
|
|
|
/obj/item/gun/projectile/handle_click_empty()
|
|
..()
|
|
process_chambered()
|
|
|
|
/obj/item/gun/projectile/proc/process_chambered()
|
|
if (!chambered) return
|
|
|
|
// Aurora forensics port, gunpowder residue.
|
|
if(chambered.leaves_residue)
|
|
var/mob/living/carbon/human/H = loc
|
|
if(istype(H))
|
|
if(!istype(H.gloves, /obj/item/clothing))
|
|
H.gunshot_residue = chambered.caliber
|
|
else
|
|
var/obj/item/clothing/G = H.gloves
|
|
G.gunshot_residue = chambered.caliber
|
|
|
|
switch(handle_casings)
|
|
if(EJECT_CASINGS) //eject casing onto ground.
|
|
if(chambered.caseless)
|
|
qdel(chambered)
|
|
return
|
|
else
|
|
chambered.loc = get_turf(src)
|
|
playsound(src, "casing", 50, 1)
|
|
if(CYCLE_CASINGS) //cycle the casing back to the end.
|
|
if(ammo_magazine)
|
|
ammo_magazine.stored_ammo += chambered
|
|
else
|
|
loaded += chambered
|
|
|
|
if(handle_casings != HOLD_CASINGS)
|
|
chambered = null
|
|
|
|
var/mob/living/M = loc // TGMC Ammo HUD
|
|
if(istype(M)) // TGMC Ammo HUD
|
|
M?.hud_used.update_ammo_hud(M, src)
|
|
|
|
|
|
//Attempts to load A into src, depending on the type of thing being loaded and the load_method
|
|
//Maybe this should be broken up into separate procs for each load method?
|
|
/obj/item/gun/projectile/proc/load_ammo(var/obj/item/A, mob/user)
|
|
if(istype(A, /obj/item/ammo_magazine))
|
|
var/obj/item/ammo_magazine/AM = A
|
|
if(!(load_method & AM.mag_type) || caliber != AM.caliber || allowed_magazines && !is_type_in_list(A, allowed_magazines))
|
|
to_chat(user, span_warning("[AM] won't load into [src]!"))
|
|
return
|
|
switch(AM.mag_type)
|
|
if(MAGAZINE)
|
|
if(ammo_magazine)
|
|
to_chat(user, span_warning("[src] already has a magazine loaded.")) //already a magazine here
|
|
return
|
|
if(do_after(user, reload_time * AM.w_class))
|
|
user.remove_from_mob(AM)
|
|
AM.loc = src
|
|
ammo_magazine = AM
|
|
user.visible_message("[user] inserts [AM] into [src].", span_notice("You insert [AM] into [src]."))
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
playsound(src, 'sound/weapons/flipblade.ogg', 50, 1)
|
|
if(SPEEDLOADER)
|
|
if(loaded.len >= max_shells)
|
|
to_chat(user, span_warning("[src] is full!"))
|
|
return
|
|
var/count = 0
|
|
for(var/obj/item/ammo_casing/C in AM.stored_ammo)
|
|
if(loaded.len >= max_shells)
|
|
break
|
|
if(C.caliber == caliber)
|
|
C.loc = src
|
|
loaded += C
|
|
AM.stored_ammo -= C //should probably go inside an ammo_magazine proc, but I guess less proc calls this way...
|
|
count++
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
if(do_after(user, reload_time * AM.w_class))
|
|
if(count)
|
|
user.visible_message("[user] reloads [src].", span_notice("You load [count] round\s into [src]."))
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
|
|
AM.update_icon()
|
|
else if(istype(A, /obj/item/ammo_casing))
|
|
var/obj/item/ammo_casing/C = A
|
|
if(!(load_method & SINGLE_CASING) || caliber != C.caliber)
|
|
return //incompatible
|
|
if(loaded.len >= max_shells)
|
|
to_chat(user, span_warning("[src] is full."))
|
|
return
|
|
|
|
if(do_after(user, reload_time * C.w_class))
|
|
user.remove_from_mob(C)
|
|
C.loc = src
|
|
loaded.Insert(1, C) //add to the head of the list
|
|
user.visible_message("[user] inserts \a [C] into [src].", span_notice("You insert \a [C] into [src]."))
|
|
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
|
|
|
|
else if(istype(A, /obj/item/storage))
|
|
var/obj/item/storage/storage = A
|
|
if(!(load_method & SINGLE_CASING))
|
|
return //incompatible
|
|
|
|
to_chat(user, span_notice("You start loading \the [src]."))
|
|
sleep(1 SECOND)
|
|
for(var/obj/item/ammo_casing/ammo in storage.contents)
|
|
if(caliber != ammo.caliber)
|
|
continue
|
|
|
|
load_ammo(ammo, user)
|
|
|
|
if(loaded.len >= max_shells)
|
|
to_chat(user, span_warning("[src] is full."))
|
|
break
|
|
sleep(1 SECOND)
|
|
|
|
update_icon()
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
|
|
//attempts to unload src. If allow_dump is set to 0, the speedloader unloading method will be disabled
|
|
/obj/item/gun/projectile/proc/unload_ammo(mob/user, var/allow_dump=1)
|
|
if(ammo_magazine)
|
|
user.put_in_hands(ammo_magazine)
|
|
user.visible_message("[user] removes [ammo_magazine] from [src].", span_notice("You remove [ammo_magazine] from [src]."))
|
|
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
|
|
ammo_magazine.update_icon()
|
|
ammo_magazine = null
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
else if(loaded.len)
|
|
//presumably, if it can be speed-loaded, it can be speed-unloaded.
|
|
if(allow_dump && (load_method & SPEEDLOADER))
|
|
var/count = 0
|
|
var/turf/T = get_turf(user)
|
|
if(T)
|
|
for(var/obj/item/ammo_casing/C in loaded)
|
|
C.loc = T
|
|
count++
|
|
loaded.Cut()
|
|
if(count)
|
|
user.visible_message("[user] unloads [src].", span_notice("You unload [count] round\s from [src]."))
|
|
else if(load_method & SINGLE_CASING)
|
|
var/obj/item/ammo_casing/C = loaded[loaded.len]
|
|
loaded.len--
|
|
user.put_in_hands(C)
|
|
user.visible_message("[user] removes \a [C] from [src].", span_notice("You remove \a [C] from [src]."))
|
|
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
else
|
|
to_chat(user, span_warning("[src] is empty."))
|
|
update_icon()
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
|
|
/obj/item/gun/projectile/attackby(var/obj/item/A as obj, mob/user as mob)
|
|
..()
|
|
load_ammo(A, user)
|
|
|
|
/obj/item/gun/projectile/attack_self(mob/user as mob)
|
|
if(firemodes.len > 1)
|
|
switch_firemodes(user)
|
|
else
|
|
unload_ammo(user)
|
|
|
|
/obj/item/gun/projectile/attack_hand(mob/user as mob)
|
|
if(user.get_inactive_hand() == src)
|
|
unload_ammo(user, allow_dump=0)
|
|
else
|
|
return ..()
|
|
|
|
/obj/item/gun/projectile/afterattack(atom/A, mob/living/user)
|
|
..()
|
|
if(auto_eject && ammo_magazine && ammo_magazine.stored_ammo && !ammo_magazine.stored_ammo.len)
|
|
ammo_magazine.loc = get_turf(src.loc)
|
|
user.visible_message(
|
|
"[ammo_magazine] falls out and clatters on the floor!",
|
|
span_notice("[ammo_magazine] falls out and clatters on the floor!")
|
|
)
|
|
if(auto_eject_sound)
|
|
playsound(src, auto_eject_sound, 40, 1)
|
|
ammo_magazine.update_icon()
|
|
ammo_magazine = null
|
|
update_icon() //make sure to do this after unsetting ammo_magazine
|
|
user.hud_used.update_ammo_hud(user, src)
|
|
|
|
/obj/item/gun/projectile/examine(mob/user)
|
|
. = ..()
|
|
if(ammo_magazine)
|
|
. += "It has \a [ammo_magazine] loaded."
|
|
. += "It has [getAmmo()] round\s remaining."
|
|
|
|
/obj/item/gun/projectile/proc/getAmmo()
|
|
var/bullets = 0
|
|
if(loaded)
|
|
bullets += loaded.len
|
|
if(ammo_magazine && ammo_magazine.stored_ammo)
|
|
bullets += ammo_magazine.stored_ammo.len
|
|
if(chambered)
|
|
bullets += 1
|
|
return bullets
|
|
|
|
/* Unneeded -- so far.
|
|
//in case the weapon has firemodes and can't unload using attack_hand()
|
|
/obj/item/gun/projectile/verb/unload_gun()
|
|
set name = "Unload Ammo"
|
|
set category = "Object"
|
|
set src in usr
|
|
|
|
if(usr.stat || usr.restrained()) return
|
|
|
|
unload_ammo(usr)
|
|
*/
|
|
|
|
// TGMC Ammo HUD Insertion
|
|
/obj/item/gun/projectile/has_ammo_counter()
|
|
return TRUE
|
|
|
|
/obj/item/gun/projectile/get_ammo_type()
|
|
if(load_method & MAGAZINE)
|
|
if(chambered) // Do we have an ammo casing chambered
|
|
var/obj/item/ammo_casing/A = chambered
|
|
var/obj/item/projectile/P = A.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty))
|
|
else if(ammo_magazine && ammo_magazine.stored_ammo.len) // Do we have a mag, and have ammo in the mag, but nothing chambered?
|
|
var/obj/item/ammo_casing/A = ammo_magazine.stored_ammo[1]
|
|
var/obj/item/projectile/P = A.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty))
|
|
else if(src.projectile_type) // Else, we're entirely empty, and irregardless of the mag we have loaded (as it's empty, or it would've passed the length check above), return the DEFAULT projectile_type on the gun, if set.
|
|
var/obj/item/projectile/P = src.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty))
|
|
else
|
|
return list("unknown", "unknown") // Safety, this shouldn't happen, but just in case
|
|
else if(load_method & (SINGLE_CASING|SPEEDLOADER)) // Do we load with single casings OR speedloaders?
|
|
if(chambered) // Do we have an ammo casing loaded in the chamber? All casings still have a projectile_type var.
|
|
var/obj/item/ammo_casing/A = chambered
|
|
var/obj/item/projectile/P = A.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty)) // Return the casing's projectile_type ammo hud state
|
|
else if(loaded.len) // Else, is the gun loaded, but no ammo casings in chamber currently?
|
|
var/obj/item/ammo_casing/A = loaded[1]
|
|
var/obj/item/projectile/P = A.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty)) // Return the ammunition loaded in the gun's hud_state
|
|
else if(src.projectile_type) // Else, we're entirely empty, and have nothing loaded in the gun, and nothing in the chamber. Return the DEFAULT projectile_type on the gun, if set.
|
|
var/obj/item/projectile/P = src.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty))
|
|
else
|
|
return list("unknown", "unknown") // Safety, this shouldn't happen, but just in case
|
|
else if(src.projectile_type) // Failsafe if we somehow don't pass the above. Return the DEFAULT projectile_type on the gun, if set.
|
|
var/obj/item/projectile/P = src.projectile_type
|
|
return list(initial(P.hud_state), initial(P.hud_state_empty))
|
|
else // Failsafe if we somehow fail all three methods
|
|
return list("unknown", "unknown")
|
|
|
|
/obj/item/gun/projectile/get_ammo_count()
|
|
if(ammo_magazine) // Do we have a magazine loaded?
|
|
var/shots_left
|
|
if(chambered && chambered.BB) // Do we have a bullet in the currently-chambered casing, if any?
|
|
shots_left++
|
|
for(var/obj/item/ammo_casing/bullet in ammo_magazine.stored_ammo)
|
|
if(bullet.BB)
|
|
shots_left++
|
|
|
|
if(shots_left > 0)
|
|
return shots_left
|
|
else
|
|
return 0 // No ammo left or failsafe.
|
|
else if(loaded) // Do we use internal ammunition
|
|
var/shots_left
|
|
if(chambered && chambered.BB) // Do we have a bullet in the currently-chambered casing, if any?
|
|
shots_left++
|
|
for(var/obj/item/ammo_casing/bullet in loaded)
|
|
if(bullet.BB) // Only increment how many shots we have left if we're loaded.
|
|
shots_left++
|
|
|
|
if(shots_left > 0)
|
|
return shots_left
|
|
else
|
|
return 0 // No ammo left or failsafe.
|
|
else if(chambered) // If we don't have a magazine or internal ammunition loaded, but we have a casing in chamber, return the amount.
|
|
return chambered.BB ? 1 : 0
|
|
else // Failsafe, or completely unloaded
|
|
return 0
|