From 7fa44a31fb522fcb62ae4d5f4cca8707f1d99cf9 Mon Sep 17 00:00:00 2001 From: Mikhail Dzianishchyts Date: Tue, 23 Apr 2024 00:32:49 +0300 Subject: [PATCH] Full Auto Gun Mode Component (#24998) * Full Auto Gun Mode Component * Apply some refactoring * spaces mhmm * Some signal movement * Apply DGamerL suggestions * Unknown SIGNAL_HANDLER no more * Fix runtime if uid is passed * Why the hell you even care * god you serious --------- Co-authored-by: Gottfrei --- code/__DEFINES/combat_defines.dm | 7 + code/__DEFINES/dcs/signals.dm | 22 +- code/__DEFINES/is_helpers.dm | 4 + code/__HELPERS/mob_helpers.dm | 8 +- code/__HELPERS/unsorted.dm | 21 ++ code/_onclick/drag_drop.dm | 69 +++- code/datums/components/fullauto.dm | 325 ++++++++++++++++++ code/datums/components/shielded.dm | 2 +- code/datums/components/squeak.dm | 2 +- code/game/gamemodes/cult/blood_magic.dm | 2 +- code/game/gamemodes/cult/cult_items.dm | 2 +- code/game/machinery/deployable.dm | 4 +- code/game/machinery/shieldgen.dm | 4 +- code/game/machinery/transformer.dm | 2 +- code/game/objects/effects/spiders.dm | 2 +- .../objects/items/devices/taperecorder.dm | 2 +- code/game/objects/items/his_grace.dm | 2 +- code/game/objects/items/sport.dm | 4 +- .../objects/items/weapons/flamethrower.dm | 2 +- .../objects/items/weapons/holy_weapons.dm | 2 +- .../weapons/melee/energy_melee_weapons.dm | 2 +- code/game/objects/items/weapons/shields.dm | 2 +- .../items/weapons/storage/briefcase.dm | 2 +- code/game/objects/structures/fence.dm | 2 +- code/game/objects/structures/girders.dm | 2 +- code/game/objects/structures/grille.dm | 2 +- code/game/objects/structures/railings.dm | 4 +- code/modules/client/client_defines.dm | 24 ++ code/modules/clothing/head/misc_special.dm | 2 +- code/modules/clothing/suits/armor_suits.dm | 2 +- .../clothing/under/accessories/holster.dm | 2 +- .../food_and_drinks/drinks/drinks/cans.dm | 2 +- .../mining/lavaland/loot/tendril_loot.dm | 2 +- .../mining/lavaland/necropolis_chests.dm | 2 +- .../mob/living/carbon/human/human_mob.dm | 3 +- .../hostile/megafauna/hierophant.dm | 2 +- .../hostile/terror_spiders/actions.dm | 2 +- code/modules/mob/mob_movement.dm | 2 +- .../power/engines/singularity/singularity.dm | 2 +- code/modules/projectiles/gun.dm | 4 +- code/modules/projectiles/guns/energy_guns.dm | 2 +- .../projectiles/guns/projectile_gun.dm | 4 +- .../projectile/magic_projectiles.dm | 2 +- code/modules/recycling/disposal.dm | 2 +- code/modules/recycling/sortingmachinery.dm | 2 +- .../research/xenobiology/xenobiology.dm | 2 +- code/modules/surgery/organs/augments_arms.dm | 2 +- .../effects/mouse_pointers/weapon_pointer.dmi | Bin 0 -> 234 bytes paradise.dme | 1 + 49 files changed, 521 insertions(+), 51 deletions(-) create mode 100644 code/datums/components/fullauto.dm create mode 100644 icons/effects/mouse_pointers/weapon_pointer.dmi diff --git a/code/__DEFINES/combat_defines.dm b/code/__DEFINES/combat_defines.dm index ee2ec5345af..01e0c8990c7 100644 --- a/code/__DEFINES/combat_defines.dm +++ b/code/__DEFINES/combat_defines.dm @@ -157,3 +157,10 @@ #define STATUS_EFFECT_CONSTANT * 20 #define IS_HORIZONTAL(x) x.body_position + +/// Compatible firemode is in the gun. Wait until it's held in the user hands. +#define AUTOFIRE_STAT_IDLE (1<<0) +/// Gun is active and in the user hands. Wait until user does a valid click. +#define AUTOFIRE_STAT_ALERT (1<<1) +/// Gun is shooting. +#define AUTOFIRE_STAT_FIRING (1<<2) diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 05150f92552..585f6c5365f 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -219,8 +219,17 @@ ///////////////// -///from base of client/Click(): (atom/target, atom/location, control, params, mob/user) +// /client signals + +/// from base of client/Click(): (atom/target, atom/location, control, params, mob/user) #define COMSIG_CLIENT_CLICK "atom_client_click" +/// from base of client/MouseDown(): (/client, object, location, control, params) +#define COMSIG_CLIENT_MOUSEDOWN "client_mousedown" +/// from base of client/MouseUp(): (/client, object, location, control, params) +#define COMSIG_CLIENT_MOUSEUP "client_mouseup" + #define COMPONENT_CLIENT_MOUSEUP_INTERCEPT (1<<0) +/// from base of client/MouseUp(): (/client, object, location, control, params) +#define COMSIG_CLIENT_MOUSEDRAG "client_mousedrag" ///from base of area/Entered(): (/area) #define COMSIG_ENTER_AREA "enter_area" @@ -708,11 +717,16 @@ // /obj/item/gun signals -///called in /obj/item/gun/process_fire (user, target, params, zone_override) +/// called in /obj/item/gun/process_fire (user, target, params, zone_override) #define COMSIG_MOB_FIRED_GUN "mob_fired_gun" - -///called in /obj/item/gun/process_fire (user, target) +/// called in /obj/item/gun/process_fire (user, target) #define COMSIG_GUN_FIRED "gun_fired" +/// called in /datum/component/automatic_fire/proc/on_mouse_down: (client/clicker, atom/target, turf/location, control, params) +#define COMSIG_AUTOFIRE_ONMOUSEDOWN "autofire_onmousedown" + #define COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS (1<<0) +/// called in /datum/component/automatic_fire/proc/process_shot(): (atom/target, mob/living/shooter, allow_akimbo, params) +#define COMSIG_AUTOFIRE_SHOT "autofire_shot" + #define COMPONENT_AUTOFIRE_SHOT_SUCCESS (1<<0) // /obj/item/grenade signals diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 4f808a9ad0a..62d3df6514e 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -58,6 +58,10 @@ #define isclothing(A) (istype(A, /obj/item/clothing)) +#define isprojectile(A) (istype(A, /obj/item/projectile)) + +#define isgun(A) (istype(A, /obj/item/gun)) + #define is_pen(W) (istype(W, /obj/item/pen) || istype(W, /obj/item/flashlight/pen)) #define is_pda(W) (istype(W, /obj/item/pda)) diff --git a/code/__HELPERS/mob_helpers.dm b/code/__HELPERS/mob_helpers.dm index 42a2c024560..cb4632129d9 100644 --- a/code/__HELPERS/mob_helpers.dm +++ b/code/__HELPERS/mob_helpers.dm @@ -609,7 +609,7 @@ GLOBAL_LIST_EMPTY(do_after_once_tracker) var/viewX var/viewY if(isnum(view)) - var/totalviewrange = 1 + 2 * view + var/totalviewrange = (view < 0 ? -1 : 1) + 2 * view viewX = totalviewrange viewY = totalviewrange else if(istext(view)) @@ -625,6 +625,12 @@ GLOBAL_LIST_EMPTY(do_after_once_tracker) return list(viewX, viewY) +/proc/in_view_range(mob/user, atom/A) + var/list/view_range = getviewsize(user.client.view) + var/turf/source = get_turf(user) + var/turf/target = get_turf(A) + return ISINRANGE(target.x, source.x - view_range[1], source.x + view_range[1]) && ISINRANGE(target.y, source.y - view_range[2], source.y + view_range[2]) + //Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. /proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 579be0eb83a..34a232d4a13 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1951,6 +1951,27 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) pixel_x = initialpixelx pixel_y = initialpixely +/// Returns a turf based on text inputs, original turf and viewing client +/proc/parse_caught_click_modifiers(list/modifiers, turf/origin, client/viewing_client) + if(!modifiers) + return + + var/screen_loc = splittext(modifiers["screen-loc"], ",") + var/list/actual_view = getviewsize(viewing_client ? viewing_client.view : world.view) + var/click_turf_x = splittext(screen_loc[1], ":") + var/click_turf_y = splittext(screen_loc[2], ":") + var/click_turf_z = origin.z + + var/click_turf_px = text2num(click_turf_x[2]) + var/click_turf_py = text2num(click_turf_y[2]) + click_turf_x = origin.x + text2num(click_turf_x[1]) - round(actual_view[1] / 2) - 1 + click_turf_y = origin.y + text2num(click_turf_y[1]) - round(actual_view[2] / 2) - 1 + + var/turf/click_turf = locate(clamp(click_turf_x, 1, world.maxx), clamp(click_turf_y, 1, world.maxy), click_turf_z) + LAZYSET(modifiers, "icon-x", "[(click_turf_px - click_turf.pixel_x) + ((click_turf_x - click_turf.x) * world.icon_size)]") + LAZYSET(modifiers, "icon-y", "[(click_turf_py - click_turf.pixel_y) + ((click_turf_y - click_turf.y) * world.icon_size)]") + return click_turf + /proc/params2turf(scr_loc, turf/origin, client/C) if(!scr_loc) return null diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index 84f87e5bfd5..9f40c657b95 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -47,4 +47,71 @@ actually if you do something in that proc like changing user location or whateve to inform the game this action was expected and its fine */ /atom/proc/MouseDrop_T(atom/dropping, mob/user, params) // return TRUE if you want to prevent us click the object after it - return + return FALSE + +/client/MouseDown(datum/object, location, control, params) + if(QDELETED(object)) // Yep, you can click on qdeleted things before they have time to nullspace. Fun. + return + SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDOWN, object, location, control, params) + if(mouse_down_icon) + mouse_pointer_icon = mouse_down_icon + var/delay = mob.CanMobAutoclick(object, location, params) + if(delay) + selected_target[1] = object + selected_target[2] = params + while(selected_target[1]) + Click(selected_target[1], location, control, selected_target[2]) + sleep(delay) + +/client/MouseUp(object, location, control, params) + if(SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEUP, object, location, control, params) & COMPONENT_CLIENT_MOUSEUP_INTERCEPT) + click_intercept_time = world.time + if(mouse_up_icon) + mouse_pointer_icon = mouse_up_icon + selected_target[1] = null + +/mob/proc/CanMobAutoclick(object, location, params) + return FALSE + +/mob/living/carbon/CanMobAutoclick(atom/object, location, params) + if(!object.IsAutoclickable()) + return FALSE + var/obj/item/active_item = get_active_hand() + return active_item?.CanItemAutoclick(object, location, params) + +/obj/item/proc/CanItemAutoclick(object, location, params) + return FALSE + +/atom/proc/IsAutoclickable() + return TRUE + +/obj/screen/IsAutoclickable() + return FALSE + +/obj/screen/click_catcher/IsAutoclickable() + return TRUE + +// The actual arguments of this: +// MouseDrag(src_object as null|atom in usr.client, +// over_object as null|atom in usr.client, +// src_location as null|turf|text in usr.client, +// over_location as null|turf|text in usr.client, +// src_control as text, over_control as text, params as text) +/client/MouseDrag(src_object, atom/over_object, src_location, turf/over_location, src_control, over_control, params) + var/list/modifiers = params2list(params) + if(!drag_start) // If we're just starting to drag + drag_start = world.time + drag_details = modifiers.Copy() + mouseParams = params + mouse_location_UID = isturf(over_location) ? over_location.UID() : over_location + mouse_object_UID = over_object?.UID() + if(selected_target[1] && over_object?.IsAutoclickable()) + selected_target[1] = over_object + selected_target[2] = params + SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDRAG, src_object, over_object, src_location, over_location, src_control, over_control, params) + return ..() + +/client/MouseDrop(atom/src_object, atom/over_object, atom/src_location, atom/over_location, src_control, over_control, params) + ..() + drag_start = 0 + drag_details = null diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm new file mode 100644 index 00000000000..cfd4da69ce1 --- /dev/null +++ b/code/datums/components/fullauto.dm @@ -0,0 +1,325 @@ +#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 + /// Time between individual shots. + var/autofire_shot_delay = 0.3 SECONDS + /// 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. + var/mouse_status = AUTOFIRE_MOUSEUP + /// Should dual wielding be allowed? + var/allow_akimbo + + /// windup autofire vars + /// Whether the delay between shots increases over time, simulating a spooling weapon + var/windup_autofire = FALSE + /// the reduction to shot delay for windup + var/current_windup_reduction = 0 + /// the percentage of autfire_shot_delay that is added to current_windup_reduction + var/windup_autofire_reduction_multiplier = 0.3 + /// How high of a reduction that current_windup_reduction can reach + var/windup_autofire_cap = 0.3 + /// How long it takes for weapons that have spooled-up to reset back to the original firing speed + var/windup_spindown = 3 SECONDS + /// Timer for tracking the spindown reset timings + var/timerid + COOLDOWN_DECLARE(next_shot_cd) + +/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown, allow_akimbo = TRUE) + . = ..() + if(!isgun(parent)) + return COMPONENT_INCOMPATIBLE + var/obj/item/gun = parent + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(wake_up)) + if(autofire_shot_delay) + src.autofire_shot_delay = autofire_shot_delay + src.allow_akimbo = allow_akimbo + if(windup_autofire) + src.windup_autofire = windup_autofire + src.windup_autofire_reduction_multiplier = windup_autofire_reduction_multiplier + src.windup_autofire_cap = windup_autofire_cap + src.windup_spindown = windup_spindown + if(autofire_stat == AUTOFIRE_STAT_IDLE && ismob(gun.loc)) + var/mob/user = gun.loc + wake_up(src, user) + +/datum/component/automatic_fire/Destroy() + autofire_off() + return ..() + +/datum/component/automatic_fire/process(seconds_per_tick) + if(autofire_stat != AUTOFIRE_STAT_FIRING) + STOP_PROCESSING(SSprojectiles, src) + return + process_shot() + +/datum/component/automatic_fire/proc/wake_up(datum/source, mob/user, slot) + SIGNAL_HANDLER // COMSIG_ITEM_EQUIPPED + + 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 + if(user.is_holding(parent)) + autofire_on(user.client) + +// 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/user_client) + if(autofire_stat != AUTOFIRE_STAT_IDLE) + return + autofire_stat = AUTOFIRE_STAT_ALERT + if(!QDELETED(user_client)) + clicker = user_client + shooter = user_client.mob + RegisterSignal(user_client, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(on_mouse_down)) + if(!QDELETED(shooter)) + RegisterSignal(shooter, COMSIG_MOB_LOGOUT, PROC_REF(autofire_off)) + UnregisterSignal(shooter, COMSIG_MOB_LOGIN) + RegisterSignal(parent, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED), PROC_REF(autofire_off)) + parent.RegisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN, TYPE_PROC_REF(/obj/item/gun, autofire_bypass_check)) + parent.RegisterSignal(parent, COMSIG_AUTOFIRE_SHOT, TYPE_PROC_REF(/obj/item/gun, do_autofire)) + +/datum/component/automatic_fire/proc/autofire_off(datum/source) + SIGNAL_HANDLER // COMSIG_MOB_LOGOUT + + 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 + if(!QDELETED(shooter)) + RegisterSignal(shooter, COMSIG_MOB_LOGIN, PROC_REF(on_client_login)) + UnregisterSignal(shooter, COMSIG_MOB_LOGOUT) + UnregisterSignal(parent, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED)) + 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 // COMSIG_MOB_LOGIN + 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) + SIGNAL_HANDLER // COMSIG_CLIENT_MOUSEDOWN + + var/list/modifiers = params2list(params) // If they're shift+clicking, for example, let's not have them accidentally shoot. + + if(modifiers["shift"]) + return + if(modifiers["ctrl"]) + return + if(modifiers["middle"]) + return + if(modifiers["right"]) + return + if(modifiers["alt"]) + return + if(source.mob.in_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) || istype(_target, /obj/screen)) // 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 = parse_caught_click_modifiers(modifiers, get_turf(source.eye), source) + params = list2params(modifiers) + 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 + INVOKE_ASYNC(src, PROC_REF(start_autofiring)) + +/datum/component/automatic_fire/proc/start_autofiring() + if(autofire_stat == AUTOFIRE_STAT_FIRING) + return + 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_REF(on_mouse_up)) + mouse_status = AUTOFIRE_MOUSEDOWN + + RegisterSignal(shooter, COMSIG_MOB_SWAPPED_HANDS, PROC_REF(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_REF(on_mouse_drag)) + +/datum/component/automatic_fire/proc/on_mouse_up(datum/source, atom/object, turf/location, control, params) + SIGNAL_HANDLER // COMSIG_CLIENT_MOUSEUP + + 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 // COMSIG_MOB_SWAPPED_HANDS + + if(autofire_stat != AUTOFIRE_STAT_FIRING) + 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_SWAPPED_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 // COMSIG_CLIENT_MOUSEDRAG + + 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 = parse_caught_click_modifiers(modifiers, get_turf(source.eye), source) + params = list2params(modifiers) + 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 FALSE + + if(!COOLDOWN_FINISHED(src, next_shot_cd)) + return TRUE + + 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) + var/next_delay = autofire_shot_delay + if(windup_autofire) + next_delay = clamp(next_delay - current_windup_reduction, round(autofire_shot_delay * windup_autofire_cap), autofire_shot_delay) + current_windup_reduction = (current_windup_reduction + round(autofire_shot_delay * windup_autofire_reduction_multiplier)) + timerid = addtimer(CALLBACK(src, PROC_REF(windup_reset), FALSE), windup_spindown, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) + + COOLDOWN_START(src, next_shot_cd, next_delay) + + if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, allow_akimbo, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS) + return TRUE + + stop_autofiring() + return FALSE + +/// Reset for our windup, resetting everything back to initial values after a variable set amount of time (determined by var/windup_spindown). +/datum/component/automatic_fire/proc/windup_reset(deltimer) + current_windup_reduction = initial(current_windup_reduction) + if(deltimer && timerid) + deltimer(timerid) + +// Gun procs. + +/obj/item/gun/proc/on_autofire_start(mob/living/shooter) + if(semicd || shooter.incapacitated() || !can_trigger_gun(shooter)) + return FALSE + + if(!can_shoot()) + shoot_with_empty_chamber(shooter) + return FALSE + + var/other_hand = shooter.get_organ(shooter.hand ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) + if(weapon_weight == WEAPON_HEAVY && (shooter.get_inactive_hand() || !other_hand)) + to_chat(shooter, "You need to use both hands!") + return FALSE + + return TRUE + +/obj/item/gun/proc/autofire_bypass_check(datum/source, client/clicker, atom/target, turf/location, control, params) + SIGNAL_HANDLER // COMSIG_AUTOFIRE_ONMOUSEDOWN + + if(clicker.mob.get_active_hand() != src) + return COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS + +/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, allow_akimbo, params) + SIGNAL_HANDLER // COMSIG_AUTOFIRE_SHOT + + if(semicd || shooter.incapacitated()) + return NONE + + if(!can_shoot()) + shoot_with_empty_chamber(shooter) + return NONE + + INVOKE_ASYNC(src, PROC_REF(do_autofire_shot), source, target, shooter, allow_akimbo, params) + return COMPONENT_AUTOFIRE_SHOT_SUCCESS // All is well, we can continue shooting. + +/obj/item/gun/proc/do_autofire_shot(datum/source, atom/target, mob/living/shooter, allow_akimbo, params) + var/obj/item/gun/akimbo_gun = shooter.get_inactive_hand() + var/bonus_spread = 0 + if(isgun(akimbo_gun) && weapon_weight < WEAPON_MEDIUM && allow_akimbo) + if(akimbo_gun.weapon_weight < WEAPON_MEDIUM && akimbo_gun.can_trigger_gun(shooter)) + bonus_spread = dual_wield_spread + addtimer(CALLBACK(akimbo_gun, TYPE_PROC_REF(/obj/item/gun, process_fire), target, shooter, TRUE, params, null, bonus_spread), 1) + process_fire(target, shooter, TRUE, params, null, bonus_spread) + +#undef AUTOFIRE_MOUSEUP +#undef AUTOFIRE_MOUSEDOWN diff --git a/code/datums/components/shielded.dm b/code/datums/components/shielded.dm index effad26c8fe..adbe78c3b7f 100644 --- a/code/datums/components/shielded.dm +++ b/code/datums/components/shielded.dm @@ -158,7 +158,7 @@ var/charge_loss = 1 // how many charges do we lose - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby if(P.shield_buster) charge_loss = 3 diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm index 7445e577605..c2aef66bcaa 100644 --- a/code/datums/components/squeak.dm +++ b/code/datums/components/squeak.dm @@ -76,7 +76,7 @@ var/obj/item/I = AM if(I.flags & ABSTRACT) return - else if(istype(AM, /obj/item/projectile)) + else if(isprojectile(AM)) var/obj/item/projectile/P = AM if(P.original != parent) return diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm index 30370c7d905..5a62abbd8c1 100644 --- a/code/game/gamemodes/cult/blood_magic.dm +++ b/code/game/gamemodes/cult/blood_magic.dm @@ -546,7 +546,7 @@ var/input_rune_key = tgui_input_list(user, "Choose a rune to teleport to", "Rune to Teleport to", potential_runes) //we know what key they picked var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? - if(QDELETED(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune) + if(QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !actual_selected_rune) return if(HAS_TRAIT(user, TRAIT_FLOORED)) diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm index f72ed0d0b15..f8a3ca79d77 100644 --- a/code/game/gamemodes/cult/cult_items.dm +++ b/code/game/gamemodes/cult/cult_items.dm @@ -466,7 +466,7 @@ if(IS_CULTIST(owner) && !owner.holy_check()) // Cultist holding the shield // Hit by a projectile - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby var/shatter_chance = 0 // Percent chance of the shield shattering on a projectile hit var/threshold // Depends on the damage Type (Brute or Burn) diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm index f3e3025c7ab..63b1325a260 100644 --- a/code/game/machinery/deployable.dm +++ b/code/game/machinery/deployable.dm @@ -56,7 +56,7 @@ /obj/structure/barricade/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff. if(locate(/obj/structure/barricade) in get_turf(mover)) return TRUE - else if(istype(mover, /obj/item/projectile)) + else if(isprojectile(mover)) if(!anchored) return TRUE var/obj/item/projectile/proj = mover @@ -449,7 +449,7 @@ /obj/structure/barricade/dropwall/firewall/Crossed(atom/movable/AM, oldloc) . = ..() - if(!istype(AM, /obj/item/projectile)) + if(!isprojectile(AM)) return var/obj/item/projectile/P = AM P.immolate ++ diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm index 3ad64115567..4010ba26453 100644 --- a/code/game/machinery/shieldgen.dm +++ b/code/game/machinery/shieldgen.dm @@ -547,7 +547,7 @@ if(istype(mover) && mover.checkpass(PASSGLASS)) return prob(20) else - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return prob(10) else return !density @@ -563,7 +563,7 @@ var/mob/living/M = mover if("syndicate" in M.faction) return TRUE - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return FALSE return ..(mover, target, height) diff --git a/code/game/machinery/transformer.dm b/code/game/machinery/transformer.dm index b72b6a7b714..1b84d8faef3 100644 --- a/code/game/machinery/transformer.dm +++ b/code/game/machinery/transformer.dm @@ -223,7 +223,7 @@ sleep(30) /obj/machinery/transformer/xray/proc/scan_rec(obj/item/I) - if(istype(I, /obj/item/gun)) + if(isgun(I)) return TRUE if(istype(I, /obj/item/transfer_valve)) return TRUE diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 08703285cc2..3005942594c 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -47,7 +47,7 @@ if(prob(50)) to_chat(mover, "You get stuck in [src] for a moment.") return FALSE - else if(istype(mover, /obj/item/projectile)) + else if(isprojectile(mover)) return prob(30) return TRUE diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 2e8f73059fd..463a69a18d3 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -75,7 +75,7 @@ /obj/item/taperecorder/attack_hand(mob/user) if(loc == user) if(mytape) - if(user.l_hand != src && user.r_hand != src) + if(!user.is_holding(src)) ..() return eject(user) diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index 7766452893d..12d5a908dce 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -110,7 +110,7 @@ go_rabid() return - if(!(src in list(master.l_hand, master.r_hand))) + if(!master.is_holding(src)) go_rabid() return diff --git a/code/game/objects/items/sport.dm b/code/game/objects/items/sport.dm index a0fe0d7f43b..862cdeecce2 100644 --- a/code/game/objects/items/sport.dm +++ b/code/game/objects/items/sport.dm @@ -46,7 +46,7 @@ . = ..() var/mob/living/carbon/human/M = hit_atom if(ishuman(hit_atom) && (M.wear_suit?.type in suit_types)) - if(M.r_hand == src || M.l_hand == src) + if(M.is_holding(src)) return playsound(src, 'sound/items/dodgeball.ogg', 50, 1) M.KnockDown(6 SECONDS) @@ -86,7 +86,7 @@ return /obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(isitem(AM) && !istype(AM, /obj/item/projectile)) + if(isitem(AM) && !isprojectile(AM)) if(prob(50) || HAS_TRAIT(throwingdatum.thrower, TRAIT_BADASS)) AM.forceMove(get_turf(src)) visible_message("Swish! [AM] lands in [src].") diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm index a16963b648a..67bc3f4994e 100644 --- a/code/game/objects/items/weapons/flamethrower.dm +++ b/code/game/objects/items/weapons/flamethrower.dm @@ -42,7 +42,7 @@ var/turf/location = loc if(ismob(location)) var/mob/M = location - if(M.l_hand == src || M.r_hand == src) + if(M.is_holding(src)) location = M.loc if(isturf(location)) //start a fire if possible igniter.flamethrower_process(location) diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm index 0cf30e2db5e..a117746c570 100644 --- a/code/game/objects/items/weapons/holy_weapons.dm +++ b/code/game/objects/items/weapons/holy_weapons.dm @@ -360,7 +360,7 @@ if(ishuman(loc)) var/mob/living/carbon/human/our_location = loc if(istype(our_location)) - if(src != our_location.l_hand && src != our_location.r_hand) + if(!our_location.is_holding(src)) return if(our_location.Adjacent(attacking_atom)) // with a buddy we deal 12 damage :D our_location.do_attack_animation(attacking_atom, used_item = src) diff --git a/code/game/objects/items/weapons/melee/energy_melee_weapons.dm b/code/game/objects/items/weapons/melee/energy_melee_weapons.dm index 22acf04add8..0313f5ae46a 100644 --- a/code/game/objects/items/weapons/melee/energy_melee_weapons.dm +++ b/code/game/objects/items/weapons/melee/energy_melee_weapons.dm @@ -240,7 +240,7 @@ . = ..() if(!.) // they did not block the attack return - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby if(P.reflectability == REFLECTABILITY_NEVER) //only 1 magic spell does this, but hey, needed owner.visible_message("[owner] blocks [attack_text] with [src]!") diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm index e2d1b0fe3c7..398b6c4d4ed 100644 --- a/code/game/objects/items/weapons/shields.dm +++ b/code/game/objects/items/weapons/shields.dm @@ -86,7 +86,7 @@ return /obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby if(P.shield_buster && active) toggle(owner, TRUE) diff --git a/code/game/objects/items/weapons/storage/briefcase.dm b/code/game/objects/items/weapons/storage/briefcase.dm index ed9a96a2b93..77cc3c0f361 100644 --- a/code/game/objects/items/weapons/storage/briefcase.dm +++ b/code/game/objects/items/weapons/storage/briefcase.dm @@ -43,7 +43,7 @@ /obj/item/storage/briefcase/false_bottomed/afterattack(atom/A, mob/user, flag, params) ..() - if(stored_item && istype(stored_item, /obj/item/gun) && !Adjacent(A)) + if(stored_item && isgun(stored_item) && !Adjacent(A)) var/obj/item/gun/stored_gun = stored_item stored_gun.afterattack(A, user, flag, params) diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm index e544227d383..1a2535814c0 100644 --- a/code/game/objects/structures/fence.dm +++ b/code/game/objects/structures/fence.dm @@ -61,7 +61,7 @@ /obj/structure/fence/CanPass(atom/movable/mover, turf/target) if(istype(mover) && mover.checkpass(PASSFENCE)) return TRUE - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return TRUE if(!density) return TRUE diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 6585976999e..1c0bc9cd224 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -410,7 +410,7 @@ if(istype(mover) && mover.checkpass(PASSGRILLE)) return prob(girderpasschance) else - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return prob(girderpasschance) else return 0 diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index fbc538af4a1..4097219ef38 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -123,7 +123,7 @@ return TRUE if(istype(mover) && mover.checkpass(PASSGRILLE)) return TRUE - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return (prob(30) || !density) /obj/structure/grille/CanPathfindPass(obj/item/card/id/ID, dir, caller, no_id = FALSE) diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index ff67ffde85a..5acfde9058a 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -102,7 +102,7 @@ /obj/structure/railing/CanPass(atom/movable/mover, turf/target) if(istype(mover) && mover.checkpass(PASSFENCE)) return TRUE - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return TRUE if(ismob(mover)) var/mob/living/M = mover @@ -130,7 +130,7 @@ var/mob/living/M = O if(istype(O) && O.checkpass(PASSFENCE)) return TRUE - if(istype(O, /obj/item/projectile)) + if(isprojectile(O)) return TRUE if(istype(M)) if(M.flying || M.floating || (IS_HORIZONTAL(M) && HAS_TRAIT(M, TRAIT_CONTORTED_BODY))) diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index aa06fa2055c..d47f82b5e02 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -62,6 +62,9 @@ var/datum/click_intercept/click_intercept = null + /// Time when the click was intercepted + var/click_intercept_time = 0 + //datum that controls the displaying and hiding of tooltips var/datum/tooltip/tooltips @@ -132,6 +135,27 @@ var/list/active_keybindings = list() /// The client's movement keybindings to directions, which work regardless of modifiers. var/list/movement_kb_dirs = list() + + /// Used to make a special mouse cursor, this one for mouse up icon + var/mouse_up_icon = null + /// Used to make a special mouse cursor, this one for mouse up icon + var/mouse_down_icon = null + /// Used to override the mouse cursor so it doesnt get reset + var/mouse_override_icon = null + + /// Autoclick list of two elements, first being the clicked thing, second being the parameters. + var/list/atom/selected_target[2] + /// Used in MouseDrag to preserve the original mouse click parameters + var/mouseParams = "" + /// Used in MouseDrag to preserve the last mouse-entered location. + var/mouse_location_UID + /// Used in MouseDrag to preserve the last mouse-entered object. + var/mouse_object_UID + /// When we started the currently active drag + var/drag_start = 0 + /// The params we passed at the start of the drag, in list form + var/list/drag_details + /// The client's currently moused over datum, limited to movable and stored as UID var/atom/movable/moused_over diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index f21c1b3fbc3..9cc9981ea28 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -75,7 +75,7 @@ var/turf/location = loc if(ismob(location)) var/mob/living/carbon/human/M = location - if(M.l_hand == src || M.r_hand == src || M.head == src) + if(M.is_holding(src) || M.head == src) location = M.loc if(isturf(location)) diff --git a/code/modules/clothing/suits/armor_suits.dm b/code/modules/clothing/suits/armor_suits.dm index 9a5dda61757..f57c9fecf16 100644 --- a/code/modules/clothing/suits/armor_suits.dm +++ b/code/modules/clothing/suits/armor_suits.dm @@ -476,7 +476,7 @@ /obj/item/clothing/suit/armor/reactive/proc/reaction_check(hitby) if(prob(hit_reaction_chance)) - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby if(istype(P, /obj/item/projectile/ion)) return FALSE diff --git a/code/modules/clothing/under/accessories/holster.dm b/code/modules/clothing/under/accessories/holster.dm index ac798acda90..e31e66f186a 100644 --- a/code/modules/clothing/under/accessories/holster.dm +++ b/code/modules/clothing/under/accessories/holster.dm @@ -36,7 +36,7 @@ to_chat(user, "There is already a [holstered] holstered here!") return - if(!istype(I, /obj/item/gun)) + if(!isgun(I)) to_chat(user, "Only guns can be holstered!") return diff --git a/code/modules/food_and_drinks/drinks/drinks/cans.dm b/code/modules/food_and_drinks/drinks/drinks/cans.dm index c98487ba69c..e797c5ef7b6 100644 --- a/code/modules/food_and_drinks/drinks/drinks/cans.dm +++ b/code/modules/food_and_drinks/drinks/drinks/cans.dm @@ -49,7 +49,7 @@ if(can_opened) to_chat(H, "You can't shake up an already opened drink!") return - if(src == H.l_hand || src == H.r_hand) + if(H.is_holding(src)) can_shake = FALSE addtimer(CALLBACK(src, PROC_REF(reset_shakable)), 1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) to_chat(H, "You start shaking up [src].") diff --git a/code/modules/mining/lavaland/loot/tendril_loot.dm b/code/modules/mining/lavaland/loot/tendril_loot.dm index fc5a2570944..2055b5e736a 100644 --- a/code/modules/mining/lavaland/loot/tendril_loot.dm +++ b/code/modules/mining/lavaland/loot/tendril_loot.dm @@ -57,7 +57,7 @@ /obj/item/shared_storage/attack_self(mob/living/carbon/user) if(!iscarbon(user)) return - if(src == user.l_hand || src == user.r_hand) + if(user.is_holding(src)) bag?.open(user) else ..() diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index bf9bfc53edf..65386ffc6de 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -223,7 +223,7 @@ /obj/item/clothing/head/hooded/berserker/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) if(berserk_active) return - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby if(P.damage_type == STAMINA) return //no disabler rage diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm index 6938a3e3451..59786362d25 100644 --- a/code/modules/mob/living/carbon/human/human_mob.dm +++ b/code/modules/mob/living/carbon/human/human_mob.dm @@ -1448,8 +1448,7 @@ Eyes need to have significantly high darksight to shine unless the mob has the X /mob/living/carbon/human/singularity_pull(S, current_size) ..() if(current_size >= STAGE_THREE) - var/list/handlist = list(l_hand, r_hand) - for(var/obj/item/hand in handlist) + for(var/obj/item/hand in get_both_hands(src)) if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && unEquip(hand)) step_towards(hand, src) to_chat(src, "\The [S] pulls \the [hand] from your grip!") diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 7e98435c073..f59b35b46e2 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -605,7 +605,7 @@ Difficulty: Hard return FALSE if(mover == caster.pulledby) return TRUE - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) var/obj/item/projectile/P = mover if(P.firer == caster) return TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm index 4d4e048ce32..5c1a666f9b1 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm @@ -181,7 +181,7 @@ if(!(M.mobility_flags & MOBILITY_MOVE)) return TRUE return prob(80) - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return prob(20) return ..() diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index f7f5e5a38c8..2f7ac040189 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -6,7 +6,7 @@ if(height==0) return 1 - if(istype(mover, /obj/item/projectile)) + if(isprojectile(mover)) return projectile_hit_check(mover) if(mover.throwing) return (!density || horizontal || (mover.throwing.thrower == src)) diff --git a/code/modules/power/engines/singularity/singularity.dm b/code/modules/power/engines/singularity/singularity.dm index 3fcf4600a74..f9da157db0d 100644 --- a/code/modules/power/engines/singularity/singularity.dm +++ b/code/modules/power/engines/singularity/singularity.dm @@ -519,7 +519,7 @@ /obj/effect/abstract/proximity_checker/singulo/Crossed(atom/movable/AM, oldloc) . = ..() - if(!istype(AM, /obj/item/projectile)) + if(!isprojectile(AM)) return var/obj/item/projectile/P = AM var/distance = distance_to_singulo diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 0e33fbc6ef4..b5365256fe8 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -35,6 +35,8 @@ var/semicd = 0 //cooldown handler var/execution_speed = 6 SECONDS var/weapon_weight = WEAPON_LIGHT + /// Additional spread when dual wielding. + var/dual_wield_spread = 24 var/list/restricted_species var/spread = 0 @@ -201,7 +203,7 @@ continue else if(G.can_trigger_gun(user)) if(!HAS_TRAIT(user, TRAIT_BADASS)) - bonus_spread += 24 * G.weapon_weight + bonus_spread += dual_wield_spread * G.weapon_weight loop_counter++ addtimer(CALLBACK(G, PROC_REF(process_fire), target, user, 1, params, null, bonus_spread), loop_counter) diff --git a/code/modules/projectiles/guns/energy_guns.dm b/code/modules/projectiles/guns/energy_guns.dm index 67f2d45398c..5980eaad8ec 100644 --- a/code/modules/projectiles/guns/energy_guns.dm +++ b/code/modules/projectiles/guns/energy_guns.dm @@ -200,7 +200,7 @@ if(can_shoot()) user.visible_message("[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!") sleep(25) - if(user.l_hand == src || user.r_hand == src) + if(user.is_holding(src)) user.visible_message("[user] melts [user.p_their()] face off with [src]!") playsound(loc, fire_sound, 50, 1, -1) var/obj/item/ammo_casing/energy/shot = ammo_type[select] diff --git a/code/modules/projectiles/guns/projectile_gun.dm b/code/modules/projectiles/guns/projectile_gun.dm index 37d17ed38d4..25dc5282cb4 100644 --- a/code/modules/projectiles/guns/projectile_gun.dm +++ b/code/modules/projectiles/guns/projectile_gun.dm @@ -148,7 +148,7 @@ if(loc == user) if(suppressed && can_unsuppress) var/obj/item/suppressor/S = suppressed - if(user.l_hand != src && user.r_hand != src) + if(!user.is_holding(src)) ..() return to_chat(user, "You unscrew [suppressed] from [src].") @@ -198,7 +198,7 @@ if(chambered && chambered.BB && !chambered.BB.nodamage) user.visible_message("[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!") sleep(25) - if(user.l_hand == src || user.r_hand == src) + if(user.is_holding(src)) process_fire(user, user, 0, zone_override = "head") user.visible_message("[user] blows [user.p_their()] brains out with [src]!") return BRUTELOSS diff --git a/code/modules/projectiles/projectile/magic_projectiles.dm b/code/modules/projectiles/projectile/magic_projectiles.dm index 1d21a315982..8ccc59ffc30 100644 --- a/code/modules/projectiles/projectile/magic_projectiles.dm +++ b/code/modules/projectiles/projectile/magic_projectiles.dm @@ -357,7 +357,7 @@ GLOBAL_LIST_INIT(wabbajack_docile_animals, list( qdel(src) else var/obj/O = change - if(istype(O, /obj/item/gun)) + if(isgun(O)) new /mob/living/simple_animal/hostile/mimic/copy/ranged(O.loc, O, firer) else new /mob/living/simple_animal/hostile/mimic/copy(O.loc, O, firer) diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm index 38b67f3c086..f273631c1f5 100644 --- a/code/modules/recycling/disposal.dm +++ b/code/modules/recycling/disposal.dm @@ -524,7 +524,7 @@ /obj/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0) if(isitem(mover) && mover.throwing) var/obj/item/I = mover - if(istype(I, /obj/item/projectile)) + if(isprojectile(I)) return if(prob(75) || (istype(mover.throwing.thrower) && (HAS_TRAIT(mover.throwing.thrower, TRAIT_BADASS) || HAS_TRAIT(mover.throwing.thrower, TRAIT_NEVER_MISSES_DISPOSALS)))) I.forceMove(src) diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index a1258bf72d9..8fae073f154 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -285,7 +285,7 @@ return /obj/machinery/disposal/deliveryChute/Bumped(atom/movable/AM) //Go straight into the chute - if(istype(AM, /obj/item/projectile) || isAI(AM) || QDELETED(AM)) + if(isprojectile(AM) || isAI(AM) || QDELETED(AM)) return switch(dir) if(NORTH) diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index fde9a420793..7ba2fec07b2 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -539,7 +539,7 @@ H.AIStatus = AI_OFF H.LoseTarget() stopped_atoms |= M - else if(istype(A, /obj/item/projectile)) + else if(isprojectile(A)) var/obj/item/projectile/P = A P.paused = TRUE stopped_atoms |= P diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm index 0088aaa7fdc..2c7806b9dce 100644 --- a/code/modules/surgery/organs/augments_arms.dm +++ b/code/modules/surgery/organs/augments_arms.dm @@ -690,7 +690,7 @@ set_light(3) addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, set_light), 0), 0.25 SECONDS) - if(istype(hitby, /obj/item/projectile)) + if(isprojectile(hitby)) var/obj/item/projectile/P = hitby if(P.shield_buster || istype(P, /obj/item/projectile/ion)) //EMP's and unpariable attacks, after all. return FALSE diff --git a/icons/effects/mouse_pointers/weapon_pointer.dmi b/icons/effects/mouse_pointers/weapon_pointer.dmi new file mode 100644 index 0000000000000000000000000000000000000000..b5070062c0bb6c7a06e95dc52b8cc1631daada63 GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxE?rg;HIQuav+;EP=v80 z$S;_|;n|HeASb;lB%;J6wK%ybv!En1KaYW-Voq>aK~d@VFTn*Lzdq6O*3~+9=6vvm zP=kxc4<6~9_t8AbP;|y;hlO#F@nvH#g*lHV9hnrO5Ugpa?%vsLth#xH#0ldkk9@Sf z&iJ-&m?|3cH|9Va6T?pyNk5zFnyWx7-Lo_BPCrEHL{A4JVaAEj;xc$FCk^t+A d7ta`37=%BFn=Y<3a|bGA@O1TaS?83{1OTnfQ!4-f literal 0 HcmV?d00001 diff --git a/paradise.dme b/paradise.dme index 395bb3eb17f..912489efe04 100644 --- a/paradise.dme +++ b/paradise.dme @@ -406,6 +406,7 @@ #include "code\datums\components\edit_complainer.dm" #include "code\datums\components\emissive_blocker.dm" #include "code\datums\components\footstep.dm" +#include "code\datums\components\fullauto.dm" #include "code\datums\components\label.dm" #include "code\datums\components\largeobjecttransparency.dm" #include "code\datums\components\material_container.dm"