-
Notifications
You must be signed in to change notification settings - Fork 555
/
gun.dm
1981 lines (1660 loc) · 80.6 KB
/
gun.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#define CODEX_ARMOR_MAX 50
#define CODEX_ARMOR_STEP 5
/obj/item/weapon/gun
name = "gun"
desc = "It's a gun. It's pretty terrible, though."
icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
icon_state = ""
item_state = "gun"
pickup_sound = "gunequip"
drop_sound = "gunrustle"
pickupvol = 7
dropvol = 15
matter = null
//Guns generally have their own unique levels.
w_class = SIZE_MEDIUM
throwforce = 5
throw_speed = SPEED_VERY_FAST
throw_range = 5
force = 5
attack_verb = null
item_icons = list(
WEAR_L_HAND = 'icons/mob/humans/onmob/items_lefthand_1.dmi',
WEAR_R_HAND = 'icons/mob/humans/onmob/items_righthand_1.dmi'
)
flags_atom = FPRINT|QUICK_DRAWABLE|CONDUCT
flags_item = TWOHANDED
light_system = DIRECTIONAL_LIGHT
var/accepted_ammo = list()
///Determines what kind of bullet is created when the gun is unloaded - used to match rounds to magazines. Set automatically when reloading.
var/caliber
var/muzzle_flash = "muzzle_flash"
///muzzle flash brightness
var/muzzle_flash_lum = 3
var/fire_sound = 'sound/weapons/Gunshot.ogg'
/// If fire_sound is null, it will pick a sound from the list here instead.
var/list/fire_sounds = list('sound/weapons/Gunshot.ogg', 'sound/weapons/gun_uzi.ogg')
var/firesound_volume = 60 //Volume of gunshot, adjust depending on volume of shot
///Does our gun have a unique empty mag sound? If so use instead of pitch shifting.
var/fire_rattle = null
var/unload_sound = 'sound/weapons/flipblade.ogg'
var/empty_sound = 'sound/weapons/smg_empty_alarm.ogg'
//We don't want these for guns that don't have them.
var/reload_sound = null
var/cocked_sound = null
///world.time value, to prevent COCK COCK COCK COCK
var/cock_cooldown = 0
///Delay before we can cock again, in tenths of seconds
var/cock_delay = 30
/**How the bullet will behave once it leaves the gun, also used for basic bullet damage and effects, etc.
Ammo will be replaced on New() for things that do not use mags.**/
var/datum/ammo/ammo = null
///What is currently in the chamber. Most guns will want something in the chamber upon creation.
var/obj/projectile/in_chamber = null
/*Ammo mags may or may not be internal, though the difference is a few additional variables. If they are not internal, don't call
on those unique vars. This is done for quicker pathing. Just keep in mind most mags aren't internal, though some are.
This is also the default magazine path loaded into a projectile weapon for reverse lookups on New(). Leave this null to do your own thing.*/
var/obj/item/ammo_magazine/internal/current_mag = null
//Basic stats.
///Multiplier. Increased and decreased through attachments. Multiplies the projectile's accuracy by this number.
var/accuracy_mult = 0
///Multiplier. Increased and decreased through attachments. Multiplies the projectile's damage by this number.
var/damage_mult = 1
///Multiplier. Increased and decreased through attachments. Multiplies the projectile's damage bleed (falloff) by this number.
var/damage_falloff_mult = 1
///Multiplier. Increased and decreased through attachments. Multiplies the projectile's damage bleed (buildup) by this number.
var/damage_buildup_mult = 1
///Screen shake when the weapon is fired.
var/recoil = 0
///How much the bullet scatters when fired.
var/scatter = 0
/// Added velocity to fired bullet.
var/velocity_add = 0
///Multiplier. Increases or decreases how much bonus scatter is added with each bullet during burst fire (wielded only).
var/burst_scatter_mult = 4
///What minimum range the weapon deals full damage, builds up the closer you get. 0 for no minimum.
var/effective_range_min = 0
///What maximum range the weapon deals full damage, tapers off using damage_falloff after hitting this value. 0 for no maximum.
var/effective_range_max = 0
///Multiplier. Increased and decreased through attachments. Multiplies the projectile's accuracy when unwielded by this number.
var/accuracy_mult_unwielded = 1
///Multiplier. Increased and decreased through attachments. Multiplies the gun's recoil when unwielded by this number.
var/recoil_unwielded = 0
///Multiplier. Increased and decreased through attachments. Multiplies the projectile's scatter when the gun is unwielded by this number.
var/scatter_unwielded = 0
///Multiplier. Increased and decreased through attachments. Multiplies the accuracy/scatter penalty of the projectile when firing onehanded while moving.
var/movement_onehanded_acc_penalty_mult = 5 //Multiplier. Increased and decreased through attachments. Multiplies the accuracy/scatter penalty of the projectile when firing onehanded while moving.
///For regular shots, how long to wait before firing again. Use modify_fire_delay and set_fire_delay instead of modifying this on the fly
VAR_PROTECTED/fire_delay = 0
///When it was last fired, related to world.time.
var/last_fired = 0
///Self explanatory. How much does aiming (wielding the gun) slow you
var/aim_slowdown = 0
///How long between wielding and firing in tenths of seconds
var/wield_delay = WIELD_DELAY_FAST
///Storing value for wield delay.
var/wield_time = 0
///Storing value for guaranteed delay
var/guaranteed_delay_time = 0
///Storing value for how long pulling a gun takes before you can use it
var/pull_time = 0
///Determines what happens when you fire a gun before its wield or pull time has finished. This one is extra scatter and an acc. malus.
var/delay_style = WEAPON_DELAY_SCATTER_AND_ACCURACY
//Burst fire.
///How many shots can the weapon shoot in burst? Anything less than 2 and you cannot toggle burst. Use modify_burst_amount and set_burst_amount instead of modifying this
VAR_PROTECTED/burst_amount = 1
///The delay in between shots. Lower = less delay = faster. Use modify_burst_delay and set_burst_delay instead of modifying this
VAR_PROTECTED/burst_delay = 1
///When burst-firing, this number is extra time before the weapon can fire again. Depends on number of rounds fired.
var/extra_delay = 0
///When PB burst firing and handing off to /fire after a target moves out of range, this is how many bullets have been fired.
var/PB_burst_bullets_fired = 0
// Full auto
///Whether or not the gun is firing full-auto
var/fa_firing = FALSE
///How many full-auto shots to get to max scatter?
var/fa_scatter_peak = 4
///How bad does the scatter get on full auto?
var/fa_max_scatter = 6.5
///Click parameters to use when firing full-auto
var/fa_params = null
///Used to fire faster at more than one person.
var/tmp/mob/living/last_moved_mob
var/tmp/lock_time = -100
///Used to determine if you can target multiple people.
var/automatic = 0
///So that it doesn't spam them with the fact they cannot hit them.
var/tmp/told_cant_shoot = 0
//Attachments.
///List of all current attachments on the gun.
var/list/attachments = list()
///List of overlays so we can switch them in an out, instead of using Cut() on overlays.
var/attachable_overlays[] = null
///Is a list, see examples of from the other files. Initiated on New() because lists don't initial() properly.
var/attachable_offset[] = null
///Must be the exact path to the attachment present in the list. Empty list for a default.
var/list/attachable_allowed = list()
///Chance for random attachments to spawn in general.
var/random_spawn_chance = 50
///Chance for random spawn to give this gun a rail attachment.
var/random_rail_chance = 100
//Used when a gun will have a chance to spawn with attachments.
var/list/random_spawn_rail = list()
///Chance for random spawn to give this gun a muzzle attachment.
var/random_muzzle_chance = 100
///Used when a gun will have a chance to spawn with attachments.
var/list/random_spawn_muzzle = list()
///Chance for random spawn to give this gun a underbarrel attachment.
var/random_under_chance = 100
///Used when a gun will have a chance to spawn with attachments.
var/list/random_spawn_under = list()
///Chance for random spawn to give this gun a stock attachment.
var/random_stock_chance = 100
///Used when a gun will have a chance to spawn with attachments.
var/list/random_spawn_stock = list()
///This will link to one of the attachments, or remain null.
var/obj/item/attachable/attached_gun/active_attachable = null
///What attachments this gun starts with THAT CAN BE REMOVED. Important to avoid nuking the attachments on restocking! Added on New()
var/list/starting_attachment_types = null
var/flags_gun_features = GUN_AUTO_EJECTOR|GUN_CAN_POINTBLANK
///Only guns of the same category can be fired together while dualwielding.
var/gun_category
///the default gun icon_state. change to reskin the gun
var/base_gun_icon
/// whether gun has icon state of (base_gun_icon)_e
var/has_empty_icon = TRUE
/// whether gun has icon state of (base_gun_icon)_o
var/has_open_icon = FALSE
var/bonus_overlay_x = 0
var/bonus_overlay_y = 0
/// How much recoil_buildup is lost per second. Builds up as time passes, and is set to 0 when a single shot is fired
var/recoil_loss_per_second = 10
/// The recoil on a dynamic recoil gun
var/recoil_buildup = 0
///The limit at which the recoil on a gun can reach. Usually the maximum value
var/recoil_buildup_limit = 0
var/last_recoil_update = 0
var/auto_retrieval_slot
/** An assoc list in the format list(/datum/element/bullet_trait_to_give = list(...args))
that will be given to a projectile with the current ammo datum**/
var/list/list/traits_to_give
/**
* The group or groups of the gun where a fire delay is applied and the delays applied to each group when the gun is dropped
* after being fired
*
* Guns with this var set will apply the gun's remaining fire delay to any other guns in the same group
*
* Set as null (does not apply any fire delays to any other gun group) or a list of fire delay groups (string defines)
* matched with the corresponding fire delays applied
*/
var/list/fire_delay_group
var/additional_fire_group_delay = 0 // adds onto the fire delay of the above
// Set to TRUE or FALSE, it overrides the is_civilian_usable check with its value. Does nothing if null.
var/civilian_usable_override = null
///Current selected firemode of the gun.
var/gun_firemode = GUN_FIREMODE_SEMIAUTO
///List of allowed firemodes.
var/list/gun_firemode_list = list()
///How many bullets the gun fired while bursting/auto firing
var/shots_fired = 0
/// Currently selected target to fire at. Set with set_target()
VAR_PRIVATE/atom/target
/// Current user (holding) of the gun. Set with set_gun_user()
VAR_PRIVATE/mob/gun_user
/// If this gun should spawn with semi-automatic fire. Protected due to it never needing to be edited.
VAR_PROTECTED/start_semiauto = TRUE
/// If this gun should spawn with automatic fire. Protected due to it never needing to be edited.
VAR_PROTECTED/start_automatic = FALSE
/// The type of projectile that this gun should shoot
var/projectile_type = /obj/projectile
/// The multiplier for how much slower this should fire in automatic mode. 1 is normal, 1.2 is 20% slower, 2 is 100% slower, etc. Protected due to it never needing to be edited.
VAR_PROTECTED/autofire_slow_mult = 1
/**
* An assoc list where the keys are fire delay group string defines
* and the keys are when the guns of the group can be fired again
*/
/mob/var/list/fire_delay_next_fire
//----------------------------------------------------------
// \\
// NECESSARY PROCS \\
// \\
// \\
//----------------------------------------------------------
/obj/item/weapon/gun/Initialize(mapload, spawn_empty) //You can pass on spawn_empty to make the sure the gun has no bullets or mag or anything when created.
. = ..() //This only affects guns you can get from vendors for now. Special guns spawn with their own things regardless.
base_gun_icon = icon_state
attachable_overlays = list("muzzle" = null, "rail" = null, "under" = null, "stock" = null, "mag" = null, "special" = null)
LAZYSET(item_state_slots, WEAR_BACK, item_state)
LAZYSET(item_state_slots, WEAR_JACKET, item_state)
if(current_mag)
if(spawn_empty && !(flags_gun_features & GUN_INTERNAL_MAG)) //Internal mags will still spawn, but they won't be filled.
current_mag = null
update_icon()
else
current_mag = new current_mag(src, spawn_empty? 1:0)
replace_ammo(null, current_mag)
else
ammo = GLOB.ammo_list[ammo] //If they don't have a mag, they fire off their own thing.
set_gun_attachment_offsets()
set_gun_config_values()
set_bullet_traits()
setup_firemodes()
update_force_list() //This gives the gun some unique attack verbs for attacking.
handle_starting_attachment()
handle_random_attachments()
GLOB.gun_list += src
if(auto_retrieval_slot)
AddElement(/datum/element/drop_retrieval/gun, auto_retrieval_slot)
update_icon() //for things like magazine overlays
gun_firemode = gun_firemode_list[1] || GUN_FIREMODE_SEMIAUTO
AddComponent(/datum/component/automatedfire/autofire, fire_delay, burst_delay, burst_amount, gun_firemode, autofire_slow_mult, CALLBACK(src, PROC_REF(set_bursting)), CALLBACK(src, PROC_REF(reset_fire)), CALLBACK(src, PROC_REF(fire_wrapper)), CALLBACK(src, PROC_REF(display_ammo)), CALLBACK(src, PROC_REF(set_auto_firing))) //This should go after handle_starting_attachment() and setup_firemodes() to get the proper values set.
/obj/item/weapon/gun/proc/set_gun_attachment_offsets()
attachable_offset = null
/obj/item/weapon/gun/Destroy()
in_chamber = null
ammo = null
QDEL_NULL(current_mag)
target = null
last_moved_mob = null
if(flags_gun_features & GUN_FLASHLIGHT_ON)//Handle flashlight.
flags_gun_features &= ~GUN_FLASHLIGHT_ON
attachments = null
attachable_overlays = null
QDEL_NULL(active_attachable)
GLOB.gun_list -= src
set_gun_user(null)
. = ..()
/*
* Called by the gun's New(), set the gun variables' values.
* Each gun gets its own version of the proc instead of adding/substracting
* amounts to get specific values in each gun subtype's New().
* This makes reading each gun's values MUCH easier.
*/
/obj/item/weapon/gun/proc/set_gun_config_values()
set_fire_delay(FIRE_DELAY_TIER_5)
accuracy_mult = BASE_ACCURACY_MULT
accuracy_mult_unwielded = BASE_ACCURACY_MULT
scatter = SCATTER_AMOUNT_TIER_6
burst_scatter_mult = SCATTER_AMOUNT_TIER_7
set_burst_amount(BURST_AMOUNT_TIER_1)
scatter_unwielded = SCATTER_AMOUNT_TIER_6
damage_mult = BASE_BULLET_DAMAGE_MULT
damage_falloff_mult = DAMAGE_FALLOFF_TIER_10
damage_buildup_mult = DAMAGE_BUILDUP_TIER_1
velocity_add = BASE_VELOCITY_BONUS
recoil = RECOIL_OFF
recoil_unwielded = RECOIL_OFF
movement_onehanded_acc_penalty_mult = MOVEMENT_ACCURACY_PENALTY_MULT_TIER_1
effective_range_min = EFFECTIVE_RANGE_OFF
effective_range_max = EFFECTIVE_RANGE_OFF
recoil_buildup_limit = RECOIL_AMOUNT_TIER_1 / RECOIL_BUILDUP_VIEWPUNCH_MULTIPLIER
//reset initial define-values
aim_slowdown = initial(aim_slowdown)
wield_delay = initial(wield_delay)
/// Populate traits_to_give in this proc
/obj/item/weapon/gun/proc/set_bullet_traits()
return
/// @bullet_trait_entries: A list of bullet trait entries
/obj/item/weapon/gun/proc/add_bullet_traits(list/list/bullet_trait_entries)
LAZYADD(traits_to_give, bullet_trait_entries)
for(var/entry in bullet_trait_entries)
if(!in_chamber)
break
var/list/L
// Check if this is an ID'd bullet trait
if(istext(entry))
L = bullet_trait_entries[entry].Copy()
else
// Prepend the bullet trait to the list
L = list(entry) + bullet_trait_entries[entry]
// Apply bullet traits from gun to current projectile
in_chamber.apply_bullet_trait(L)
/// @bullet_traits: A list of bullet trait typepaths or ids
/obj/item/weapon/gun/proc/remove_bullet_traits(list/bullet_traits)
for(var/entry in bullet_traits)
if(!LAZYISIN(traits_to_give, entry))
continue
var/list/L
if(istext(entry))
L = traits_to_give[entry].Copy()
else
L = list(entry) + traits_to_give[entry]
LAZYREMOVE(traits_to_give, entry)
if(in_chamber)
// Remove bullet traits of gun from current projectile
// Need to use the proc instead of the wrapper because each entry is a list
in_chamber._RemoveElement(L)
/obj/item/weapon/gun/proc/recalculate_attachment_bonuses()
//reset weight and force mods
force = initial(force)
w_class = initial(w_class)
//reset HUD and pixel offsets
hud_offset = initial(hud_offset)
pixel_x = initial(hud_offset)
//reset traits from attachments
for(var/slot in attachments)
REMOVE_TRAITS_IN(src, TRAIT_SOURCE_ATTACHMENT(slot))
//Get default gun config values
set_gun_config_values()
//Add attachment bonuses
for(var/slot in attachments)
var/obj/item/attachable/R = attachments[slot]
if(!R)
continue
modify_fire_delay(R.delay_mod)
accuracy_mult += R.accuracy_mod
accuracy_mult_unwielded += R.accuracy_unwielded_mod
scatter += R.scatter_mod
scatter_unwielded += R.scatter_unwielded_mod
damage_mult += R.damage_mod
velocity_add += R.velocity_mod
damage_falloff_mult += R.damage_falloff_mod
damage_buildup_mult += R.damage_buildup_mod
effective_range_min += R.range_min_mod
effective_range_max += R.range_max_mod
recoil += R.recoil_mod
burst_scatter_mult += R.burst_scatter_mod
modify_burst_amount(R.burst_mod)
recoil_unwielded += R.recoil_unwielded_mod
aim_slowdown += R.aim_speed_mod
wield_delay += R.wield_delay_mod
movement_onehanded_acc_penalty_mult += R.movement_onehanded_acc_penalty_mod
force += R.melee_mod
w_class += R.size_mod
if(!R.hidden)
hud_offset += R.hud_offset_mod
pixel_x += R.hud_offset_mod
for(var/trait in R.gun_traits)
ADD_TRAIT(src, trait, TRAIT_SOURCE_ATTACHMENT(slot))
//Refresh location in HUD.
if(ishuman(loc))
var/mob/living/carbon/human/M = loc
if(M.l_hand == src)
M.update_inv_l_hand()
else if(M.r_hand == src)
M.update_inv_r_hand()
setup_firemodes()
SEND_SIGNAL(src, COMSIG_GUN_RECALCULATE_ATTACHMENT_BONUSES)
/obj/item/weapon/gun/proc/handle_random_attachments()
#ifdef AUTOWIKI // no randomness for my gun pictures, please
return
#endif
var/attachmentchoice
var/randchance = random_spawn_chance
if(!prob(randchance))
return
var/railchance = random_rail_chance
if(prob(railchance) && !attachments["rail"]) // Rail
attachmentchoice = SAFEPICK(random_spawn_rail)
if(attachmentchoice)
var/obj/item/attachable/R = new attachmentchoice(src)
R.Attach(src)
update_attachable(R.slot)
attachmentchoice = FALSE
var/muzzlechance = random_muzzle_chance
if(prob(muzzlechance) && !attachments["muzzle"]) // Muzzle
attachmentchoice = SAFEPICK(random_spawn_muzzle)
if(attachmentchoice)
var/obj/item/attachable/M = new attachmentchoice(src)
M.Attach(src)
update_attachable(M.slot)
attachmentchoice = FALSE
var/underchance = random_under_chance
if(prob(underchance) && !attachments["under"]) // Underbarrel
attachmentchoice = SAFEPICK(random_spawn_under)
if(attachmentchoice)
var/obj/item/attachable/U = new attachmentchoice(src)
U.Attach(src)
update_attachable(U.slot)
attachmentchoice = FALSE
var/stockchance = random_stock_chance
if(prob(stockchance) && !attachments["stock"]) // Stock
attachmentchoice = SAFEPICK(random_spawn_stock)
if(attachmentchoice)
var/obj/item/attachable/S = new attachmentchoice(src)
S.Attach(src)
update_attachable(S.slot)
attachmentchoice = FALSE
/obj/item/weapon/gun/proc/handle_starting_attachment()
if(LAZYLEN(starting_attachment_types))
for(var/path in starting_attachment_types)
var/obj/item/attachable/A = new path(src)
A.Attach(src)
update_attachable(A.slot)
/obj/item/weapon/gun/emp_act(severity)
. = ..()
for(var/obj/O in contents)
O.emp_act(severity)
/*
Note: pickup and dropped on weapons must have both the ..() to update zoom AND twohanded,
As sniper rifles have both and weapon mods can change them as well. ..() deals with zoom only.
*/
/obj/item/weapon/gun/equipped(mob/living/user, slot)
if(flags_item & NODROP) return
unwield(user)
pull_time = world.time + wield_delay
if(HAS_TRAIT(user, TRAIT_DAZED))
pull_time += 3
guaranteed_delay_time = world.time + WEAPON_GUARANTEED_DELAY
var/delay_left = (last_fired + fire_delay + additional_fire_group_delay) - world.time
if(fire_delay_group && delay_left > 0)
LAZYSET(user.fire_delay_next_fire, src, world.time + delay_left)
if(slot in list(WEAR_L_HAND, WEAR_R_HAND))
set_gun_user(user)
if(HAS_TRAIT_FROM_ONLY(src, TRAIT_GUN_LIGHT_FORCE_DEACTIVATED, WEAKREF(user)))
force_light(on = TRUE)
REMOVE_TRAIT(src, TRAIT_GUN_LIGHT_FORCE_DEACTIVATED, WEAKREF(user))
else
set_gun_user(null)
// we force the light off and turn it back on again when the gun is equipped. Otherwise bad things happen.
if(light_sources())
force_light(on = FALSE)
ADD_TRAIT(src, TRAIT_GUN_LIGHT_FORCE_DEACTIVATED, WEAKREF(user))
return ..()
/obj/item/weapon/gun/dropped(mob/user)
. = ..()
var/delay_left = (last_fired + fire_delay + additional_fire_group_delay) - world.time
if(fire_delay_group && delay_left > 0)
LAZYSET(user.fire_delay_next_fire, src, world.time + delay_left)
for(var/obj/item/attachable/stock/smg/collapsible/brace/current_stock in contents) //SMG armbrace folds to stop it getting stuck on people
if(current_stock.stock_activated)
current_stock.activate_attachment(src, user, turn_off = TRUE)
unwield(user)
set_gun_user(null)
/obj/item/weapon/gun/update_icon()
if(overlays)
overlays.Cut()
else
overlays = list()
..()
if(blood_overlay) //need to reapply bloodstain because of the Cut.
overlays += blood_overlay
var/new_icon_state = base_gun_icon
if(has_empty_icon && (!current_mag || current_mag.current_rounds <= 0))
new_icon_state += "_e"
if(has_open_icon && (!current_mag || !current_mag.chamber_closed))
new_icon_state += "_o"
icon_state = new_icon_state
update_mag_overlay()
update_attachables()
/obj/item/weapon/gun/get_examine_text(mob/user)
. = ..()
if(flags_gun_features & GUN_NO_DESCRIPTION)
return .
var/dat = ""
if(flags_gun_features & GUN_TRIGGER_SAFETY)
dat += "The safety's on!<br>"
else
dat += "The safety's off!<br>"
for(var/slot in attachments)
var/obj/item/attachable/R = attachments[slot]
if(!R) continue
dat += R.handle_attachment_description()
if(!(flags_gun_features & (GUN_INTERNAL_MAG|GUN_UNUSUAL_DESIGN))) //Internal mags and unusual guns have their own stuff set.
if(current_mag && current_mag.current_rounds > 0)
if(flags_gun_features & GUN_AMMO_COUNTER) dat += "Ammo counter shows [current_mag.current_rounds] round\s remaining.<br>"
else dat += "It's loaded[in_chamber?" and has a round chambered":""].<br>"
else dat += "It's unloaded[in_chamber?" but has a round chambered":""].<br>"
if(!(flags_gun_features & GUN_UNUSUAL_DESIGN))
dat += "<a href='?src=\ref[src];list_stats=1'>\[See combat statistics]</a>"
if(dat)
. += dat
/obj/item/weapon/gun/Topic(href, href_list)
. = ..()
if(.)
return
if(!ishuman(usr) && !isobserver(usr))
return
if(href_list["list_stats"]&& !(flags_gun_features & GUN_UNUSUAL_DESIGN))
tgui_interact(usr)
// TGUI GOES HERE \\
/obj/item/weapon/gun/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "WeaponStats", name)
ui.open()
/obj/item/weapon/gun/ui_state(mob/user)
return GLOB.always_state // can't interact why should I care
/obj/item/weapon/gun/ui_data(mob/user)
var/list/data = list()
var/ammo_name = "bullet"
var/damage = 0
var/bonus_projectile_amount = 0
var/falloff = 0
var/gun_recoil = src.recoil
if(flags_gun_features & GUN_RECOIL_BUILDUP)
update_recoil_buildup() // Need to update recoil values
gun_recoil = recoil_buildup
var/penetration = 0
var/armor_punch = 0
var/accuracy = 0
var/min_accuracy = 0
var/max_range = 0
var/scatter = 0
var/list/damage_armor_profile_xeno = list()
var/list/damage_armor_profile_marine = list()
var/list/damage_armor_profile_armorbreak = list()
var/list/damage_armor_profile_headers = list()
var/datum/ammo/in_ammo
if(in_chamber && in_chamber.ammo)
in_ammo = in_chamber.ammo
else if(current_mag && current_mag.current_rounds > 0)
if(istype(current_mag) && length(current_mag.chamber_contents) && current_mag.chamber_contents[current_mag.chamber_position] != "empty")
in_ammo = GLOB.ammo_list[current_mag.chamber_contents[current_mag.chamber_position]]
if(!istype(in_ammo))
in_ammo = GLOB.ammo_list[current_mag.default_ammo]
else if(!istype(current_mag) && ammo)
in_ammo = ammo
var/has_ammo = istype(in_ammo)
if(has_ammo)
ammo_name = in_ammo.name
damage = in_ammo.damage * damage_mult
bonus_projectile_amount = in_ammo.bonus_projectiles_amount
falloff = in_ammo.damage_falloff * damage_falloff_mult
penetration = in_ammo.penetration
armor_punch = in_ammo.damage_armor_punch
accuracy = in_ammo.accurate_range
min_accuracy = in_ammo.accurate_range_min
max_range = in_ammo.max_range
scatter = in_ammo.scatter
for(var/i = 0; i<=CODEX_ARMOR_MAX; i+=CODEX_ARMOR_STEP)
damage_armor_profile_headers.Add(i)
damage_armor_profile_marine.Add(floor(armor_damage_reduction(GLOB.marine_ranged_stats, damage, i, penetration)))
damage_armor_profile_xeno.Add(floor(armor_damage_reduction(GLOB.xeno_ranged_stats, damage, i, penetration)))
if(!GLOB.xeno_general.armor_ignore_integrity)
if(i != 0)
damage_armor_profile_armorbreak.Add("[floor(armor_break_calculation(GLOB.xeno_ranged_stats, damage, i, penetration, in_ammo.pen_armor_punch, armor_punch)/i)]%")
else
damage_armor_profile_armorbreak.Add("N/A")
var/rpm = max(fire_delay, 1)
var/burst_rpm = max((fire_delay * 1.5 + (burst_amount - 1) * burst_delay)/max(burst_amount, 1), 0.0001)
// weapon info
data["icon"] = base_gun_icon
data["name"] = name
data["desc"] = desc
data["two_handed_only"] = (flags_gun_features & GUN_WIELDED_FIRING_ONLY)
data["recoil"] = max(gun_recoil, 0.1)
data["unwielded_recoil"] = max(recoil_unwielded, 0.1)
data["firerate"] = floor(1 MINUTES / rpm) // 3 minutes so that the values look greater than they actually are
data["burst_firerate"] = floor(1 MINUTES / burst_rpm)
data["firerate_second"] = round(1 SECONDS / rpm, 0.01)
data["burst_firerate_second"] = round(1 SECONDS / burst_rpm, 0.01)
data["scatter"] = max(0.1, scatter + src.scatter)
data["unwielded_scatter"] = max(0.1, scatter + scatter_unwielded)
data["burst_scatter"] = src.burst_scatter_mult
data["burst_amount"] = burst_amount
// ammo info
data["has_ammo"] = has_ammo
data["ammo_name"] = ammo_name
data["damage"] = damage
data["falloff"] = falloff
data["total_projectile_amount"] = bonus_projectile_amount+1
data["armor_punch"] = armor_punch
data["penetration"] = penetration
data["accuracy"] = accuracy * accuracy_mult
data["unwielded_accuracy"] = accuracy * accuracy_mult_unwielded
data["min_accuracy"] = min_accuracy
data["max_range"] = max_range
// damage table data
data["damage_armor_profile_headers"] = damage_armor_profile_headers
data["damage_armor_profile_marine"] = damage_armor_profile_marine
data["damage_armor_profile_xeno"] = damage_armor_profile_xeno
data["damage_armor_profile_armorbreak"] = damage_armor_profile_armorbreak
return data
/obj/item/weapon/gun/ui_static_data(mob/user)
var/list/data = list()
// consts (maxes)
data["recoil_max"] = RECOIL_AMOUNT_TIER_1
data["scatter_max"] = SCATTER_AMOUNT_TIER_1
data["firerate_max"] = 1 MINUTES / FIRE_DELAY_TIER_12
data["damage_max"] = 100
data["accuracy_max"] = 32
data["range_max"] = 32
data["falloff_max"] = DAMAGE_FALLOFF_TIER_1
data["penetration_max"] = ARMOR_PENETRATION_TIER_10
data["punch_max"] = 5
data["glob_armourbreak"] = GLOB.xeno_general.armor_ignore_integrity
data["automatic"] = (GUN_FIREMODE_AUTOMATIC in gun_firemode_list)
data["auto_only"] = ((length(gun_firemode_list) == 1) && (GUN_FIREMODE_AUTOMATIC in gun_firemode_list))
return data
/obj/item/weapon/gun/ui_assets(mob/user)
. = ..() || list()
. += get_asset_datum(/datum/asset/spritesheet/gun_lineart_modes)
. += get_asset_datum(/datum/asset/spritesheet/gun_lineart)
// END TGUI \\
/obj/item/weapon/gun/wield(mob/living/user)
if(!(flags_item & TWOHANDED) || flags_item & WIELDED)
return
if(world.time < pull_time) //Need to wait until it's pulled out to aim
return
var/obj/item/I = user.get_inactive_hand()
if(I)
if(!user.drop_inv_item_on_ground(I))
return
if(ishuman(user))
var/check_hand = user.r_hand == src ? "l_hand" : "r_hand"
var/mob/living/carbon/human/wielder = user
var/obj/limb/hand = wielder.get_limb(check_hand)
if(!istype(hand) || !hand.is_usable())
to_chat(user, SPAN_WARNING("Your other hand can't hold \the [src]!"))
return
flags_item ^= WIELDED
name += " (Wielded)"
item_state += "_w"
slowdown = initial(slowdown) + aim_slowdown
place_offhand(user, initial(name))
wield_time = world.time + wield_delay
if(HAS_TRAIT(user, TRAIT_DAZED))
wield_time += 5
guaranteed_delay_time = world.time + WEAPON_GUARANTEED_DELAY
//slower or faster wield delay depending on skill.
if(user.skills)
if(user.skills.get_skill_level(SKILL_FIREARMS) == SKILL_FIREARMS_CIVILIAN && !is_civilian_usable(user))
wield_time += 3
else
wield_time -= 2*user.skills.get_skill_level(SKILL_FIREARMS)
return 1
/obj/item/weapon/gun/unwield(mob/user)
. = ..()
if(.)
slowdown = initial(slowdown)
//----------------------------------------------------------
// \\
// LOADING, RELOADING, AND CASINGS \\
// \\
// \\
//----------------------------------------------------------
/obj/item/weapon/gun/proc/replace_ammo(mob/user = null, obj/item/ammo_magazine/magazine)
if(!magazine.default_ammo)
to_chat(user, "Something went horribly wrong. Ahelp the following: ERROR CODE A1: null ammo while reloading.")
log_debug("ERROR CODE A1: null ammo while reloading. User: <b>[user]</b> Weapon: <b>[src]</b> Magazine: <b>[magazine]</b>")
ammo = GLOB.ammo_list[/datum/ammo/bullet] //Looks like we're defaulting it.
else
ammo = GLOB.ammo_list[magazine.default_ammo]
if(!magazine.caliber)
to_chat(user, "Something went horribly wrong. Ahelp the following: ERROR CODE A2: null calibre while reloading.")
log_debug("ERROR CODE A2: null calibre while reloading. User: <b>[user]</b> Weapon: <b>[src]</b> Magazine: <b>[magazine]</b>")
caliber = "bugged calibre"
else
caliber = magazine.caliber
//Hardcoded and horrible
/obj/item/weapon/gun/proc/cock_gun(mob/user)
set waitfor = 0
if(cocked_sound)
addtimer(CALLBACK(src, PROC_REF(cock_sound), user), 0.5 SECONDS)
/obj/item/weapon/gun/proc/cock_sound(mob/user)
if(user && loc)
playsound(user, cocked_sound, 25, TRUE)
/*
Reload a gun using a magazine.
This sets all the initial datum's stuff. The bullet does the rest.
User can be passed as null, (a gun reloading itself for instance), so we need to watch for that constantly.
*/
/obj/item/weapon/gun/proc/reload(mob/user, obj/item/ammo_magazine/magazine) //override for guns who use more special mags.
if(flags_gun_features & (GUN_BURST_FIRING|GUN_UNUSUAL_DESIGN|GUN_INTERNAL_MAG))
return
if(!magazine || !istype(magazine))
to_chat(user, SPAN_WARNING("That's not a magazine!"))
return
if(magazine.flags_magazine & AMMUNITION_HANDFUL)
to_chat(user, SPAN_WARNING("[src] needs an actual magazine."))
return
if(magazine.current_rounds <= 0)
to_chat(user, SPAN_WARNING("[magazine] is empty!"))
return
if(!istype(src, magazine.gun_type) && !((magazine.type) in src.accepted_ammo))
to_chat(user, SPAN_WARNING("That magazine doesn't fit in there!"))
return
if(current_mag)
to_chat(user, SPAN_WARNING("It's still got something loaded."))
return
if(user)
if(magazine.reload_delay > 1)
to_chat(user, SPAN_NOTICE("You begin reloading [src]. Hold still..."))
if(!do_after(user, magazine.reload_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
to_chat(user, SPAN_WARNING("Your reload was interrupted!"))
return
replace_magazine(user, magazine)
SEND_SIGNAL(user, COMSIG_MOB_RELOADED_GUN, src)
else
current_mag = magazine
magazine.forceMove(src)
replace_ammo(,magazine)
if(!in_chamber) load_into_chamber()
update_icon()
return TRUE
/obj/item/weapon/gun/proc/replace_magazine(mob/user, obj/item/ammo_magazine/magazine)
user.drop_inv_item_to_loc(magazine, src) //Click!
current_mag = magazine
replace_ammo(user,magazine)
if(!in_chamber)
ready_in_chamber()
cock_gun(user)
user.visible_message(SPAN_NOTICE("[user] loads [magazine] into [src]!"),
SPAN_NOTICE("You load [magazine] into [src]!"), null, 3, CHAT_TYPE_COMBAT_ACTION)
if(reload_sound)
playsound(user, reload_sound, 25, 1, 5)
//Drop out the magazine. Keep the ammo type for next time so we don't need to replace it every time.
//This can be passed with a null user, so we need to check for that as well.
/obj/item/weapon/gun/proc/unload(mob/user, reload_override = 0, drop_override = 0, loc_override = 0) //Override for reloading mags after shooting, so it doesn't interrupt burst. Drop is for dropping the magazine on the ground.
if(!reload_override && (flags_gun_features & (GUN_BURST_FIRING|GUN_UNUSUAL_DESIGN|GUN_INTERNAL_MAG)))
return
if(!current_mag || QDELETED(current_mag) || (current_mag.loc != src && !loc_override))
cock(user)
current_mag = null
update_icon()
return
if(drop_override || !user) //If we want to drop it on the ground or there's no user.
current_mag.forceMove(get_turf(src))//Drop it on the ground.
else
user.put_in_hands(current_mag)
playsound(user, unload_sound, 25, 1, 5)
user.visible_message(SPAN_NOTICE("[user] unloads [current_mag] from [src]."),
SPAN_NOTICE("You unload [current_mag] from [src]."), null, 4, CHAT_TYPE_COMBAT_ACTION)
current_mag.update_icon()
current_mag = null
update_icon()
///Unload a chambered round, if one exists, and empty the chamber.
/obj/item/weapon/gun/proc/unload_chamber(mob/user)
if(!in_chamber)
return
var/found_handful
for(var/obj/item/ammo_magazine/handful/H in user.loc)
if(H.default_ammo == in_chamber.ammo.type && H.caliber == caliber && H.current_rounds < H.max_rounds)
found_handful = TRUE
H.current_rounds++
H.update_icon()
break
if(!found_handful)
var/obj/item/ammo_magazine/handful/new_handful = new(get_turf(src))
new_handful.generate_handful(in_chamber.ammo.type, caliber, 8, 1, type)
QDEL_NULL(in_chamber)
//Manually cock the gun
//This only works on weapons NOT marked with UNUSUAL_DESIGN or INTERNAL_MAG
/obj/item/weapon/gun/proc/cock(mob/user)
if(flags_gun_features & (GUN_BURST_FIRING|GUN_UNUSUAL_DESIGN|GUN_INTERNAL_MAG))
return
if(cock_cooldown > world.time)
return
cock_cooldown = world.time + cock_delay
cock_gun(user)
if(in_chamber)
user.visible_message(SPAN_NOTICE("[user] cocks [src], clearing a [in_chamber.name] from its chamber."),
SPAN_NOTICE("You cock [src], clearing a [in_chamber.name] from its chamber."), null, 4, CHAT_TYPE_COMBAT_ACTION)
unload_chamber(user)
else
user.visible_message(SPAN_NOTICE("[user] cocks [src]."),
SPAN_NOTICE("You cock [src]."), null, 4, CHAT_TYPE_COMBAT_ACTION)
display_ammo(user)
ready_in_chamber() //This will already check for everything else, loading the next bullet.
//----------------------------------------------------------
// \\
// AFTER ATTACK AND CHAMBERING \\
// \\
// \\
//----------------------------------------------------------
/**
load_into_chamber(), reload_into_chamber(), and clear_jam() do all of the heavy lifting.
If you need to change up how a gun fires, just change these procs for that subtype
and you're good to go.
**/
/obj/item/weapon/gun/proc/load_into_chamber(mob/user)
//The workhorse of the bullet procs.
//If we have a round chambered and no active attachable, we're good to go.
if(in_chamber && !active_attachable)
return in_chamber //Already set!
//Let's check on the active attachable. It loads ammo on the go, so it never chambers anything
if(active_attachable)
if(shots_fired >= 1) // This is what you'll want to remove if you want automatic underbarrel guns in the future
SEND_SIGNAL(src, COMSIG_GUN_INTERRUPT_FIRE)
return
if(active_attachable.current_rounds > 0) //If it's still got ammo and stuff.
active_attachable.current_rounds--
var/obj/projectile/bullet = create_bullet(active_attachable.ammo, initial(name))
// For now, only bullet traits from the attachment itself will apply to its projectiles
for(var/entry in active_attachable.traits_to_give_attached)
var/list/L
// Check if this is an ID'd bullet trait
if(istext(entry))
L = active_attachable.traits_to_give_attached[entry].Copy()
else
// Prepend the bullet trait to the list
L = list(entry) + active_attachable.traits_to_give_attached[entry]
bullet.apply_bullet_trait(L)
return bullet
else
to_chat(user, SPAN_WARNING("[active_attachable] is empty!"))
to_chat(user, SPAN_NOTICE("You disable [active_attachable]."))
playsound(user, active_attachable.activation_sound, 15, 1)
active_attachable.activate_attachment(src, null, TRUE)
else
return ready_in_chamber()//We're not using the active attachable, we must use the active mag if there is one.
/obj/item/weapon/gun/proc/apply_traits(obj/projectile/P)
// Apply bullet traits from gun
for(var/entry in traits_to_give)
var/list/L
// Check if this is an ID'd bullet trait
if(istext(entry))
L = traits_to_give[entry].Copy()
else
// Prepend the bullet trait to the list
L = list(entry) + traits_to_give[entry]
P.apply_bullet_trait(L)
// Apply bullet traits from attachments
for(var/slot in attachments)
if(!attachments[slot])
continue
var/obj/item/attachable/AT = attachments[slot]
for(var/entry in AT.traits_to_give)
var/list/L
// Check if this is an ID'd bullet trait