mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-26 09:03:37 +00:00
Converts most spans into span procs. Mostly used regex for this and sorted out any compile time errors afterwards so there could be some bugs. Was initially going to do defines, but ninja said to make it into a proc, and if there's any overhead, they can easily be changed to defines. Makes it easier to control the formatting and prevents typos when creating spans as it'll runtime if you misspell instead of silently failing. Reduces the code you need to write when writing spans, as you don't need to close the span as that's automatically handled by the proc. (Note from Lemon: This should be converted to defines once we update the minimum version to 514. Didn't do it now because byond pain and such)
282 lines
11 KiB
Plaintext
282 lines
11 KiB
Plaintext
#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
|