Files
CHOMPStation2/code/modules/projectiles/guns/projectile.dm
Datraen beda3185eb Reverts the second mob clean up because of incomplete implementation.
This caused an error that would then cause machinery to hang on the server, and was reproduced successfully.
2016-05-04 00:24:55 -04:00

231 lines
8.1 KiB
Plaintext

#define HOLD_CASINGS 0 //do not do anything after firing. Manual action, like pump shotguns, or guns that want to define custom behaviour
#define EJECT_CASINGS 1 //drop spent casings on the ground after firing
#define CYCLE_CASINGS 2 //experimental: cycle casings, like a revolver. Also works for multibarrelled guns
/obj/item/weapon/gun/projectile
name = "gun"
desc = "A gun that fires bullets."
icon_state = "revolver"
origin_tech = list(TECH_COMBAT = 2, TECH_MATERIAL = 2)
w_class = 3
matter = list(DEFAULT_WALL_MATERIAL = 1000)
recoil = 1
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
//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/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
/obj/item/weapon/gun/projectile/New()
..()
if(ispath(ammo_type) && (load_method & (SINGLE_CASING|SPEEDLOADER)))
for(var/i in 1 to max_shells)
loaded += new ammo_type(src)
if(ispath(magazine_type) && (load_method & MAGAZINE))
ammo_magazine = new magazine_type(src)
update_icon()
/obj/item/weapon/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[1]
if(handle_casings != HOLD_CASINGS)
ammo_magazine.stored_ammo -= chambered
if (chambered)
return chambered.BB
return null
/obj/item/weapon/gun/projectile/handle_post_fire()
..()
if(chambered)
chambered.expend()
process_chambered()
/obj/item/weapon/gun/projectile/handle_click_empty()
..()
process_chambered()
/obj/item/weapon/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(!H.gloves)
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.
chambered.loc = get_turf(src)
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
//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/weapon/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)
return //incompatible
switch(AM.mag_type)
if(MAGAZINE)
if(ammo_magazine)
user << "<span class='warning'>[src] already has a magazine loaded.</span>" //already a magazine here
return
user.remove_from_mob(AM)
AM.loc = src
ammo_magazine = AM
user.visible_message("[user] inserts [AM] into [src].", "<span class='notice'>You insert [AM] into [src].</span>")
playsound(src.loc, 'sound/weapons/flipblade.ogg', 50, 1)
if(SPEEDLOADER)
if(loaded.len >= max_shells)
user << "<span class='warning'>[src] is full!</span>"
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++
if(count)
user.visible_message("[user] reloads [src].", "<span class='notice'>You load [count] round\s into [src].</span>")
playsound(src.loc, '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)
user << "<span class='warning'>[src] is full.</span>"
return
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 class='notice'>You insert \a [C] into [src].</span>")
playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1)
update_icon()
//attempts to unload src. If allow_dump is set to 0, the speedloader unloading method will be disabled
/obj/item/weapon/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 class='notice'>You remove [ammo_magazine] from [src].</span>")
playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1)
ammo_magazine.update_icon()
ammo_magazine = null
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 class='notice'>You unload [count] round\s from [src].</span>")
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 class='notice'>You remove \a [C] from [src].</span>")
else
user << "<span class='warning'>[src] is empty.</span>"
update_icon()
/obj/item/weapon/gun/projectile/attackby(var/obj/item/A as obj, mob/user as mob)
load_ammo(A, user)
/obj/item/weapon/gun/projectile/attack_self(mob/user as mob)
if(firemodes.len > 1)
switch_firemodes(user)
else
unload_ammo(user)
/obj/item/weapon/gun/projectile/attack_hand(mob/user as mob)
if(user.get_inactive_hand() == src)
unload_ammo(user, allow_dump=0)
else
return ..()
/obj/item/weapon/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 class='notice'>[ammo_magazine] falls out and clatters on the floor!</span>"
)
if(auto_eject_sound)
playsound(user, auto_eject_sound, 40, 1)
ammo_magazine.update_icon()
ammo_magazine = null
update_icon() //make sure to do this after unsetting ammo_magazine
/obj/item/weapon/gun/projectile/examine(mob/user)
..(user)
if(ammo_magazine)
user << "It has \a [ammo_magazine] loaded."
user << "Has [getAmmo()] round\s remaining."
return
/obj/item/weapon/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/weapon/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)
*/