/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR FULLAUTO.DM #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) if(_autofire_shot_delay) autofire_shot_delay = _autofire_shot_delay 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(delta_time) 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 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(iscarbon(user)) var/mob/living/carbon/arizona_ranger = user if(arizona_ranger.is_holding(parent)) autofire_on(arizona_ranger.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/usercli) SIGNAL_HANDLER if(autofire_stat != AUTOFIRE_STAT_IDLE) return autofire_stat = AUTOFIRE_STAT_ALERT if(!QDELETED(usercli)) clicker = usercli 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) 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 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)) 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) 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)) 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 INVOKE_ASYNC(src, .proc/start_autofiring) //Dakka-dakka /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/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 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_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 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) 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(semicd || shooter.stat || !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, span_warning("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 if(semicd || shooter.stat) return NONE if(!can_shoot()) shoot_with_empty_chamber(shooter) return NONE INVOKE_ASYNC(src, .proc/do_autofire_shot, source, target, shooter, 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, params) 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) #undef AUTOFIRE_MOUSEUP #undef AUTOFIRE_MOUSEDOWN */