mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-20 19:44:09 +01:00
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 <zweisen@gmail.com>
This commit is contained in:
committed by
GitHub
parent
00be61a811
commit
7fa44a31fb
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "<span class='warning'>You need to use both hands!</span>")
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ++
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
if(prob(50))
|
||||
to_chat(mover, "<span class='danger'>You get stuck in [src] for a moment.</span>")
|
||||
return FALSE
|
||||
else if(istype(mover, /obj/item/projectile))
|
||||
else if(isprojectile(mover))
|
||||
return prob(30)
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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("<span class='notice'>Swish! [AM] lands in [src].</span>")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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("<span class='danger'>[owner] blocks [attack_text] with [src]!</span>")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
to_chat(user, "<span class='warning'>There is already a [holstered] holstered here!</span>")
|
||||
return
|
||||
|
||||
if(!istype(I, /obj/item/gun))
|
||||
if(!isgun(I))
|
||||
to_chat(user, "<span class='warning'>Only guns can be holstered!</span>")
|
||||
return
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
if(can_opened)
|
||||
to_chat(H, "<span class='warning'>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, "<span class='notice'>You start shaking up [src].</span>")
|
||||
|
||||
@@ -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
|
||||
..()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "<span class='warning'>\The [S] pulls \the [hand] from your grip!</span>")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
if(can_shoot())
|
||||
user.visible_message("<span class='suicide'>[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
sleep(25)
|
||||
if(user.l_hand == src || user.r_hand == src)
|
||||
if(user.is_holding(src))
|
||||
user.visible_message("<span class='suicide'>[user] melts [user.p_their()] face off with [src]!</span>")
|
||||
playsound(loc, fire_sound, 50, 1, -1)
|
||||
var/obj/item/ammo_casing/energy/shot = ammo_type[select]
|
||||
|
||||
@@ -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, "<span class='notice'>You unscrew [suppressed] from [src].</span>")
|
||||
@@ -198,7 +198,7 @@
|
||||
if(chambered && chambered.BB && !chambered.BB.nodamage)
|
||||
user.visible_message("<span class='suicide'>[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
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("<span class='suicide'>[user] blows [user.p_their()] brains out with [src]!</span>")
|
||||
return BRUTELOSS
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 234 B |
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user