From 56ecb035a227a8c24273f9e4b46e745e104f0c53 Mon Sep 17 00:00:00 2001 From: Gandalf Date: Tue, 23 Mar 2021 00:13:45 +0000 Subject: [PATCH] Fully automatic weaponry implementation [semi, burst, automatic] (#4301) * FULLY AUTOMATIC REIFLES RHEUASBVHDSA * a * a * SHITCODE BEGONE * a * EEEE * Update gun.dm * a * Update gun.dm * aaaa * Update gun.dm * Update gun.dm * a * Update gun.dm * a * a * aaa * Update gun.dm * Update gun.dm * Update gun.dm * Update code/modules/projectiles/gun.dm * Update code/modules/projectiles/gun.dm * aaa * tests * a * wew * aaaa * aaaaa * aa * aa * Update gun.dm * aaaa * Update gun.dm * aaaa * Update fullauto.dm * aaaaaaa * Update master.dm * Update master.dm * Update master.dm --- code/__DEFINES/~skyrat_defines/gun.dm | 3 + code/__DEFINES/~skyrat_defines/signals.dm | 16 +- code/datums/action.dm | 4 +- code/datums/components/fullauto.dm | 2 + code/modules/projectiles/gun.dm | 3 +- .../projectiles/guns/ballistic/automatic.dm | 10 +- .../projectiles/guns/ballistic/launchers.dm | 2 +- .../projectiles/guns/ballistic/pistol.dm | 4 +- .../projectiles/guns/ballistic/shotgun.dm | 2 +- .../projectiles/guns/energy/laser_gatling.dm | 4 +- .../projectiles/guns/energy/special.dm | 2 +- .../code/datums/components/fullauto.dm | 278 ++++++ .../projectiles/guns/ballistic/automatic.dm | 328 +++++++ .../code/modules/projectiles/guns/gun.dm | 833 ++++++++++++++++++ .../icons/mob/actions/actions_items.dmi | Bin 0 -> 637 bytes .../modules/ERT_Factions/FTU/code/items.dm | 2 - .../modules/aesthetics/guns/code/guns.dm | 8 +- .../modules/gunsafety/code/gun_safety.dm | 57 +- .../modules/gunsgalore/code/guns/master.dm | 59 +- .../modules/gunsgalore/code/guns/stg/stg.dm | 1 + .../projectiles/guns/ballistic/rifle.dm | 2 +- .../modules/sec_haul/code/guns/guns.dm | 12 +- tgstation.dme | 4 + 23 files changed, 1498 insertions(+), 138 deletions(-) create mode 100644 code/__DEFINES/~skyrat_defines/gun.dm create mode 100644 modular_skyrat/master_files/code/datums/components/fullauto.dm create mode 100644 modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm create mode 100644 modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm create mode 100644 modular_skyrat/master_files/icons/mob/actions/actions_items.dmi diff --git a/code/__DEFINES/~skyrat_defines/gun.dm b/code/__DEFINES/~skyrat_defines/gun.dm new file mode 100644 index 00000000000..05ce5118a30 --- /dev/null +++ b/code/__DEFINES/~skyrat_defines/gun.dm @@ -0,0 +1,3 @@ +#define SELECT_SEMI_AUTOMATIC 1 +#define SELECT_BURST_SHOT 2 +#define SELECT_FULLY_AUTOMATIC 3 diff --git a/code/__DEFINES/~skyrat_defines/signals.dm b/code/__DEFINES/~skyrat_defines/signals.dm index b04618b41e1..e362ae9f521 100644 --- a/code/__DEFINES/~skyrat_defines/signals.dm +++ b/code/__DEFINES/~skyrat_defines/signals.dm @@ -1,9 +1,15 @@ -// Movable signals +//Movable signals +///When someone talks into a radio #define COMSIG_MOVABLE_RADIO_TALK_INTO "movable_radio_talk_into" //from radio talk_into(): (obj/item/radio/radio, message, channel, list/spans, datum/language/language, direct) -// Mob signals -// Living signals +//Mob signals +///Resting position for living mob updated #define COMSIG_LIVING_UPDATED_RESTING "living_updated_resting" //from base of (/mob/living/proc/update_resting): (resting) - -//Horror form bombastic flag +///Horror form bombastic flag #define COMSIG_HORRORFORM_EXPLODE "horrorform_explode" + +//Gun signals +///When a gun is switched to automatic fire mode +#define COMSIG_GUN_AUTOFIRE_SELECTED "gun_autofire_selected" +///When a gun is switched off of automatic fire mode +#define COMSIG_GUN_AUTOFIRE_DESELECTED "gun_autofire_deselected" diff --git a/code/datums/action.dm b/code/datums/action.dm index 0447d7d4a81..8bfe791c261 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -201,6 +201,8 @@ name = "Toggle Hood" /datum/action/item_action/toggle_firemode + icon_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_items.dmi' //SKYRAT EDIT ADDITION + button_icon_state = "fireselect_no" //SKYRAT EDIT ADDITION name = "Toggle Firemode" /datum/action/item_action/rcl_col @@ -675,7 +677,7 @@ desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps." icon_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "jetboot" - + /datum/action/item_action/bhop/brocket name = "Activate Rocket Boots" desc = "Activates the boot's rocket propulsion system, allowing the user to hurl themselves great distances." diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm index 0da742c8a1f..40f81a435f4 100644 --- a/code/datums/components/fullauto.dm +++ b/code/datums/components/fullauto.dm @@ -1,3 +1,4 @@ +/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR FULLAUTO.DM #define AUTOFIRE_MOUSEUP 0 #define AUTOFIRE_MOUSEDOWN 1 @@ -275,3 +276,4 @@ #undef AUTOFIRE_MOUSEUP #undef AUTOFIRE_MOUSEDOWN +*/ diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index b32b81b838c..eaedabdb140 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1,4 +1,4 @@ - +/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR GUN.DM #define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4 #define FIRING_PIN_REMOVAL_DELAY 50 @@ -718,3 +718,4 @@ #undef FIRING_PIN_REMOVAL_DELAY #undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER +*/ diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index dde5ef6cd69..2698f289c33 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -1,3 +1,4 @@ +/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR AUTOMATIC.DM /obj/item/gun/ballistic/automatic w_class = WEIGHT_CLASS_NORMAL can_suppress = TRUE @@ -63,7 +64,7 @@ var/datum/action/A = X A.UpdateButtonIcon() -/obj/item/gun/ballistic/automatic/c20r //ICON OVERRIDEN IN SKYRAT AESTHETICS - SEE MODULE +/obj/item/gun/ballistic/automatic/c20r name = "\improper C-20r SMG" desc = "A bullpup three-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp." icon_state = "c20r" @@ -136,7 +137,7 @@ mag_display = TRUE rack_sound = 'sound/weapons/gun/pistol/slide_lock.ogg' -/obj/item/gun/ballistic/automatic/m90 //ICON OVERRIDEN IN SKYRAT AESTHETICS - SEE MODULE +/obj/item/gun/ballistic/automatic/m90 name = "\improper M-90gl Carbine" desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher which can be fired using right click." icon_state = "m90" @@ -275,7 +276,6 @@ /obj/item/gun/ballistic/automatic/l6_saw/AltClick(mob/user) - . = ..() //SKYRAT EDIT ADDITION - GUNSGALORE if(!user.canUseTopic(src)) return cover_open = !cover_open @@ -359,8 +359,7 @@ /obj/item/gun/ballistic/automatic/surplus name = "Surplus Rifle" -// desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm ammo and its bulky frame prevents one-hand firing." // SKYRAT EDIT: Original - desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm Magnum ammo and its bulky frame prevents one-hand firing." // SKYRAT EDIT: Calibre rename + desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm ammo and its bulky frame prevents one-hand firing." icon_state = "surplus" inhand_icon_state = "moistnugget" worn_icon_state = null @@ -391,3 +390,4 @@ actions_types = list() fire_sound = 'sound/weapons/laser.ogg' casing_ejector = FALSE +*/ diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm index 558157a6ad5..d838bfaca00 100644 --- a/code/modules/projectiles/guns/ballistic/launchers.dm +++ b/code/modules/projectiles/guns/ballistic/launchers.dm @@ -39,7 +39,7 @@ mag_type = /obj/item/ammo_box/magazine/m75 burst_size = 1 fire_delay = 0 - actions_types = list() + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE casing_ejector = FALSE /obj/item/gun/ballistic/rocketlauncher diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index 24e026d8737..698f47be167 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -8,7 +8,7 @@ can_suppress = TRUE burst_size = 1 fire_delay = 0 - actions_types = list() + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE bolt_type = BOLT_TYPE_LOCKING fire_sound = 'sound/weapons/gun/pistol/shot.ogg' dry_fire_sound = 'sound/weapons/gun/pistol/dry_fire.ogg' @@ -81,7 +81,7 @@ burst_size = 3 fire_delay = 1 spread = 10 - actions_types = list(/datum/action/item_action/toggle_firemode) + //actions_types = list(/datum/action/item_action/toggle_firemode) SKYRAT EDIT REMOVAL suppressor_x_offset = 6 /obj/item/gun/ballistic/automatic/pistol/stickman diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 80c0138a3b0..853cd0f7d79 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -128,7 +128,7 @@ fire_delay = 0 pin = /obj/item/firing_pin/implant/pindicate fire_sound = 'sound/weapons/gun/shotgun/shot_alt.ogg' - actions_types = list() + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE mag_display = TRUE empty_indicator = TRUE empty_alarm = TRUE diff --git a/code/modules/projectiles/guns/energy/laser_gatling.dm b/code/modules/projectiles/guns/energy/laser_gatling.dm index b82c983c901..18f525ee87b 100644 --- a/code/modules/projectiles/guns/energy/laser_gatling.dm +++ b/code/modules/projectiles/guns/energy/laser_gatling.dm @@ -109,6 +109,8 @@ item_flags = NEEDS_PERMIT | SLOWS_WHILE_IN_HAND can_charge = FALSE var/obj/item/minigunpack/ammo_pack + fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_FULLY_AUTOMATIC)//SKYRAT EDIT ADDITION + /obj/item/gun/energy/minigun/Initialize() if(istype(loc, /obj/item/minigunpack)) //We should spawn inside an ammo pack so let's use that one. @@ -121,7 +123,7 @@ /obj/item/gun/energy/minigun/ComponentInitialize() . = ..() AddElement(/datum/element/update_icon_blocker) - AddComponent(/datum/component/automatic_fire, 0.2 SECONDS) + //AddComponent(/datum/component/automatic_fire, 0.2 SECONDS) SKYRAT EDIT REMOVAL /obj/item/gun/energy/minigun/attack_self(mob/living/user) return diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 998773f5079..5b9255a6fcd 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -380,4 +380,4 @@ /obj/item/gun/energy/tesla_cannon/ComponentInitialize() . = ..() - AddComponent(/datum/component/automatic_fire, 0.1 SECONDS) + //AddComponent(/datum/component/automatic_fire, 0.1 SECONDS) SKYRAT EDIT REMOVAL diff --git a/modular_skyrat/master_files/code/datums/components/fullauto.dm b/modular_skyrat/master_files/code/datums/components/fullauto.dm new file mode 100644 index 00000000000..7286e1d36de --- /dev/null +++ b/modular_skyrat/master_files/code/datums/components/fullauto.dm @@ -0,0 +1,278 @@ +#define AUTOFIRE_MOUSEUP 0 +#define AUTOFIRE_MOUSEDOWN 1 + +/datum/component/automatic_fire + var/client/clicker + var/mob/living/shooter + var/atom/target + var/turf/target_loc //For dealing with locking on targets due to BYOND engine limitations (the mouse input only happening when mouse moves). + var/autofire_stat = AUTOFIRE_STAT_IDLE + var/mouse_parameters + var/autofire_shot_delay = 0.3 SECONDS //Time between individual shots. + var/mouse_status = AUTOFIRE_MOUSEUP //This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar. + + COOLDOWN_DECLARE(next_shot_cd) + +/datum/component/automatic_fire/Initialize(_autofire_shot_delay) + . = ..() + if(!isgun(parent)) + return COMPONENT_INCOMPATIBLE + var/obj/item/gun = parent + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/wake_up) + RegisterSignal(parent, COMSIG_GUN_AUTOFIRE_SELECTED, .proc/wake_up) + RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED, COMSIG_GUN_AUTOFIRE_DESELECTED), .proc/autofire_off) + if(_autofire_shot_delay) + autofire_shot_delay = _autofire_shot_delay + if(ismob(gun.loc)) + var/mob/user = gun.loc + wake_up(src, user) + + +/datum/component/automatic_fire/Destroy() + UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED, COMSIG_GUN_AUTOFIRE_DESELECTED)) + autofire_off() + return ..() + +/datum/component/automatic_fire/process(delta_time) + if(!(autofire_stat & AUTOFIRE_STAT_FIRING)) + STOP_PROCESSING(SSprojectiles, src) + return + + if(!COOLDOWN_FINISHED(src, next_shot_cd)) + return + + process_shot() + +/datum/component/automatic_fire/proc/wake_up(datum/source, mob/user, slot) + SIGNAL_HANDLER + + if(autofire_stat & (AUTOFIRE_STAT_ALERT)) + return //We've updated the firemode. No need for more. + if(autofire_stat & AUTOFIRE_STAT_FIRING) + stop_autofiring() //Let's stop shooting to avoid issues. + return + + var/obj/item/gun/G = parent + + if(iscarbon(user)) + var/mob/living/carbon/shooter = user + if(shooter.is_holding(parent) && G.fire_select == SELECT_FULLY_AUTOMATIC) + autofire_on(shooter.client) + else + autofire_off() + +// There is a gun and there is a user wielding it. The component now waits for the mouse click. +/datum/component/automatic_fire/proc/autofire_on(client/usercli) + SIGNAL_HANDLER + if(autofire_stat & (AUTOFIRE_STAT_ALERT|AUTOFIRE_STAT_FIRING)) + return + autofire_stat = AUTOFIRE_STAT_ALERT + clicker = usercli + shooter = clicker.mob + RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDOWN, .proc/on_mouse_down) + RegisterSignal(shooter, COMSIG_MOB_LOGOUT, .proc/autofire_off) + if(!QDELETED(shooter)) + UnregisterSignal(shooter, COMSIG_MOB_LOGIN) + parent.RegisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN, /obj/item/gun/.proc/autofire_bypass_check) + parent.RegisterSignal(parent, COMSIG_AUTOFIRE_SHOT, /obj/item/gun/.proc/do_autofire) + + +/datum/component/automatic_fire/proc/autofire_off(datum/source) + SIGNAL_HANDLER + if(autofire_stat & (AUTOFIRE_STAT_IDLE)) + return + if(autofire_stat & AUTOFIRE_STAT_FIRING) + stop_autofiring() + + autofire_stat = AUTOFIRE_STAT_IDLE + + if(!QDELETED(clicker)) + UnregisterSignal(clicker, list(COMSIG_CLIENT_MOUSEDOWN, COMSIG_CLIENT_MOUSEUP, COMSIG_CLIENT_MOUSEDRAG)) + mouse_status = AUTOFIRE_MOUSEUP //In regards to the component there's no click anymore to care about. + clicker = null + RegisterSignal(shooter, COMSIG_MOB_LOGIN, .proc/on_client_login) + if(!QDELETED(shooter)) + UnregisterSignal(shooter, COMSIG_MOB_LOGOUT) + shooter = null + parent.UnregisterSignal(parent, COMSIG_AUTOFIRE_SHOT) + parent.UnregisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN) + +/datum/component/automatic_fire/proc/on_client_login(mob/source) + SIGNAL_HANDLER + if(!source.client) + return + if(source.is_holding(parent)) + autofire_on(source.client) + +/datum/component/automatic_fire/proc/on_mouse_down(client/source, atom/_target, turf/location, control, params) + var/list/modifiers = params2list(params) //If they're shift+clicking, for example, let's not have them accidentally shoot. + + if(LAZYACCESS(modifiers, SHIFT_CLICK)) + return + if(LAZYACCESS(modifiers, CTRL_CLICK)) + return + if(LAZYACCESS(modifiers, MIDDLE_CLICK)) + return + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + return + if(LAZYACCESS(modifiers, ALT_CLICK)) + return + if(source.mob.throw_mode) + return + if(!isturf(source.mob.loc)) //No firing inside lockers and stuff. + return + if(get_dist(source.mob, _target) < 2) //Adjacent clicking. + return + + if(isnull(location)) //Clicking on a screen object. + if(_target.plane != CLICKCATCHER_PLANE) //The clickcatcher is a special case. We want the click to trigger then, under it. + return //If we click and drag on our worn backpack, for example, we want it to open instead. + _target = params2turf(modifiers["screen-loc"], get_turf(source.eye), source) + if(!_target) + CRASH("Failed to get the turf under clickcatcher") + + if(SEND_SIGNAL(src, COMSIG_AUTOFIRE_ONMOUSEDOWN, source, _target, location, control, params) & COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS) + return + + source.click_intercept_time = world.time //From this point onwards Click() will no longer be triggered. + + if(autofire_stat & (AUTOFIRE_STAT_IDLE)) + CRASH("on_mouse_down() called with [autofire_stat] autofire_stat") + if(autofire_stat & AUTOFIRE_STAT_FIRING) + stop_autofiring() //This can happen if we click and hold and then alt+tab, printscreen or other such action. MouseUp won't be called then and it will keep autofiring. + + target = _target + target_loc = get_turf(target) + mouse_parameters = params + start_autofiring() + + +//Dakka-dakka +/datum/component/automatic_fire/proc/start_autofiring() + if(autofire_stat == AUTOFIRE_STAT_FIRING) + return //Already pew-pewing. + autofire_stat = AUTOFIRE_STAT_FIRING + + clicker.mouse_override_icon = 'icons/effects/mouse_pointers/weapon_pointer.dmi' + clicker.mouse_pointer_icon = clicker.mouse_override_icon + + if(mouse_status == AUTOFIRE_MOUSEUP) //See mouse_status definition for the reason for this. + RegisterSignal(clicker, COMSIG_CLIENT_MOUSEUP, .proc/on_mouse_up) + mouse_status = AUTOFIRE_MOUSEDOWN + + RegisterSignal(shooter, COMSIG_MOB_SWAP_HANDS, .proc/stop_autofiring) + + if(isgun(parent)) + var/obj/item/gun/shoota = parent + if(!shoota.on_autofire_start(shooter)) //This is needed because the minigun has a do_after before firing and signals are async. + stop_autofiring() + return + if(autofire_stat != AUTOFIRE_STAT_FIRING) + return //Things may have changed while on_autofire_start() was being processed, due to do_after's sleep. + + if(!process_shot()) //First shot is processed instantly. + return //If it fails, such as when the gun is empty, then there's no need to schedule a second shot. + + START_PROCESSING(SSprojectiles, src) + RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG, .proc/on_mouse_drag) + + +/datum/component/automatic_fire/proc/on_mouse_up(datum/source, atom/object, turf/location, control, params) + SIGNAL_HANDLER + UnregisterSignal(clicker, COMSIG_CLIENT_MOUSEUP) + mouse_status = AUTOFIRE_MOUSEUP + if(autofire_stat == AUTOFIRE_STAT_FIRING) + stop_autofiring() + return COMPONENT_CLIENT_MOUSEUP_INTERCEPT + + +/datum/component/automatic_fire/proc/stop_autofiring(datum/source, atom/object, turf/location, control, params) + SIGNAL_HANDLER + switch(autofire_stat) + if(AUTOFIRE_STAT_IDLE, AUTOFIRE_STAT_ALERT) + return + STOP_PROCESSING(SSprojectiles, src) + autofire_stat = AUTOFIRE_STAT_ALERT + if(clicker) + clicker.mouse_override_icon = null + clicker.mouse_pointer_icon = clicker.mouse_override_icon + UnregisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG) + if(!QDELETED(shooter)) + UnregisterSignal(shooter, COMSIG_MOB_SWAP_HANDS) + target = null + target_loc = null + mouse_parameters = null + +/datum/component/automatic_fire/proc/on_mouse_drag(client/source, atom/src_object, atom/over_object, turf/src_location, turf/over_location, src_control, over_control, params) + SIGNAL_HANDLER + if(isnull(over_location)) //This happens when the mouse is over an inventory or screen object, or on entering deep darkness, for example. + var/list/modifiers = params2list(params) + var/new_target = params2turf(modifiers["screen-loc"], get_turf(source.eye), source) + mouse_parameters = params + if(!new_target) + if(QDELETED(target)) //No new target acquired, and old one was deleted, get us out of here. + stop_autofiring() + CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]. Old target was incidentally QDELETED.") + target = get_turf(target) //If previous target wasn't a turf, let's turn it into one to avoid locking onto a potentially moving target. + target_loc = target + CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]") + target = new_target + target_loc = new_target + return + target = over_object + target_loc = get_turf(over_object) + mouse_parameters = params + + +/datum/component/automatic_fire/proc/process_shot() + if(autofire_stat != AUTOFIRE_STAT_FIRING) + return + if(QDELETED(target) || get_turf(target) != target_loc) //Target moved or got destroyed since we last aimed. + target = target_loc //So we keep firing on the emptied tile until we move our mouse and find a new target. + if(get_dist(shooter, target) <= 0) + target = get_step(shooter, shooter.dir) //Shoot in the direction faced if the mouse is on the same tile as we are. + target_loc = target + else if(!in_view_range(shooter, target)) + stop_autofiring() //Elvis has left the building. + return FALSE + shooter.face_atom(target) + COOLDOWN_START(src, next_shot_cd, autofire_shot_delay) + if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS) + return TRUE + stop_autofiring() + return FALSE + +// Gun procs. + +/obj/item/gun/proc/on_autofire_start(mob/living/shooter) + if(!can_shoot(shooter) || !can_trigger_gun(shooter) || semicd) + return FALSE + var/obj/item/bodypart/other_hand = shooter.has_hand_for_held_index(shooter.get_inactive_hand_index()) + if(weapon_weight == WEAPON_HEAVY && (shooter.get_inactive_held_item() || !other_hand)) + to_chat(shooter, "You need two hands to fire [src]!") + return FALSE + return TRUE + + +/obj/item/gun/proc/autofire_bypass_check(datum/source, client/clicker, atom/target, turf/location, control, params) + SIGNAL_HANDLER + if(clicker.mob.get_active_held_item() != src) + return COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS + + +/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, params) + SIGNAL_HANDLER_DOES_SLEEP + if(!can_shoot()) + shoot_with_empty_chamber(shooter) + return NONE + var/obj/item/gun/akimbo_gun = shooter.get_inactive_held_item() + var/bonus_spread = 0 + if(istype(akimbo_gun) && weapon_weight < WEAPON_MEDIUM) + if(akimbo_gun.weapon_weight < WEAPON_MEDIUM && akimbo_gun.can_trigger_gun(shooter)) + bonus_spread = dual_wield_spread + addtimer(CALLBACK(akimbo_gun, /obj/item/gun.proc/process_fire, target, shooter, TRUE, params, null, bonus_spread), 1) + process_fire(target, shooter, TRUE, params, null, bonus_spread) + return COMPONENT_AUTOFIRE_SHOT_SUCCESS //All is well, we can continue shooting. + +#undef AUTOFIRE_MOUSEUP +#undef AUTOFIRE_MOUSEDOWN diff --git a/modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm b/modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm new file mode 100644 index 00000000000..a105ba6245b --- /dev/null +++ b/modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm @@ -0,0 +1,328 @@ +/obj/item/gun/ballistic/automatic + w_class = WEIGHT_CLASS_NORMAL + can_suppress = TRUE + burst_size = 3 + fire_delay = 2 + semi_auto = TRUE + fire_sound = 'sound/weapons/gun/smg/shot.ogg' + fire_sound_volume = 90 + vary_fire_sound = FALSE + rack_sound = 'sound/weapons/gun/smg/smgrack.ogg' + suppressed_sound = 'sound/weapons/gun/smg/shot_suppressed.ogg' + fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT, SELECT_FULLY_AUTOMATIC) + +/obj/item/gun/ballistic/automatic/proto + name = "\improper Nanotrasen Saber SMG" + desc = "A prototype three-round burst 9mm submachine gun, designated 'SABR'. Has a threaded barrel for suppressors." + icon_state = "saber" + selector_switch_icon = TRUE + mag_display = TRUE + empty_indicator = TRUE + mag_type = /obj/item/ammo_box/magazine/smgm9mm + pin = null + bolt_type = BOLT_TYPE_LOCKING + show_bolt_icon = FALSE + +/obj/item/gun/ballistic/automatic/proto/unrestricted + pin = /obj/item/firing_pin + +/obj/item/gun/ballistic/automatic/c20r + name = "\improper C-20r SMG" + desc = "A bullpup three-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp." + icon_state = "c20r" + inhand_icon_state = "c20r" + selector_switch_icon = TRUE + mag_type = /obj/item/ammo_box/magazine/smgm45 + fire_delay = 2 + burst_size = 3 + pin = /obj/item/firing_pin/implant/pindicate + can_bayonet = TRUE + knife_x_offset = 26 + knife_y_offset = 12 + mag_display = TRUE + mag_display_ammo = TRUE + empty_indicator = TRUE + +/obj/item/gun/ballistic/automatic/c20r/update_overlays() + . = ..() + if(!chambered && empty_indicator) + . += "[icon_state]_empty" + +/obj/item/gun/ballistic/automatic/c20r/unrestricted + pin = /obj/item/firing_pin + +/obj/item/gun/ballistic/automatic/c20r/Initialize() + . = ..() + update_appearance() + +/obj/item/gun/ballistic/automatic/wt550 + name = "security auto rifle" + desc = "An outdated personal defence weapon. Uses 4.6x30mm rounds and is designated the WT-550 Automatic Rifle." + icon_state = "wt550" + w_class = WEIGHT_CLASS_BULKY + inhand_icon_state = "arg" + mag_type = /obj/item/ammo_box/magazine/wt550m9 + fire_delay = 2 + can_suppress = FALSE + burst_size = 0 + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + can_bayonet = TRUE + knife_x_offset = 25 + knife_y_offset = 12 + mag_display = TRUE + mag_display_ammo = TRUE + empty_indicator = TRUE + +/obj/item/gun/ballistic/automatic/plastikov + name = "\improper PP-95 SMG" + desc = "An ancient 9mm submachine gun pattern updated and simplified to lower costs, though perhaps simplified too much." + icon_state = "plastikov" + inhand_icon_state = "plastikov" + mag_type = /obj/item/ammo_box/magazine/plastikov9mm + burst_size = 5 + spread = 25 + can_suppress = FALSE + projectile_damage_multiplier = 0.35 //It's like 10.5 damage per bullet, it's close enough to 10 shots + mag_display = TRUE + empty_indicator = TRUE + fire_sound = 'sound/weapons/gun/smg/shot_alt.ogg' + +/obj/item/gun/ballistic/automatic/mini_uzi + name = "\improper Type U3 Uzi" + desc = "A lightweight, burst-fire submachine gun, for when you really want someone dead. Uses 9mm rounds." + icon_state = "miniuzi" + mag_type = /obj/item/ammo_box/magazine/uzim9mm + burst_size = 2 + bolt_type = BOLT_TYPE_OPEN + show_bolt_icon = FALSE + mag_display = TRUE + rack_sound = 'sound/weapons/gun/pistol/slide_lock.ogg' + +/obj/item/gun/ballistic/automatic/m90 + name = "\improper M-90gl Carbine" + desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher which can be fired using right click." + icon_state = "m90" + w_class = WEIGHT_CLASS_BULKY + inhand_icon_state = "m90" + selector_switch_icon = TRUE + mag_type = /obj/item/ammo_box/magazine/m556 + can_suppress = FALSE + var/obj/item/gun/ballistic/revolver/grenadelauncher/underbarrel + burst_size = 3 + fire_delay = 2 + spread = 5 + pin = /obj/item/firing_pin/implant/pindicate + mag_display = TRUE + empty_indicator = TRUE + fire_sound = 'sound/weapons/gun/smg/shot_alt.ogg' + +/obj/item/gun/ballistic/automatic/m90/Initialize() + . = ..() + underbarrel = new /obj/item/gun/ballistic/revolver/grenadelauncher(src) + update_appearance() + +/obj/item/gun/ballistic/automatic/m90/unrestricted + pin = /obj/item/firing_pin + +/obj/item/gun/ballistic/automatic/m90/unrestricted/Initialize() + . = ..() + underbarrel = new /obj/item/gun/ballistic/revolver/grenadelauncher/unrestricted(src) + update_appearance() + +/obj/item/gun/ballistic/automatic/m90/afterattack_secondary(atom/target, mob/living/user, flag, params) + underbarrel.afterattack(target, user, flag, params) + return SECONDARY_ATTACK_CONTINUE_CHAIN + +/obj/item/gun/ballistic/automatic/m90/attackby(obj/item/A, mob/user, params) + if(istype(A, /obj/item/ammo_casing)) + if(istype(A, underbarrel.magazine.ammo_type)) + underbarrel.attack_self(user) + underbarrel.attackby(A, user, params) + else + ..() + +/obj/item/gun/ballistic/automatic/tommygun + name = "\improper Thompson SMG" + desc = "Based on the classic 'Chicago Typewriter'." + icon_state = "tommygun" + inhand_icon_state = "shotgun" + selector_switch_icon = TRUE + w_class = WEIGHT_CLASS_BULKY + slot_flags = 0 + mag_type = /obj/item/ammo_box/magazine/tommygunm45 + can_suppress = FALSE + burst_size = 4 + fire_delay = 1 + bolt_type = BOLT_TYPE_OPEN + empty_indicator = TRUE + show_bolt_icon = FALSE + +/obj/item/gun/ballistic/automatic/ar + name = "\improper NT-ARG 'Boarder'" + desc = "A robust assault rifle used by Nanotrasen fighting forces." + icon_state = "arg" + inhand_icon_state = "arg" + slot_flags = 0 + mag_type = /obj/item/ammo_box/magazine/m556 + can_suppress = FALSE + burst_size = 3 + fire_delay = 1 + + +// L6 SAW // + +/obj/item/gun/ballistic/automatic/l6_saw + name = "\improper L6 SAW" + desc = "A heavily modified 7.12x82mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation." + icon_state = "l6" + inhand_icon_state = "l6" + base_icon_state = "l6" + w_class = WEIGHT_CLASS_HUGE + slot_flags = 0 + mag_type = /obj/item/ammo_box/magazine/mm712x82 + weapon_weight = WEAPON_HEAVY + burst_size = 1 + fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_FULLY_AUTOMATIC) + can_suppress = FALSE + spread = 7 + pin = /obj/item/firing_pin/implant/pindicate + bolt_type = BOLT_TYPE_OPEN + show_bolt_icon = FALSE + mag_display = TRUE + mag_display_ammo = TRUE + tac_reloads = FALSE + fire_sound = 'sound/weapons/gun/l6/shot.ogg' + rack_sound = 'sound/weapons/gun/l6/l6_rack.ogg' + suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' + var/cover_open = FALSE + +/obj/item/gun/ballistic/automatic/l6_saw/unrestricted + pin = /obj/item/firing_pin + +/obj/item/gun/ballistic/automatic/l6_saw/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/gun/ballistic/automatic/l6_saw/examine(mob/user) + . = ..() + . += "alt + click to [cover_open ? "close" : "open"] the dust cover." + if(cover_open && magazine) + . += "It seems like you could use an empty hand to remove the magazine." + + +/obj/item/gun/ballistic/automatic/l6_saw/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src)) + return + cover_open = !cover_open + to_chat(user, "You [cover_open ? "open" : "close"] [src]'s cover.") + playsound(src, 'sound/weapons/gun/l6/l6_door.ogg', 60, TRUE) + update_appearance() + +/obj/item/gun/ballistic/automatic/l6_saw/update_icon_state() + . = ..() + inhand_icon_state = "[base_icon_state][cover_open ? "open" : "closed"][magazine ? "mag":"nomag"]" + +/obj/item/gun/ballistic/automatic/l6_saw/update_overlays() + . = ..() + . += "l6_door_[cover_open ? "open" : "closed"]" + + +/obj/item/gun/ballistic/automatic/l6_saw/afterattack(atom/target as mob|obj|turf, mob/living/user as mob|obj, flag, params) + if(cover_open) + to_chat(user, "[src]'s cover is open! Close it before firing!") + return + else + . = ..() + update_appearance() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/gun/ballistic/automatic/l6_saw/attack_hand(mob/user, list/modifiers) + if (loc != user) + ..() + return + if (!cover_open) + to_chat(user, "[src]'s cover is closed! Open it before trying to remove the magazine!") + return + ..() + +/obj/item/gun/ballistic/automatic/l6_saw/attackby(obj/item/A, mob/user, params) + if(!cover_open && istype(A, mag_type)) + to_chat(user, "[src]'s dust cover prevents a magazine from being fit.") + return + ..() + +// SNIPER // + +/obj/item/gun/ballistic/automatic/sniper_rifle + name = "sniper rifle" + desc = "A long ranged weapon that does significant damage. No, you can't quickscope." + icon_state = "sniper" + w_class = WEIGHT_CLASS_BULKY + inhand_icon_state = "sniper" + worn_icon_state = null + fire_sound = 'sound/weapons/gun/sniper/shot.ogg' + fire_sound_volume = 90 + vary_fire_sound = FALSE + load_sound = 'sound/weapons/gun/sniper/mag_insert.ogg' + rack_sound = 'sound/weapons/gun/sniper/rack.ogg' + suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' + recoil = 2 + weapon_weight = WEAPON_HEAVY + mag_type = /obj/item/ammo_box/magazine/sniper_rounds + fire_delay = 40 + burst_size = 1 + w_class = WEIGHT_CLASS_NORMAL + zoomable = TRUE + zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you. + zoom_out_amt = 5 + slot_flags = ITEM_SLOT_BACK + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + mag_display = TRUE + suppressor_x_offset = 3 + suppressor_y_offset = 3 + +/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate + name = "syndicate sniper rifle" + desc = "An illegally modified .50 cal sniper rifle with suppression compatibility. Quickscoping still doesn't work." + can_suppress = TRUE + can_unsuppress = TRUE + pin = /obj/item/firing_pin/implant/pindicate + +// Old Semi-Auto Rifle // + +/obj/item/gun/ballistic/automatic/surplus + name = "Surplus Rifle" + desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm Magnum ammo and its bulky frame prevents one-hand firing." + icon_state = "surplus" + inhand_icon_state = "moistnugget" + worn_icon_state = null + weapon_weight = WEAPON_HEAVY + mag_type = /obj/item/ammo_box/magazine/m10mm/rifle + fire_delay = 30 + burst_size = 1 + can_unsuppress = TRUE + can_suppress = TRUE + w_class = WEIGHT_CLASS_HUGE + slot_flags = ITEM_SLOT_BACK + //actions_types = list() SKYRAT EDIT REMOVAL + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + mag_display = TRUE + +// Laser rifle (rechargeable magazine) // + +/obj/item/gun/ballistic/automatic/laser + name = "laser rifle" + desc = "Though sometimes mocked for the relatively weak firepower of their energy weapons, the logistic miracle of rechargeable ammunition has given Nanotrasen a decisive edge over many a foe." + icon_state = "oldrifle" + w_class = WEIGHT_CLASS_BULKY + inhand_icon_state = "arg" + mag_type = /obj/item/ammo_box/magazine/recharge + mag_display_ammo = TRUE + fire_delay = 2 + can_suppress = FALSE + burst_size = 0 + //actions_types = list() SKYRAT EDIT REMOVAL + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + fire_sound = 'sound/weapons/laser.ogg' + casing_ejector = FALSE diff --git a/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm b/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm new file mode 100644 index 00000000000..c23bafac9a4 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm @@ -0,0 +1,833 @@ + +#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4 +#define FIRING_PIN_REMOVAL_DELAY 50 + +/obj/item/gun + name = "gun" + desc = "It's a gun. It's pretty terrible, though." + icon = 'icons/obj/guns/ballistic.dmi' + icon_state = "detective" + inhand_icon_state = "gun" + worn_icon_state = "gun" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=2000) + w_class = WEIGHT_CLASS_NORMAL + throwforce = 5 + throw_speed = 3 + throw_range = 5 + force = 5 + item_flags = NEEDS_PERMIT + attack_verb_continuous = list("strikes", "hits", "bashes") + attack_verb_simple = list("strike", "hit", "bash") + + var/gun_flags = NONE + var/fire_sound = 'sound/weapons/gun/pistol/shot.ogg' + var/vary_fire_sound = TRUE + var/fire_sound_volume = 50 + var/dry_fire_sound = 'sound/weapons/gun/general/dry_fire.ogg' + var/suppressed = null //whether or not a message is displayed when fired + var/can_suppress = FALSE + var/suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' + var/suppressed_volume = 60 + var/can_unsuppress = TRUE + var/recoil = 0 //boom boom shake the room + var/clumsy_check = TRUE + var/obj/item/ammo_casing/chambered = null + trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers + var/sawn_desc = null //description change if weapon is sawn-off + var/sawn_off = FALSE + var/burst_size = 1 //how large a burst is + var/fire_delay = 0 //rate of fire for burst firing and semi auto + var/firing_burst = 0 //Prevent the weapon from firing again while already firing + var/semicd = 0 //cooldown handler + var/weapon_weight = WEAPON_LIGHT + var/dual_wield_spread = 24 //additional spread when dual wielding + + /// Just 'slightly' snowflakey way to modify projectile damage for projectiles fired from this gun. + var/projectile_damage_multiplier = 1 + + var/spread = 0 //Spread induced by the gun itself. + var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once. + + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + + var/obj/item/firing_pin/pin = /obj/item/firing_pin //standard firing pin for most guns + + var/can_flashlight = FALSE //if a flashlight can be added or removed if it already has one. + var/obj/item/flashlight/seclite/gun_light + var/datum/action/item_action/toggle_gunlight/alight + var/gunlight_state = "flight" + + var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one. + var/obj/item/kitchen/knife/bayonet + var/knife_x_offset = 0 + var/knife_y_offset = 0 + + var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite + var/ammo_y_offset = 0 + var/flight_x_offset = 0 + var/flight_y_offset = 0 + + //Zooming + var/zoomable = FALSE //whether the gun generates a Zoom action on creation + var/zoomed = FALSE //Zoom toggle + var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect) + var/zoom_out_amt = 0 + var/datum/action/toggle_scope_zoom/azoom + var/pb_knockback = 0 + + var/safety = FALSE ///Internal variable for keeping track whether the safety is on or off + var/has_gun_safety = FALSE ///Whether the gun actually has a gun safety + var/datum/action/item_action/toggle_safety/tsafety + + var/datum/action/item_action/toggle_firemode/firemode_action + ///Current fire selection, can choose between burst, single, and full auto. + var/fire_select = SELECT_SEMI_AUTOMATIC + var/fire_select_index = 1 + ///What modes does this weapon have? Put SELECT_FULLY_AUTOMATIC in here to enable fully automatic behaviours. + var/list/fire_select_modes = list(SELECT_SEMI_AUTOMATIC) + ///if i`1t has an icon for a selector switch indicating current firemode. + var/selector_switch_icon = FALSE + +/datum/action/item_action/toggle_safety + name = "Toggle Safety" + icon_icon = 'modular_skyrat/modules/gunsafety/icons/hud/actions.dmi' + button_icon_state = "safety_on" + +/obj/item/gun/ui_action_click(mob/user, actiontype) + if(istype(actiontype, /datum/action/item_action/toggle_firemode)) + fire_select() + else if(istype(actiontype, tsafety)) + toggle_safety(user) + else + ..() + +/obj/item/gun/Initialize() + . = ..() + if(pin) + pin = new pin(src) + + if(gun_light) + alight = new(src) + + build_zooming() + + if(has_gun_safety) + safety = TRUE + tsafety = new(src) + + if(burst_size > 1 && !(SELECT_BURST_SHOT in fire_select_modes)) + fire_select_modes.Add(SELECT_BURST_SHOT) + else if(burst_size <= 1 && (SELECT_BURST_SHOT in fire_select_modes)) + fire_select_modes.Remove(SELECT_BURST_SHOT) + + burst_size = 1 + + sortList(fire_select_modes, /proc/cmp_numeric_asc) + + if(fire_select_modes.len > 1) + firemode_action = new(src) + firemode_action.button_icon_state = "fireselect_[fire_select]" + firemode_action.UpdateButtonIcon() + +/obj/item/gun/ComponentInitialize() + . = ..() + if(SELECT_FULLY_AUTOMATIC in fire_select_modes) + AddComponent(/datum/component/automatic_fire, fire_delay) + +/obj/item/gun/Destroy() + if(isobj(pin)) //Can still be the initial path, then we skip + QDEL_NULL(pin) + if(gun_light) + QDEL_NULL(gun_light) + if(bayonet) + QDEL_NULL(bayonet) + if(chambered) //Not all guns are chambered (EMP'ed energy guns etc) + QDEL_NULL(chambered) + if(azoom) + QDEL_NULL(azoom) + if(suppressed) + QDEL_NULL(suppressed) + if(tsafety) + QDEL_NULL(tsafety) + if(firemode_action) + QDEL_NULL(firemode_action) + . = ..() + +/obj/item/gun/handle_atom_del(atom/A) + if(A == pin) + pin = null + if(A == chambered) + chambered = null + update_appearance() + if(A == bayonet) + clear_bayonet() + if(A == gun_light) + clear_gunlight() + if(A == suppressed) + clear_suppressor() + . = ..() + +///Clears var and updates icon. In the case of ballistic weapons, also updates the gun's weight. +/obj/item/gun/proc/clear_suppressor() + if(!can_unsuppress) + return + suppressed = null + update_appearance() + +/obj/item/gun/examine(mob/user) + . = ..() + if(pin) + . += "It has \a [pin] installed." + . += "[pin] looks like it could be removed with some tools." + else + . += "It doesn't have a firing pin installed, and won't fire." + + if(gun_light) + . += "It has \a [gun_light] [can_flashlight ? "" : "permanently "]mounted on it." + if(can_flashlight) //if it has a light and this is false, the light is permanent. + . += "[gun_light] looks like it can be unscrewed from [src]." + else if(can_flashlight) + . += "It has a mounting point for a seclite." + + if(bayonet) + . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it." + if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent. + . += "[bayonet] looks like it can be unscrewed from [src]." + if(can_bayonet) + . += "It has a bayonet lug on it." + if(has_gun_safety) + . += "The safety is [safety ? "ON" : "OFF"]." + +/obj/item/gun/equipped(mob/living/user, slot) + . = ..() + if(zoomed && user.get_active_held_item() != src) + zoom(user, user.dir, FALSE) //we can only stay zoomed in if it's in our hands //yeah and we only unzoom if we're actually zoomed using the gun!! + +/obj/item/gun/proc/fire_select() + var/mob/living/carbon/human/user = usr + + var/max_mode = fire_select_modes.len + + if(max_mode <= 1) + to_chat(user, "[src] is not capable of switching firemodes!") + return + + fire_select_index = 1 + fire_select_index % max_mode //Magic math to cycle through this shit! + + fire_select = fire_select_modes[fire_select_index] + + switch(fire_select) + if(SELECT_SEMI_AUTOMATIC) + burst_size = 1 + fire_delay = 0 + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user) + to_chat(user, "You switch [src] to semi-automatic.") + if(SELECT_BURST_SHOT) + burst_size = initial(burst_size) + fire_delay = initial(fire_delay) + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user) + to_chat(user, "You switch [src] to [burst_size]-round burst.") + if(SELECT_FULLY_AUTOMATIC) + burst_size = 1 + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_SELECTED, user) + to_chat(user, "You switch [src] to automatic.") + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_appearance() + firemode_action.button_icon_state = "fireselect_[fire_select]" + firemode_action.UpdateButtonIcon() + return TRUE + +//called after the gun has successfully fired its chambered ammo. +/obj/item/gun/proc/process_chamber() + return FALSE + +//check if there's enough ammo/energy/whatever to shoot one time +//i.e if clicking would make it shoot +/obj/item/gun/proc/can_shoot() + return TRUE + +/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj) + to_chat(user, "*click*") + playsound(src, dry_fire_sound, 30, TRUE) + + +/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1) + if(recoil) + shake_camera(user, recoil + 1, recoil) + + if(suppressed) + playsound(user, suppressed_sound, suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) + else + playsound(user, fire_sound, fire_sound_volume, vary_fire_sound) + if(message) + if(pointblank) + user.visible_message("[user] fires [src] point blank at [pbtarget]!", \ + "You fire [src] point blank at [pbtarget]!", \ + "You hear a gunshot!", COMBAT_MESSAGE_RANGE, pbtarget) + to_chat(pbtarget, "[user] fires [src] point blank at you!") + if(pb_knockback > 0 && ismob(pbtarget)) + var/mob/PBT = pbtarget + var/atom/throw_target = get_edge_target_turf(PBT, user.dir) + PBT.throw_at(throw_target, pb_knockback, 2) + else + user.visible_message("[user] fires [src]!", \ + "You fire [src]!", \ + "You hear a gunshot!", COMBAT_MESSAGE_RANGE) + +/obj/item/gun/emp_act(severity) + . = ..() + if(!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/O in contents) + O.emp_act(severity) + +/obj/item/gun/attack_secondary(mob/living/victim, mob/living/user, params) + if(user.GetComponent(/datum/component/gunpoint)) + to_chat(user, "You are already holding someone up!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(user == victim) + to_chat(user,"You can't hold yourself up!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + user.AddComponent(/datum/component/gunpoint, victim, src) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) + . = ..() + if(QDELETED(target)) + return + if(firing_burst) + return + if(flag) //It's adjacent, is the user, or is on the user's person + if(target in user.contents) //can't shoot stuff inside us. + return + if(!ismob(target) || user.combat_mode) //melee attack + return + if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected) + return + if(iscarbon(target)) + var/mob/living/carbon/C = target + for(var/i in C.all_wounds) + var/datum/wound/W = i + if(W.try_treating(src, user)) + return // another coward cured! + + if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. + var/mob/living/L = user + if(!can_trigger_gun(L)) + return + if(flag) + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + handle_suicide(user, target, params) + return + + if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can shoot. + shoot_with_empty_chamber(user) + return + + if(check_botched(user)) + return + + var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands + if(weapon_weight == WEAPON_HEAVY && (user.get_inactive_held_item() || !other_hand)) + to_chat(user, "You need two hands to fire [src]!") + return + //DUAL (or more!) WIELDING + var/bonus_spread = 0 + var/loop_counter = 0 + if(ishuman(user) && user.combat_mode) + var/mob/living/carbon/human/H = user + for(var/obj/item/gun/G in H.held_items) + if(G == src || G.weapon_weight >= WEAPON_MEDIUM) + continue + else if(G.can_trigger_gun(user)) + bonus_spread += dual_wield_spread + loop_counter++ + addtimer(CALLBACK(G, /obj/item/gun.proc/process_fire, target, user, TRUE, params, null, bonus_spread), loop_counter) + + return process_fire(target, user, TRUE, params, null, bonus_spread) + +/obj/item/gun/proc/check_botched(mob/living/user, params) + if(clumsy_check) + if(istype(user)) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40)) + to_chat(user, "You shoot yourself in the foot with [src]!") + var/shot_leg = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + process_fire(user, user, FALSE, params, shot_leg) + SEND_SIGNAL(user, COMSIG_MOB_CLUMSY_SHOOT_FOOT) + user.dropItemToGround(src, TRUE) + return TRUE + +/obj/item/gun/can_trigger_gun(mob/living/user) + . = ..() + if(!handle_pins(user)) + return FALSE + if(has_gun_safety && safety) + to_chat(user, "The safety is on!") + return FALSE + +/obj/item/gun/proc/toggle_safety(mob/user, override) + if(!has_gun_safety) + return + if(override) + if(override == "off") + safety = FALSE + else + safety = TRUE + else + safety = !safety + tsafety.button_icon_state = "safety_[safety ? "on" : "off"]" + tsafety.UpdateButtonIcon() + playsound(src, 'sound/weapons/empty.ogg', 100, TRUE) + user.visible_message("[user] toggles [src]'s safety [safety ? "ON" : "OFF"].", + "You toggle [src]'s safety [safety ? "ON" : "OFF"].") + +/obj/item/gun/proc/handle_pins(mob/living/user) + if(pin) + if(pin.pin_auth(user) || (pin.obj_flags & EMAGGED)) + return TRUE + else + pin.auth_fail(user) + return FALSE + else + to_chat(user, "[src]'s trigger is locked. This weapon doesn't have a firing pin installed!") + return FALSE + +/obj/item/gun/proc/recharge_newshot() + return + +/obj/item/gun/proc/process_burst(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0) + if(!user || !firing_burst) + firing_burst = FALSE + return FALSE + if(!issilicon(user)) + if(iteration > 1 && !(user.is_holding(src))) //for burst firing + firing_burst = FALSE + return FALSE + if(chambered?.loaded_projectile) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...") + return + if(randomspread) + sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + else //Smart spread + sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target,user) + if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd, src)) + shoot_with_empty_chamber(user) + firing_burst = FALSE + return FALSE + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + if (iteration >= burst_size) + firing_burst = FALSE + else + shoot_with_empty_chamber(user) + firing_burst = FALSE + return FALSE + process_chamber() + update_appearance() + return TRUE + +/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + if(user) + if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex + target = pick(orange(2, target)) + SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override) + + SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override) + + add_fingerprint(user) + + if(semicd) + return + + var/sprd = 0 + var/randomized_gun_spread = 0 + var/rand_spr = rand() + if(spread) + randomized_gun_spread = rand(0,spread) + var/randomized_bonus_spread = rand(0, bonus_spread) + + if(burst_size > 1) + firing_burst = TRUE + for(var/i = 1 to burst_size) + addtimer(CALLBACK(src, .proc/process_burst, user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i), fire_delay * (i - 1)) + else + if(chambered) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...") + return + sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target,user) + if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src)) + shoot_with_empty_chamber(user) + return + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + else + shoot_with_empty_chamber(user) + return + process_chamber() + update_appearance() + semicd = TRUE + addtimer(CALLBACK(src, .proc/reset_semicd), fire_delay) + + if(user) + user.update_inv_hands() + SSblackbox.record_feedback("tally", "gun_fired", 1, type) + + return TRUE + +/obj/item/gun/proc/reset_semicd() + semicd = FALSE + +/obj/item/gun/attack(mob/M, mob/living/user) + if(user.combat_mode) //Flogging + if(bayonet) + M.attackby(bayonet, user) + return + else + return ..() + return + +/obj/item/gun/attack_obj(obj/O, mob/living/user, params) + if(user.combat_mode) + if(bayonet) + O.attackby(bayonet, user) + return + return ..() + +/obj/item/gun/attackby(obj/item/I, mob/living/user, params) + if(user.combat_mode) + return ..() + else if(istype(I, /obj/item/flashlight/seclite)) + if(!can_flashlight) + return ..() + var/obj/item/flashlight/seclite/S = I + if(!gun_light) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You click [S] into place on [src].") + set_gun_light(S) + update_gunlight() + alight = new(src) + if(loc == user) + alight.Grant(user) + else if(istype(I, /obj/item/kitchen/knife)) + var/obj/item/kitchen/knife/K = I + if(!can_bayonet || !K.bayonet || bayonet) //ensure the gun has an attachment point available, and that the knife is compatible with it. + return ..() + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You attach [K] to [src]'s bayonet lug.") + bayonet = K + update_appearance() + + else + return ..() + +/obj/item/gun/screwdriver_act(mob/living/user, obj/item/I) + . = ..() + if(.) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if((can_flashlight && gun_light) && (can_bayonet && bayonet)) //give them a choice instead of removing both + var/list/possible_items = list(gun_light, bayonet) + var/obj/item/item_to_remove = input(user, "Select an attachment to remove", "Attachment Removal") as null|obj in sortNames(possible_items) + if(!item_to_remove || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + return remove_gun_attachment(user, I, item_to_remove) + + else if(gun_light && can_flashlight) //if it has a gun_light and can_flashlight is false, the flashlight is permanently attached. + return remove_gun_attachment(user, I, gun_light, "unscrewed") + + else if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed + return remove_gun_attachment(user, I, bayonet, "unfix") + + else if(pin && user.is_holding(src)) + user.visible_message("[user] attempts to remove [pin] from [src] with [I].", + "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3) + if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50)) + if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay + return + user.visible_message("[pin] is pried out of [src] by [user], destroying the pin in the process.", + "You pry [pin] out with [I], destroying the pin in the process.", null, 3) + QDEL_NULL(pin) + return TRUE + +/obj/item/gun/welder_act(mob/living/user, obj/item/I) + . = ..() + if(.) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(pin && user.is_holding(src)) + user.visible_message("[user] attempts to remove [pin] from [src] with [I].", + "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3) + if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, 5, volume = 50)) + if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay + return + user.visible_message("[pin] is spliced out of [src] by [user], melting part of the pin in the process.", + "You splice [pin] out of [src] with [I], melting part of the pin in the process.", null, 3) + QDEL_NULL(pin) + return TRUE + +/obj/item/gun/wirecutter_act(mob/living/user, obj/item/I) + . = ..() + if(.) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(pin && user.is_holding(src)) + user.visible_message("[user] attempts to remove [pin] from [src] with [I].", + "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3) + if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50)) + if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay + return + user.visible_message("[pin] is ripped out of [src] by [user], mangling the pin in the process.", + "You rip [pin] out of [src] with [I], mangling the pin in the process.", null, 3) + QDEL_NULL(pin) + return TRUE + +/obj/item/gun/proc/remove_gun_attachment(mob/living/user, obj/item/tool_item, obj/item/item_to_remove, removal_verb) + if(tool_item) + tool_item.play_tool_sound(src) + to_chat(user, "You [removal_verb ? removal_verb : "remove"] [item_to_remove] from [src].") + item_to_remove.forceMove(drop_location()) + + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(item_to_remove) + + if(item_to_remove == bayonet) + return clear_bayonet() + else if(item_to_remove == gun_light) + return clear_gunlight() + +/obj/item/gun/proc/clear_bayonet() + if(!bayonet) + return + bayonet = null + update_appearance() + return TRUE + +/obj/item/gun/proc/clear_gunlight() + if(!gun_light) + return + var/obj/item/flashlight/seclite/removed_light = gun_light + set_gun_light(null) + update_gunlight() + removed_light.update_brightness() + QDEL_NULL(alight) + return TRUE + + +/** + * Swaps the gun's seclight, dropping the old seclight if it has not been qdel'd. + * + * Returns the former gun_light that has now been replaced by this proc. + * Arguments: + * * new_light - The new light to attach to the weapon. Can be null, which will mean the old light is removed with no replacement. + */ +/obj/item/gun/proc/set_gun_light(obj/item/flashlight/seclite/new_light) + // Doesn't look like this should ever happen? We're replacing our old light with our old light? + if(gun_light == new_light) + CRASH("Tried to set a new gun light when the old gun light was also the new gun light.") + + . = gun_light + + // If there's an old gun light that isn't being QDELETED, detatch and drop it to the floor. + if(!QDELETED(gun_light)) + gun_light.set_light_flags(gun_light.light_flags & ~LIGHT_ATTACHED) + if(gun_light.loc == src) + gun_light.forceMove(get_turf(src)) + + // If there's a new gun light to be added, attach and move it to the gun. + if(new_light) + new_light.set_light_flags(new_light.light_flags | LIGHT_ATTACHED) + if(new_light.loc != src) + new_light.forceMove(src) + + gun_light = new_light + +/obj/item/gun/ui_action_click(mob/user, actiontype) + if(istype(actiontype, alight)) + toggle_gunlight() + else + ..() + +/obj/item/gun/proc/toggle_gunlight() + if(!gun_light) + return + + var/mob/living/carbon/human/user = usr + gun_light.on = !gun_light.on + gun_light.update_brightness() + to_chat(user, "You toggle the gunlight [gun_light.on ? "on":"off"].") + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_gunlight() + +/obj/item/gun/proc/update_gunlight() + update_appearance() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/gun/pickup(mob/user) + . = ..() + if(azoom) + azoom.Grant(user) + if(w_class > WEIGHT_CLASS_SMALL && !suppressed) + user.visible_message("[user] grabs [src]!", + "You grab [src]!") + +/obj/item/gun/dropped(mob/user) + . = ..() + if(azoom) + azoom.Remove(user) + if(zoomed) + zoom(user, user.dir) + +/obj/item/gun/update_overlays() + . = ..() + if(gun_light) + var/mutable_appearance/flashlight_overlay + var/state = "[gunlight_state][gun_light.on? "_on":""]" //Generic state. + if(gun_light.icon_state in icon_states('icons/obj/guns/flashlights.dmi')) //Snowflake state? + state = gun_light.icon_state + flashlight_overlay = mutable_appearance('icons/obj/guns/flashlights.dmi', state) + flashlight_overlay.pixel_x = flight_x_offset + flashlight_overlay.pixel_y = flight_y_offset + . += flashlight_overlay + + if(bayonet) + var/mutable_appearance/knife_overlay + var/state = "bayonet" //Generic state. + if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state? + state = bayonet.icon_state + var/icon/bayonet_icons = 'icons/obj/guns/bayonets.dmi' + knife_overlay = mutable_appearance(bayonet_icons, state) + knife_overlay.pixel_x = knife_x_offset + knife_overlay.pixel_y = knife_y_offset + . += knife_overlay + +/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer) + if(!ishuman(user) || !ishuman(target)) + return + + if(semicd) + return + + if(user == target) + target.visible_message("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...", \ + "You stick [src] in your mouth, ready to pull the trigger...") + else + target.visible_message("[user] points [src] at [target]'s head, ready to pull the trigger...", \ + "[user] points [src] at your head, ready to pull the trigger...") + + semicd = TRUE + + if(!bypass_timer && (!do_mob(user, target, 120) || user.zone_selected != BODY_ZONE_PRECISE_MOUTH)) + if(user) + if(user == target) + user.visible_message("[user] decided not to shoot.") + else if(target?.Adjacent(user)) + target.visible_message("[user] has decided to spare [target]", "[user] has decided to spare your life!") + semicd = FALSE + return + + semicd = FALSE + + target.visible_message("[user] pulls the trigger!", "[(user == target) ? "You pull" : "[user] pulls"] the trigger!") + + if(chambered?.loaded_projectile) + chambered.loaded_projectile.damage *= 5 + if(chambered.loaded_projectile.wound_bonus != CANT_WOUND) + chambered.loaded_projectile.wound_bonus += 5 // much more dramatic on multiple pellet'd projectiles really + + var/fired = process_fire(target, user, TRUE, params, BODY_ZONE_HEAD) + if(!fired && chambered?.loaded_projectile) + chambered.loaded_projectile.damage /= 5 + if(chambered.loaded_projectile.wound_bonus != CANT_WOUND) + chambered.loaded_projectile.wound_bonus -= 5 + +/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins + if(pin) + qdel(pin) + pin = new /obj/item/firing_pin + +//Happens before the actual projectile creation +/obj/item/gun/proc/before_firing(atom/target,mob/user) + return + +///////////// +// ZOOMING // +///////////// + +/datum/action/toggle_scope_zoom + name = "Toggle Scope" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_LYING + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + var/obj/item/gun/gun = null + +/datum/action/toggle_scope_zoom/Trigger() + gun.zoom(owner, owner.dir) + +/datum/action/toggle_scope_zoom/IsAvailable() + . = ..() + if(!. && gun) + gun.zoom(owner, owner.dir, FALSE) + +/datum/action/toggle_scope_zoom/Remove(mob/living/L) + gun.zoom(L, L.dir, FALSE) + ..() + +/obj/item/gun/proc/rotate(atom/thing, old_dir, new_dir) + SIGNAL_HANDLER + + if(ismob(thing)) + var/mob/lad = thing + lad.client.view_size.zoomOut(zoom_out_amt, zoom_amt, new_dir) + +/obj/item/gun/proc/zoom(mob/living/user, direc, forced_zoom) + if(!user || !user.client) + return + + if(isnull(forced_zoom)) + zoomed = !zoomed + else + zoomed = forced_zoom + + if(zoomed) + RegisterSignal(user, COMSIG_ATOM_DIR_CHANGE, .proc/rotate) + user.client.view_size.zoomOut(zoom_out_amt, zoom_amt, direc) + else + UnregisterSignal(user, COMSIG_ATOM_DIR_CHANGE) + user.client.view_size.zoomIn() + return zoomed + +//Proc, so that gun accessories/scopes/etc. can easily add zooming. +/obj/item/gun/proc/build_zooming() + if(azoom) + return + + if(zoomable) + azoom = new() + azoom.gun = src + +#undef FIRING_PIN_REMOVAL_DELAY +#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER diff --git a/modular_skyrat/master_files/icons/mob/actions/actions_items.dmi b/modular_skyrat/master_files/icons/mob/actions/actions_items.dmi new file mode 100644 index 0000000000000000000000000000000000000000..115d5d71dad64b283abe7b6f1fa149a962ec01d8 GIT binary patch literal 637 zcmV-@0)qXCP)Bt9=+iHkEOv#1y-Zpg)%R+N~V3SnbY zZAgk@BT^I_<5H}w;OgfB_9Xx?TSaWEZC&O700DtXL_t(YiS3jMzmF=#CYnQnzZyS5;M$<(POTj%`BN(q*9{z|7T?Jf4nuO6Lb-HXsU zBG7Nn;CCm|uVFB#(8HFrgGd$#VWO2@k&_G{ZZj99JZ=Fme8WrR8o_;pXhw-%iWOMM z0RzF|by=p5x&mJjlCsnSH;x)X!<#nu*jiB12rYQv5)ns>Hb2zh+JwZ3G6Rsiz;YLz zja-eKfjNueLo|YsQ~vc5@-Ivy!n@`jAiTPOfEniiaq+Z!wgCYq1pMqdZD0jQ*=PlU zt?P&WFormJ-LsE2 zV@0lJFsp9Z-JiQJHA6m*xnI3Po@n0G4EZ?bm)m_l-%pe=OF8P9Og~^59mH;O>Yt1s X-d0ydl*Xl=00000NkvXXu0mjfI7AXP literal 0 HcmV?d00001 diff --git a/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm b/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm index 9051036723e..4693970a072 100644 --- a/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm +++ b/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm @@ -48,7 +48,6 @@ can_suppress = FALSE burst_size = 5 spread = 3 - actions_types = list(/datum/action/item_action/toggle_firemode) mag_display = TRUE mag_display_ammo = TRUE realistic = TRUE @@ -86,7 +85,6 @@ can_suppress = FALSE burst_size = 8 spread = 5 - actions_types = list(/datum/action/item_action/toggle_firemode) mag_display = TRUE mag_display_ammo = FALSE realistic = TRUE diff --git a/modular_skyrat/modules/aesthetics/guns/code/guns.dm b/modular_skyrat/modules/aesthetics/guns/code/guns.dm index 964843550b9..18f2a14471a 100644 --- a/modular_skyrat/modules/aesthetics/guns/code/guns.dm +++ b/modular_skyrat/modules/aesthetics/guns/code/guns.dm @@ -142,12 +142,12 @@ /obj/item/ammo_casing/energy/laser/accelerator fire_sound = 'modular_skyrat/modules/aesthetics/guns/sound/laser_cannon_fire.ogg' - + /obj/item/gun/ballistic/automatic/sniper_rifle icon = 'modular_skyrat/modules/aesthetics/guns/icons/guns_gubman2.dmi' icon_state = "sniper" fire_delay = 60 - + /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate icon = 'modular_skyrat/modules/aesthetics/guns/icons/guns_gubman2.dmi' icon_state = "sniper2" @@ -198,7 +198,7 @@ zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you. zoom_out_amt = 5 slot_flags = ITEM_SLOT_BACK - actions_types = list() + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE mag_display = TRUE /obj/item/gun/ballistic/automatic/ar/modular @@ -223,6 +223,6 @@ w_class = WEIGHT_CLASS_NORMAL can_suppress = FALSE weapon_weight = WEAPON_HEAVY - + /obj/item/gun/energy/kinetic_accelerator icon = 'modular_skyrat/modules/aesthetics/guns/icons/energy.dmi' diff --git a/modular_skyrat/modules/gunsafety/code/gun_safety.dm b/modular_skyrat/modules/gunsafety/code/gun_safety.dm index 0e6cf38d1df..afd73605369 100644 --- a/modular_skyrat/modules/gunsafety/code/gun_safety.dm +++ b/modular_skyrat/modules/gunsafety/code/gun_safety.dm @@ -1,58 +1,3 @@ -//WATCH YOUR TRIGGER FINGER FRANK -/obj/item/gun - var/safety = FALSE //Internal variable for keeping track whether the safety is on or off - var/has_gun_safety = FALSE //Whether the gun actually has a gun safety - var/datum/action/item_action/toggle_safety/tsafety - -/obj/item/gun/Initialize() - . = ..() - if(has_gun_safety) - safety = TRUE - tsafety = new(src) - -/obj/item/gun/Destroy() - if(tsafety) - QDEL_NULL(tsafety) - return ..() - -/datum/action/item_action/toggle_safety - name = "Toggle Safety" - icon_icon = 'modular_skyrat/modules/gunsafety/icons/hud/actions.dmi' - button_icon_state = "safety_on" - -/obj/item/gun/ui_action_click(mob/user, actiontype) - if(istype(actiontype, tsafety)) - toggle_safety(user) - else - ..() - -/obj/item/gun/proc/toggle_safety(mob/user, override) - if(!has_gun_safety) - return - if(override) - if(override == "off") - safety = FALSE - else - safety = TRUE - else - safety = !safety - tsafety.button_icon_state = "safety_[safety ? "on" : "off"]" - tsafety.UpdateButtonIcon() - playsound(src, 'sound/weapons/empty.ogg', 100, TRUE) - user.visible_message("[user] toggles [src]'s safety [safety ? "ON" : "OFF"].", - "You toggle [src]'s safety [safety ? "ON" : "OFF"].") - -/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) - if(has_gun_safety) - if(safety) - to_chat(user, "The safety is on!") - return - . = ..() - -/obj/item/gun/examine(mob/user) - . = ..() - . += "The safety is [safety ? "ON" : "OFF"]." - /obj/item/gun/ballistic has_gun_safety = TRUE @@ -64,6 +9,6 @@ /obj/item/gun/energy/kinetic_accelerator/minebot has_gun_safety = FALSE - + /obj/item/gun/ballistic/rifle/enchanted has_gun_safety = FALSE diff --git a/modular_skyrat/modules/gunsgalore/code/guns/master.dm b/modular_skyrat/modules/gunsgalore/code/guns/master.dm index a05e331dbde..d08239e5389 100644 --- a/modular_skyrat/modules/gunsgalore/code/guns/master.dm +++ b/modular_skyrat/modules/gunsgalore/code/guns/master.dm @@ -11,12 +11,19 @@ var/durability = 100 //How used this gun is. var/durability_factor = 0.1 //How quickly a gun will degrade. 0.1 = 1000 shots. Edit this to change a guns base reliability. + /obj/item/gun/ballistic/Initialize() . = ..() if(realistic) base_spread = spread +/obj/item/gun/ballistic/ComponentInitialize() + . = ..() + if(alt_icons) + AddElement(/datum/element/update_icon_updates_onmob) + /obj/item/gun/ballistic/update_overlays() + . = ..() if(alt_icons) if(!magazine) if(alt_icon_state) @@ -32,14 +39,6 @@ else inhand_icon_state = "[initial(icon_state)]" worn_icon_state = "[initial(icon_state)]" - . = ..() - -//gun pickup message -/obj/item/gun/pickup(mob/user) - . = ..() - if(w_class > WEIGHT_CLASS_SMALL && !suppressed) - user.visible_message("[user] grabs [src]!", - "You grab [src]!") /obj/item/gun/ballistic/assault_rifle rack_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_cock.ogg' @@ -76,11 +75,6 @@ eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magout.ogg' eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magout.ogg' -/obj/item/gun/ballistic/ComponentInitialize() - if(alt_icons) - AddElement(/datum/element/update_icon_updates_onmob) - . = ..() - /obj/item/gun/ballistic/proc/jam(unjam = FALSE, mob/living/user) if(unjam && jammed != TRUE) unjam_time = clamp((jam_chance*10)/(durability/10), 0, 50) @@ -94,31 +88,11 @@ to_chat(user, "You unjam the [src]'s bolt.") playsound(src, 'sound/weapons/gun/l6/l6_rack.ogg', 60, TRUE) -/obj/item/gun/ballistic/can_shoot() - if(realistic) - if(jammed) - return FALSE - else - . = ..() - else - . = ..() - -/obj/item/gun/ballistic/process_burst(mob/living/user, atom/target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, iteration) - if(realistic) - if(jammed) - firing_burst = FALSE - return FALSE - else - . = ..() - else - . = ..() - -/obj/item/gun/ballistic/process_fire() - if(realistic) - if(jammed) - shoot_with_empty_chamber() - return +/obj/item/gun/ballistic/can_trigger_gun(mob/living/user) . = ..() + if(realistic && jammed) + to_chat(user, "[src] is jammed!") + return FALSE /obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message) if(realistic) @@ -161,17 +135,6 @@ return . = ..() -/obj/item/gun/ballistic/afterattack(atom/target as mob|obj|turf, mob/living/user as mob|obj, flag, params) - if(realistic) - if(jammed) - to_chat(user, "[src] is jammed, unjam it before firing!") - return - else - . = ..() - update_icon() - else - . = ..() - /obj/item/gun/ballistic/examine(mob/user) . = ..() if(realistic) diff --git a/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm b/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm index 26968c41cca..df448564a23 100644 --- a/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm +++ b/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm @@ -17,6 +17,7 @@ fire_sound_volume = 70 alt_icons = TRUE realistic = TRUE + fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT, SELECT_FULLY_AUTOMATIC) /obj/item/ammo_box/magazine/stg name = "stg magazine (7.92×33mm)" diff --git a/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm b/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm index 065ea423fdd..a74a718d09b 100644 --- a/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm +++ b/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm @@ -132,7 +132,7 @@ fire_delay = 5 can_suppress = FALSE burst_size = 0 - actions_types = list() + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE mag_display = FALSE mag_display_ammo = FALSE empty_indicator = FALSE diff --git a/modular_skyrat/modules/sec_haul/code/guns/guns.dm b/modular_skyrat/modules/sec_haul/code/guns/guns.dm index 15b968f8ade..b49ea33c377 100644 --- a/modular_skyrat/modules/sec_haul/code/guns/guns.dm +++ b/modular_skyrat/modules/sec_haul/code/guns/guns.dm @@ -73,7 +73,6 @@ burst_size = 3 fire_delay = 1 spread = 10 - actions_types = list(/datum/action/item_action/toggle_firemode) realistic = TRUE mag_display = FALSE mag_display_ammo = FALSE @@ -167,7 +166,6 @@ burst_size = 3 fire_delay = 1 spread = 5 - actions_types = list(/datum/action/item_action/toggle_firemode) realistic = TRUE dirt_modifier = 0.1 can_flashlight = TRUE @@ -391,7 +389,6 @@ can_suppress = FALSE burst_size = 3 spread = 10 - actions_types = list(/datum/action/item_action/toggle_firemode) can_bayonet = FALSE mag_display = TRUE mag_display_ammo = TRUE @@ -440,7 +437,6 @@ burst_size = 3 fire_delay = 3 spread = 15 - actions_types = list(/datum/action/item_action/toggle_firemode) mag_display = TRUE mag_display_ammo = TRUE realistic = TRUE @@ -489,7 +485,6 @@ fire_delay = 0.7 can_suppress = FALSE burst_size = 4 - actions_types = list(/datum/action/item_action/toggle_firemode) mag_display = TRUE mag_display_ammo = TRUE realistic = TRUE @@ -538,7 +533,7 @@ spread = 20 mag_display = FALSE mag_display_ammo = FALSE - actions_types = list(/datum/action/item_action/toggle_firemode) + fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT) realistic = TRUE dirt_modifier = 1.7 emp_damageable = TRUE @@ -578,7 +573,7 @@ mag_display_ammo = FALSE burst_size = 1 fire_delay = 0 - actions_types = list() + fire_select_modes = list(SELECT_SEMI_AUTOMATIC) fire_sound = 'sound/weapons/gun/rifle/shot.ogg' rack_sound = 'sound/weapons/gun/smg/smgrack.ogg' lock_back_sound = 'sound/weapons/gun/pistol/slide_lock.ogg' @@ -641,6 +636,7 @@ fire_sound = 'modular_skyrat/modules/sec_haul/sound/ltrifle_fire.ogg' emp_damageable = TRUE armadyne = TRUE + fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT) /obj/item/ammo_box/magazine/multi_sprite/norwind name = "norwind dmr magazine (12mm)" @@ -675,7 +671,6 @@ inhand_icon_state = "vintorez" mag_type = /obj/item/ammo_box/magazine/multi_sprite/vintorez can_suppress = FALSE - actions_types = list(/datum/action/item_action/toggle_firemode) can_bayonet = FALSE mag_display = FALSE mag_display_ammo = FALSE @@ -729,7 +724,6 @@ fire_delay = 1.7 can_suppress = FALSE burst_size = 3 - actions_types = list(/datum/action/item_action/toggle_firemode) can_bayonet = FALSE mag_display = TRUE mag_display_ammo = TRUE diff --git a/tgstation.dme b/tgstation.dme index 85a675e9dc9..6f14bde0835 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -155,6 +155,7 @@ #include "code\__DEFINES\~skyrat_defines\economy.dm" #include "code\__DEFINES\~skyrat_defines\examine_defines.dm" #include "code\__DEFINES\~skyrat_defines\faction.dm" +#include "code\__DEFINES\~skyrat_defines\gun.dm" #include "code\__DEFINES\~skyrat_defines\integrated_electronics.dm" #include "code\__DEFINES\~skyrat_defines\inventory.dm" #include "code\__DEFINES\~skyrat_defines\jobs.dm" @@ -3492,6 +3493,7 @@ #include "modular_skyrat\master_files\code\controllers\processing\circuit.dm" #include "modular_skyrat\master_files\code\controllers\subsystem\statpanel.dm" #include "modular_skyrat\master_files\code\datums\ert.dm" +#include "modular_skyrat\master_files\code\datums\components\fullauto.dm" #include "modular_skyrat\master_files\code\datums\id_trim\jobs.dm" #include "modular_skyrat\master_files\code\datums\id_trim\syndicate.dm" #include "modular_skyrat\master_files\code\datums\martial\cqcplus.dm" @@ -3518,6 +3520,8 @@ #include "modular_skyrat\master_files\code\modules\mob\living\carbon\carbon_say.dm" #include "modular_skyrat\master_files\code\modules\mob\living\simple_animal\friendly\bumbles.dm" #include "modular_skyrat\master_files\code\modules\power\lighting.dm" +#include "modular_skyrat\master_files\code\modules\projectiles\guns\gun.dm" +#include "modular_skyrat\master_files\code\modules\projectiles\guns\ballistic\automatic.dm" #include "modular_skyrat\master_files\code\modules\uplink\uplink_items.dm" #include "modular_skyrat\modules\3516\code\3516.dm" #include "modular_skyrat\modules\3516\code\bodypart_autosurgeon.dm"