Files
VOREStation/code/modules/projectiles/guns/projectile.dm
T
Cameron Lennox d5849910e5 Begin clickcode attack_self fix (#18797)
* Begin clickcode attack_self fix

Begins the work to make everything call back to parent for attack_self so that signals are sacred.

* Makes MORE things call the attack_self() parent

Yes, I could make special_handling a var on obj/item HOWEVER i want it to be specific so it can be tracked down later and ONLY the objects that use it can be refactored instead of sitting there literally forever and it just becoming 'a thing'.

* Finishes making the rest of attack_self call parent.

As mentioned, things such as 'specialty_goggles' 'special_handling' and the such are only there to help with attack_self until the attack_self is recoded for those items.

* begone foul demon

* some more cleanup

* These

* GOD this was annoying

* yeh

* Fix this

* fLARES

* Thesee too

* toys!

* Even more!

* More fixes

* Even more

* rest of em

* these too

* Update syndie.dm

* hardref clear

* Update code/game/gamemodes/nuclear/pinpointer.dm

* Update code/game/objects/effects/mines.dm

* Update code/game/objects/items/blueprints_vr.dm

* Update code/game/objects/items/blueprints_vr.dm

* Update code/game/objects/items/contraband_vr.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/gunbox.dm

* Update code/game/objects/items/gunbox.dm

* Update code/game/objects/items/gunbox_vr.dm

* Update code/game/objects/items/gunbox_vr.dm

* Update code/game/objects/items/weapons/gift_wrappaper.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/gunbox.dm

* these too

* Update maintpanel_stack.dm

* angry warning

* Fixes packaged snacks.

Fixes improper var default.

* Special handling for these

* proper poly types

* Fixes magclaws

Makes the 'features' it had just part  of base magboots that can be adjusted via varswap.

* Fixes jackets

Fixes https://github.com/VOREStation/VOREStation/issues/18941

* Small bugfix

Makes p_Theyre properly capitialize
Makes examine show proper wording

* Update gift_wrappaper.dm
2025-12-29 13:21:10 -05:00

363 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
special_handling = TRUE
///Var for attack_self chain
var/special_weapon_handling = FALSE
/obj/item/gun/projectile/Initialize(mapload, 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.add_gunshotresidue(chambered)
else
var/obj/item/clothing/G = H.gloves
G.add_gunshotresidue(chambered)
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, target = src))
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, target = src))
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, target = src))
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, callback)
. = ..(user)
if(.)
return TRUE
if(special_weapon_handling && !callback)
return FALSE
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