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:
Mikhail Dzianishchyts
2024-04-23 00:32:49 +03:00
committed by GitHub
parent 00be61a811
commit 7fa44a31fb
49 changed files with 521 additions and 51 deletions
+7
View File
@@ -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)
+18 -4
View File
@@ -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
+4
View File
@@ -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))
+7 -1
View File
@@ -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
+21
View File
@@ -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
+68 -1
View File
@@ -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
+325
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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 ++
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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>")
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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)))
+24
View File
@@ -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
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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 ..()
+1 -1
View File
@@ -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
+3 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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

+1
View File
@@ -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"