diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 9de0dbe7c5..47b3648054 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -348,12 +348,22 @@ #define COMSIG_ATOM_UPDATE_LIGHT_FLAGS "atom_update_light_flags" // /client signals -#define COMSIG_MOB_CLIENT_LOGIN "mob_client_login" //sent when a mob/login() finishes: (client) #define COMSIG_MOB_CLIENT_LOGOUT "mob_client_logout" //sent when a mob/logout() starts: (client) #define COMSIG_MOB_CLIENT_MOVE "mob_client_move" //sent when client/Move() finishes with no early returns: (client, direction, n, oldloc) #define COMSIG_MOB_CLIENT_CHANGE_VIEW "mob_client_change_view" //from base of /client/change_view(): (client, old_view, view) #define COMSIG_MOB_CLIENT_MOUSEMOVE "mob_client_mousemove" //from base of /client/MouseMove(): (object, location, control, params) +///sent when a mob/login() finishes: (client) +#define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login" +//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" + + // /mob/living signals #define COMSIG_LIVING_REGENERATE_LIMBS "living_regenerate_limbs" //from base of /mob/living/regenerate_limbs(): (noheal, excluded_limbs) #define COMSIG_LIVING_RESIST "living_resist" //from base of mob/living/resist() (/mob/living) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 428784e953..151cd17540 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -673,6 +673,11 @@ Turf and target are separate in case you want to teleport some distance from a t if(final_x || final_y) return locate(final_x, final_y, T.z) +///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 null + //Finds the distance between two atoms, in pixels //centered = FALSE counts from turf edge to edge //centered = TRUE counts from turf center to turf center diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index fa529d0bfd..a698706f1a 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -23,23 +23,31 @@ SEND_SIGNAL(src, COMSIG_MOUSEDROPPED_ONTO, dropping, user) return - -/client/MouseDown(object, location, control, params) - if (mouse_down_icon) +/client/MouseDown(datum/object, location, control, params) + if(!control) + return + 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], TRUE) + Click(selected_target[1], location, control, selected_target[2]) sleep(delay) active_mousedown_item = mob.canMobMousedown(object, location, params) if(active_mousedown_item) active_mousedown_item.onMouseDown(object, location, params, mob) /client/MouseUp(object, location, control, params) - if (mouse_up_icon) + if(!control) + return + 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 if(active_mousedown_item) @@ -107,6 +115,7 @@ selected_target[2] = params if(active_mousedown_item) active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDRAG, src_object, over_object, src_location, over_location, src_control, over_control, params) /obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) return diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm index 2d00783e30..fb94688cd8 100644 --- a/code/datums/components/fullauto.dm +++ b/code/datums/components/fullauto.dm @@ -11,16 +11,34 @@ 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. + ///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) +/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown) . = ..() if(!isgun(parent)) return COMPONENT_INCOMPATIBLE var/obj/item/gun = parent RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/wake_up) - if(_autofire_shot_delay) - autofire_shot_delay = _autofire_shot_delay + if(autofire_shot_delay) + src.autofire_shot_delay = autofire_shot_delay + 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) @@ -61,9 +79,9 @@ shooter = clicker.mob RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDOWN, .proc/on_mouse_down) if(!QDELETED(shooter)) - RegisterSignal(shooter, COMSIG_MOB_LOGOUT, .proc/autofire_off) - UnregisterSignal(shooter, COMSIG_MOB_LOGIN) - RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED), .proc/autofire_off) + RegisterSignal(shooter, COMSIG_MOB_CLIENT_LOGOUT, .proc/autofire_off) + UnregisterSignal(shooter, COMSIG_MOB_CLIENT_LOGIN) + RegisterSignal(parent, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED), .proc/autofire_off) 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) @@ -82,9 +100,9 @@ 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/on_client_login) - UnregisterSignal(shooter, COMSIG_MOB_LOGOUT) - UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED)) + RegisterSignal(shooter, COMSIG_MOB_CLIENT_LOGIN, .proc/on_client_login) + UnregisterSignal(shooter, COMSIG_MOB_CLIENT_LOGOUT) + UnregisterSignal(parent, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED)) shooter = null parent.UnregisterSignal(parent, COMSIG_AUTOFIRE_SHOT) parent.UnregisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN) @@ -97,29 +115,31 @@ autofire_on(source.client) /datum/component/automatic_fire/proc/on_mouse_down(client/source, atom/_target, turf/location, control, params) + SIGNAL_HANDLER var/list/modifiers = params2list(params) //If they're shift+clicking, for example, let's not have them accidentally shoot. - if(LAZYACCESS(modifiers, SHIFT_CLICK)) + if(LAZYACCESS(modifiers, COMSIG_CLICK_SHIFT)) return - if(LAZYACCESS(modifiers, CTRL_CLICK)) + if(LAZYACCESS(modifiers, COMSIG_CLICK_CTRL)) return - if(LAZYACCESS(modifiers, MIDDLE_CLICK)) + if(LAZYACCESS(modifiers, MOUSE_MIDDLE_BUTTON)) return - if(LAZYACCESS(modifiers, RIGHT_CLICK)) + if(LAZYACCESS(modifiers, MOUSE_RIGHT_BUTTON)) return - if(LAZYACCESS(modifiers, ALT_CLICK)) + if(LAZYACCESS(modifiers, COMSIG_CLICK_ALT)) return - if(source.mob.throw_mode) + 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)) //Clicking on a screen object. + if(isnull(location) || istype(_target, /atom/movable/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 = params2turf(modifiers["screen-loc"], get_turf(source.eye), source) + _target = parse_caught_click_modifiers(modifiers, get_turf(source.eye), source) + params = list2params(modifiers) if(!_target) CRASH("Failed to get the turf under clickcatcher") @@ -136,7 +156,7 @@ target = _target target_loc = get_turf(target) mouse_parameters = params - start_autofiring() + INVOKE_ASYNC(src, .proc/start_autofiring) //Dakka-dakka @@ -198,7 +218,8 @@ 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) + 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. @@ -229,23 +250,38 @@ stop_autofiring() //Elvis has left the building. return FALSE shooter.face_atom(target) - COOLDOWN_START(src, next_shot_cd, autofire_shot_delay) + 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/windup_reset, FALSE), windup_spindown, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) +/* + if(HAS_TRAIT(shooter, TRAIT_DOUBLE_TAP)) + next_delay = round(next_delay * 0.5) +*/ + COOLDOWN_START(src, next_shot_cd, next_delay) if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, 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.stat || !can_trigger_gun(shooter)) + if(semicd || shooter.incapacitated() || !can_trigger_gun(shooter)) return FALSE if(!can_shoot()) shoot_with_empty_chamber(shooter) 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]!") + to_chat(shooter, span_warning("You need two hands to fire [src]!")) return FALSE return TRUE @@ -258,7 +294,7 @@ /obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, params) SIGNAL_HANDLER - if(semicd || shooter.stat) + if(semicd || shooter.incapacitated()) return NONE if(!can_shoot()) shoot_with_empty_chamber(shooter) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 9ab2be033a..4f956b5964 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -867,10 +867,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( ip_intel = res.intel /client/Click(atom/object, atom/location, control, params, ignore_spam = FALSE, extra_info) - /*if(last_click > world.time - world.tick_lag) + if(last_click > world.time - world.tick_lag) return last_activity = world.time - last_click = world.time*/ + last_click = world.time + //fullauto stuff if(!control) return if(click_intercept_time) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 91ff5f4fbf..507a94b613 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -875,13 +875,13 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) L.alpha = lighting_alpha /mob/proc/update_mouse_pointer() - if(!client) + if (!client) return client.mouse_pointer_icon = initial(client.mouse_pointer_icon) if(istype(loc, /obj/vehicle/sealed)) - var/obj/vehicle/sealed/mecha/M = loc - if(M.mouse_pointer) - client.mouse_pointer_icon = M.mouse_pointer + var/obj/vehicle/sealed/mecha/E = loc + if(E.mouse_pointer) + client.mouse_pointer_icon = E.mouse_pointer if(client.mouse_override_icon) client.mouse_pointer_icon = client.mouse_override_icon diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 06468685c5..41356583e6 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -57,6 +57,8 @@ var/burst_spread = 0 //Spread induced by the gun itself during burst fire per iteration. Only checked if spread is 0. var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once. var/inaccuracy_modifier = 1 + var/semicd = 0 //cooldown handler + var/dual_wield_spread = 24 //additional spread when dual wielding lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 6487b57a9e..dd68ed9817 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -15,6 +15,11 @@ fire_sound = "sound/weapons/gunshot_smg_alt.ogg" mag_type = /obj/item/ammo_box/magazine/smgm9mm pin = null + burst_size = 1 + +/obj/item/gun/ballistic/automatic/proto/Initialize() + . = ..() + AddComponent(/datum/component/automatic_fire, 0.2 SECONDS) /obj/item/gun/ballistic/automatic/proto/unrestricted pin = /obj/item/firing_pin @@ -309,16 +314,15 @@ actions_types = list() spread = 7 pin = /obj/item/firing_pin/implant/pindicate - automatic_burst_overlay = FALSE var/cover_open = FALSE +/obj/item/gun/ballistic/automatic/l6_saw/Initialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + AddComponent(/datum/component/automatic_fire, 0.2 SECONDS) /obj/item/gun/ballistic/automatic/l6_saw/unrestricted pin = /obj/item/firing_pin -/obj/item/gun/ballistic/automatic/l6_saw/ComponentInitialize() - . = ..() - AddComponent(/datum/component/automatic_fire, 0.2 SECONDS) - /obj/item/gun/ballistic/automatic/l6_saw/examine(mob/user) . = ..() if(cover_open && magazine)