mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-20 03:30:01 +01:00
This adds a component you can slap onto mobs with ranged attacks which lets them fire repeatedly with the mouse held down. I applied it to a couple of existing mobs, to be honest we don't currently have any which _need_ this but I will add it to the Ranged Holoparasite (which really does need it) depending on which PR gets merged first.
204 lines
7.3 KiB
Plaintext
204 lines
7.3 KiB
Plaintext
#define AUTOFIRE_MOUSEUP 1
|
|
#define AUTOFIRE_MOUSEDOWN 0
|
|
|
|
/// Allows a mob to autofire by holding down the cursor
|
|
/datum/component/ranged_mob_full_auto
|
|
/// Delay before attempting to fire again, note that this is just when we make attempts and is separate from mob's actual firing cooldown
|
|
var/autofire_shot_delay
|
|
/// Our client for click tracking
|
|
var/client/clicker
|
|
/// Are we currently firing?
|
|
var/is_firing = FALSE
|
|
/// 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/awaiting_status = AUTOFIRE_MOUSEDOWN
|
|
/// What are we currently shooting at?
|
|
var/atom/target
|
|
/// Where are we currently shooting at?
|
|
var/turf/target_loc
|
|
/// When will we next try to shoot?
|
|
COOLDOWN_DECLARE(next_shot_cooldown)
|
|
|
|
/datum/component/ranged_mob_full_auto/Initialize(autofire_shot_delay = 0.5 SECONDS)
|
|
. = ..()
|
|
if (!isliving(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
src.autofire_shot_delay = autofire_shot_delay
|
|
|
|
var/mob/living/living_parent = parent
|
|
if (isnull(living_parent.client))
|
|
return
|
|
on_gained_client(parent)
|
|
|
|
/datum/component/ranged_mob_full_auto/RegisterWithParent()
|
|
RegisterSignal(parent, COMSIG_MOB_LOGIN, PROC_REF(on_gained_client))
|
|
RegisterSignal(parent, COMSIG_MOB_LOGOUT, PROC_REF(on_lost_client))
|
|
|
|
/datum/component/ranged_mob_full_auto/UnregisterFromParent()
|
|
UnregisterSignal(parent, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT))
|
|
|
|
/datum/component/ranged_mob_full_auto/process(seconds_per_tick)
|
|
if (!try_shooting())
|
|
return PROCESS_KILL
|
|
|
|
/// Try and take a shot, returns false if we are unable to do so and should stop trying
|
|
/datum/component/ranged_mob_full_auto/proc/try_shooting()
|
|
if (!is_firing)
|
|
return FALSE
|
|
if (!COOLDOWN_FINISHED(src, next_shot_cooldown))
|
|
return TRUE // Don't fire but also keep processing
|
|
|
|
var/mob/living/living_parent = parent
|
|
|
|
if (isnull(target) || get_turf(target) != target_loc) // Target moved or got destroyed since we last aimed.
|
|
set_target(target_loc)
|
|
target = target_loc // So we keep firing on the emptied tile until we move our mouse and find a new target.
|
|
if (get_dist(living_parent, target) <= 0)
|
|
set_target(get_step(living_parent, living_parent.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(living_parent, target))
|
|
stop_firing()
|
|
return FALSE // Can't see shit
|
|
|
|
living_parent.face_atom(target)
|
|
COOLDOWN_START(src, next_shot_cooldown, autofire_shot_delay)
|
|
living_parent.RangedAttack(target)
|
|
return TRUE
|
|
|
|
/// Setter for reference handling
|
|
/datum/component/ranged_mob_full_auto/proc/set_target(atom/new_target)
|
|
if (!isnull(target))
|
|
UnregisterSignal(target, COMSIG_QDELETING)
|
|
target = new_target
|
|
if (!isnull(target))
|
|
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_deleted))
|
|
|
|
/// Don't hang references
|
|
/datum/component/ranged_mob_full_auto/proc/on_target_deleted()
|
|
SIGNAL_HANDLER
|
|
set_target(null)
|
|
|
|
/// When we gain a client, start tracking clicks
|
|
/datum/component/ranged_mob_full_auto/proc/on_gained_client(mob/living/source)
|
|
SIGNAL_HANDLER
|
|
clicker = source.client
|
|
RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(on_mouse_down))
|
|
|
|
/// When we lose our client, stop functioning
|
|
/datum/component/ranged_mob_full_auto/proc/on_lost_client(mob/living/source)
|
|
SIGNAL_HANDLER
|
|
if (!isnull(clicker))
|
|
UnregisterSignal(clicker, list(COMSIG_CLIENT_MOUSEDOWN, COMSIG_CLIENT_MOUSEDRAG, COMSIG_CLIENT_MOUSEUP))
|
|
stop_firing()
|
|
clicker = null
|
|
|
|
/// On mouse down start shooting!
|
|
/datum/component/ranged_mob_full_auto/proc/on_mouse_down(client/source, atom/target, turf/location, control, params)
|
|
SIGNAL_HANDLER
|
|
if (awaiting_status != AUTOFIRE_MOUSEDOWN)
|
|
return // Avoid a double mousedown with no mouseup
|
|
var/list/modifiers = params2list(params)
|
|
|
|
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
|
|
var/mob/living/living_parent = parent
|
|
if (!isturf(living_parent.loc) || living_parent.Adjacent(target))
|
|
return
|
|
|
|
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.
|
|
set_target(parse_caught_click_modifiers(modifiers, get_turf(source.eye), source))
|
|
params = list2params(modifiers)
|
|
if (isnull(target))
|
|
CRASH("Failed to get the turf under clickcatcher")
|
|
|
|
awaiting_status = AUTOFIRE_MOUSEUP
|
|
source.click_intercept_time = world.time // From this point onwards Click() will no longer be triggered.
|
|
if (is_firing)
|
|
stop_firing()
|
|
|
|
set_target(target)
|
|
target_loc = get_turf(target)
|
|
INVOKE_ASYNC(src, PROC_REF(start_firing))
|
|
|
|
/// Start tracking mouse movement and processing our shots
|
|
/datum/component/ranged_mob_full_auto/proc/start_firing()
|
|
if (is_firing)
|
|
return
|
|
|
|
is_firing = TRUE
|
|
if (!try_shooting()) // First one is immediate
|
|
stop_firing()
|
|
return
|
|
|
|
clicker.mouse_override_icon = 'icons/effects/mouse_pointers/weapon_pointer.dmi'
|
|
clicker.mouse_pointer_icon = clicker.mouse_override_icon
|
|
|
|
START_PROCESSING(SSprojectiles, src)
|
|
RegisterSignal(clicker, COMSIG_CLIENT_MOUSEUP, PROC_REF(on_mouse_up))
|
|
RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG, PROC_REF(on_mouse_drag))
|
|
|
|
/// When the mouse moved let's try and shift our aim
|
|
/datum/component/ranged_mob_full_auto/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))
|
|
set_target(over_object)
|
|
target_loc = get_turf(over_object)
|
|
return
|
|
|
|
//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)
|
|
|
|
if (!isnull(new_target))
|
|
set_target(new_target)
|
|
target_loc = new_target
|
|
return
|
|
|
|
if (QDELETED(target)) //No new target acquired, and old one was deleted, get us out of here.
|
|
stop_firing()
|
|
CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]. Old target was incidentally QDELETED.")
|
|
|
|
|
|
set_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]")
|
|
|
|
/// When the mouse is released we should stop
|
|
/datum/component/ranged_mob_full_auto/proc/on_mouse_up()
|
|
SIGNAL_HANDLER
|
|
if (awaiting_status != AUTOFIRE_MOUSEUP)
|
|
return
|
|
stop_firing()
|
|
return COMPONENT_CLIENT_MOUSEUP_INTERCEPT
|
|
|
|
/// Stop watching our mouse and processing shots
|
|
/datum/component/ranged_mob_full_auto/proc/stop_firing()
|
|
if (!is_firing)
|
|
return
|
|
|
|
is_firing = FALSE
|
|
set_target(null)
|
|
target_loc = null
|
|
STOP_PROCESSING(SSprojectiles, src)
|
|
awaiting_status = AUTOFIRE_MOUSEDOWN
|
|
|
|
if (isnull(clicker))
|
|
return
|
|
UnregisterSignal(clicker, list(COMSIG_CLIENT_MOUSEDRAG, COMSIG_CLIENT_MOUSEUP))
|
|
clicker.mouse_override_icon = null
|
|
clicker.mouse_pointer_icon = null
|
|
|
|
#undef AUTOFIRE_MOUSEUP
|
|
#undef AUTOFIRE_MOUSEDOWN
|