Files
Bubberstation/code/_onclick/click.dm
小月猫 146d0fd59d Fixes a couple simple issues with interactability (#69762)
first things first, back when LemonInTheDark changed the interact_range code he did a slight modification that broke the AI cards, namely, he did this (not saying its a bad change, it is actually a good change, just mentioning what change caused the issue im fixing now):

now, his change does make sense, since he changed the range default to 0 instead of 1, however "null" is also used as a range, specifically for AIs, now this normally isnt an issue for the AI itself, as the AI generally gets a TRUE in its interaction checks before it gets down this deep (machines have a bypass specifically for AI), but there is one situation in which it does go this deep: AI Cards, when in an AI card the AIs interaction range is set to 0 and their interaction is disabled, thereby making it impossible for them to interact with anything, now when a player opens the card UI and enables the AIs ability to interact, this sets their range back to null, aka unlimited, the issue now however, is that since "null" is treated the same as "0", and AIs in cards dont hit the same bypasses an AI core does, Lemons change to submit a false return for 0, is also submitting false for null, meaning the AI card cannot interact with anything except the tile its on, despite having null/unlimited range....

fixed by changing the null value to infinity where it is used

additionally my fix of can_interact() code apparently had the unintended side effect of not allowing rotations of machines if theres no power, i missed this entirely because thats such a specific situation, since you try rotating with APC power in most cases, it also didnt affect most machines, that said the fix was simple, just changed the proc being called to only check distance, not power. fixes #61852

and last but not least, fixes some code with the syndie bombs interactability, namely removes a redudant section, and adds a check for range, turns out there were no checks for range so you could in theory open the UI and walk away and then activate it from another location, so added a quick check to ensure you actually CAN interact with it before letting you push buttons in the UI
2022-09-11 02:24:00 -07:00

573 lines
16 KiB
Plaintext

/*
Click code cleanup
~Sayu
*/
// 1 decisecond click delay (above and beyond mob/next_move)
//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move()
/mob/var/next_click = 0
// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK
/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or -
/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by
//Delays the mob's next click/action by num deciseconds
// eg: 10-3 = 7 deciseconds of delay
// eg: 10*0.5 = 5 deciseconds of delay
// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK
/mob/proc/changeNext_move(num)
next_move = world.time + ((num+next_move_adjust)*next_move_modifier)
/mob/living/changeNext_move(num)
var/mod = next_move_modifier
var/adj = next_move_adjust
for(var/datum/status_effect/effect as anything in status_effects)
mod *= effect.nextmove_modifier()
adj += effect.nextmove_adjust()
next_move = world.time + ((num + adj)*mod)
/**
* Before anything else, defer these calls to a per-mobtype handler. This allows us to
* remove istype() spaghetti code, but requires the addition of other handler procs to simplify it.
*
* Alternately, you could hardcode every mob's variation in a flat [/mob/proc/ClickOn] proc; however,
* that's a lot of code duplication and is hard to maintain.
*
* Note that this proc can be overridden, and is in the case of screen objects.
*/
/atom/Click(location, control, params)
if(flags_1 & INITIALIZED_1)
SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr)
usr.ClickOn(src, params)
/atom/DblClick(location,control,params)
if(flags_1 & INITIALIZED_1)
usr.DblClickOn(src,params)
/atom/MouseWheel(delta_x,delta_y,location,control,params)
if(flags_1 & INITIALIZED_1)
usr.MouseWheelOn(src, delta_x, delta_y, params)
/**
* Standard mob ClickOn()
* Handles exceptions: Buildmode, middle click, modified clicks, mech actions
*
* After that, mostly just check your state, check whether you're holding an item,
* check whether you're adjacent to the target, then pass off the click to whoever
* is receiving it.
* The most common are:
* * [mob/proc/UnarmedAttack] (atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves
* * [atom/proc/attackby] (item,user) - used only when adjacent
* * [obj/item/proc/afterattack] (atom,user,adjacent,params) - used both ranged and adjacent
* * [mob/proc/RangedAttack] (atom,modifiers) - used only ranged, only used for tk and laser eyes but could be changed
*/
/mob/proc/ClickOn( atom/A, params )
if(world.time <= next_click)
return
next_click = world.time + 1
if(check_click_intercept(params,A) || notransform)
return
var/list/modifiers = params2list(params)
if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, modifiers) & COMSIG_MOB_CANCEL_CLICKON)
return
if(LAZYACCESS(modifiers, SHIFT_CLICK))
if(LAZYACCESS(modifiers, MIDDLE_CLICK))
ShiftMiddleClickOn(A)
return
if(LAZYACCESS(modifiers, CTRL_CLICK))
CtrlShiftClickOn(A)
return
ShiftClickOn(A)
return
if(LAZYACCESS(modifiers, MIDDLE_CLICK))
if(LAZYACCESS(modifiers, CTRL_CLICK))
CtrlMiddleClickOn(A)
else
MiddleClickOn(A, params)
return
if(LAZYACCESS(modifiers, ALT_CLICK)) // alt and alt-gr (rightalt)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
alt_click_on_secondary(A)
else
AltClickOn(A)
return
if(LAZYACCESS(modifiers, CTRL_CLICK))
CtrlClickOn(A)
return
if(incapacitated(IGNORE_RESTRAINTS|IGNORE_STASIS))
return
face_atom(A)
if(next_move > world.time) // in the year 2000...
return
if(!LAZYACCESS(modifiers, "catcher") && A.IsObscured())
return
if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow
UnarmedAttack(A, FALSE, modifiers)
return
if(throw_mode)
if(throw_item(A))
changeNext_move(CLICK_CD_THROW)
return
var/obj/item/W = get_active_held_item()
if(W == A)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
W.attack_self_secondary(src, modifiers)
update_held_items()
return
else
W.attack_self(src, modifiers)
update_held_items()
return
//These are always reachable.
//User itself, current loc, and user inventory
if(A in DirectAccess())
if(W)
W.melee_attack_chain(src, A, params)
else
if(ismob(A))
changeNext_move(CLICK_CD_MELEE)
UnarmedAttack(A, FALSE, modifiers)
return
//Can't reach anything else in lockers or other weirdness
if(!loc.AllowClick())
return
// In a storage item with a disassociated storage parent
var/obj/item/item_atom = A
if(istype(item_atom))
if((item_atom.item_flags & IN_STORAGE) && (item_atom.loc.flags_1 & HAS_DISASSOCIATED_STORAGE_1))
UnarmedAttack(item_atom, TRUE, modifiers)
//Standard reach turf to turf or reaching inside storage
if(CanReach(A,W))
if(W)
W.melee_attack_chain(src, A, params)
else
if(ismob(A))
changeNext_move(CLICK_CD_MELEE)
UnarmedAttack(A,1,modifiers)
else
if(W)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
var/after_attack_secondary_result = W.afterattack_secondary(A, src, FALSE, params)
if(after_attack_secondary_result == SECONDARY_ATTACK_CALL_NORMAL)
W.afterattack(A, src, FALSE, params)
else
W.afterattack(A,src,0,params)
else
if(LAZYACCESS(modifiers, RIGHT_CLICK))
ranged_secondary_attack(A, modifiers)
else
RangedAttack(A,modifiers)
/// Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it
/atom/proc/IsObscured()
SHOULD_BE_PURE(TRUE)
if(!isturf(loc)) //This only makes sense for things directly on turfs for now
return FALSE
var/turf/T = get_turf_pixel(src)
if(!T)
return FALSE
for(var/atom/movable/AM in T)
if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && AM.layer > layer)
return TRUE
return FALSE
/turf/IsObscured()
for(var/item in src)
var/atom/movable/AM = item
if(AM.flags_1 & PREVENT_CLICK_UNDER_1)
return TRUE
return FALSE
/**
* A backwards depth-limited breadth-first-search to see if the target is
* logically "in" anything adjacent to us.
*/
/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE)
var/list/direct_access = DirectAccess()
var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH)
var/list/closed = list()
var/list/checking = list(ultimate_target)
while (checking.len && depth > 0)
var/list/next = list()
--depth
for(var/atom/target in checking) // will filter out nulls
if(closed[target] || isarea(target)) // avoid infinity situations
continue
if(isturf(target) || isturf(target.loc) || (target in direct_access) || (ismovable(target) && target.flags_1 & IS_ONTOP_1) || target.loc?.atom_storage) //Directly accessible atoms
if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks
return TRUE
closed[target] = TRUE
if (!target.loc)
continue
if(target.loc.atom_storage)
next += target.loc
checking = next
return FALSE
/atom/movable/proc/DirectAccess()
return list(src, loc)
/mob/DirectAccess(atom/target)
return ..() + contents
/mob/living/DirectAccess(atom/target)
return ..() + get_all_contents()
/atom/proc/AllowClick()
return FALSE
/turf/AllowClick()
return TRUE
/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach)
if(!here || !there)
return
switch(reach)
if(0)
return FALSE
if(1)
return FALSE //here.Adjacent(there)
if(2 to INFINITY)
var/obj/dummy = new(get_turf(here))
dummy.pass_flags |= PASSTABLE
dummy.invisibility = INVISIBILITY_ABSTRACT
for(var/i in 1 to reach) //Limit it to that many tries
var/turf/T = get_step(dummy, get_dir(dummy, there))
if(dummy.CanReach(there))
qdel(dummy)
return TRUE
if(!dummy.Move(T)) //we're blocked!
qdel(dummy)
return
qdel(dummy)
/// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click)
/mob/proc/DblClickOn(atom/A, params)
return
/**
* Translates into [atom/proc/attack_hand], etc.
*
* Note: proximity_flag here is used to distinguish between normal usage (flag=1),
* and usage when clicking on things telekinetically (flag=0). This proc will
* not be called at ranged except with telekinesis.
*
* proximity_flag is not currently passed to attack_hand, and is instead used
* in human click code to allow glove touches only at melee range.
*
* modifiers is a lazy list of click modifiers this attack had,
* used for figuring out different properties of the click, mostly right vs left and such.
*/
/mob/proc/UnarmedAttack(atom/A, proximity_flag, list/modifiers)
if(ismob(A))
changeNext_move(CLICK_CD_MELEE)
return
/**
* Ranged unarmed attack:
*
* This currently is just a default for all mobs, involving
* laser eyes and telekinesis. You could easily add exceptions
* for things like ranged glove touches, spitting alien acid/neurotoxin,
* animals lunging, etc.
*/
/mob/proc/RangedAttack(atom/A, modifiers)
if(SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
return TRUE
/**
* Ranged secondary attack
*
* If the same conditions are met to trigger RangedAttack but it is
* instead initialized via a right click, this will trigger instead.
* Useful for mobs that have their abilities mapped to right click.
*/
/mob/proc/ranged_secondary_attack(atom/target, modifiers)
if(SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED_SECONDARY, target, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
return TRUE
/**
* Middle click
* Mainly used for swapping hands
*/
/mob/proc/MiddleClickOn(atom/A, params)
. = SEND_SIGNAL(src, COMSIG_MOB_MIDDLECLICKON, A, params)
if(. & COMSIG_MOB_CANCEL_CLICKON)
return
swap_hand()
/**
* Shift click
* For most mobs, examine.
* This is overridden in ai.dm
*/
/mob/proc/ShiftClickOn(atom/A)
A.ShiftClick(src)
return
/atom/proc/ShiftClick(mob/user)
var/flags = SEND_SIGNAL(user, COMSIG_CLICK_SHIFT, src)
if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
return
/**
* Ctrl click
* For most objects, pull
*/
/mob/proc/CtrlClickOn(atom/A)
A.CtrlClick(src)
return
/atom/proc/CtrlClick(mob/user)
SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user)
SEND_SIGNAL(user, COMSIG_MOB_CTRL_CLICKED, src)
var/mob/living/ML = user
if(istype(ML))
ML.pulled(src)
if(!can_interact(user))
return FALSE
/mob/living/CtrlClick(mob/user)
if(!isliving(user) || !user.CanReach(src) || user.incapacitated())
return ..()
if(world.time < user.next_move)
return FALSE
var/mob/living/user_living = user
if(user_living.apply_martial_art(src, null, is_grab=TRUE) == MARTIAL_ATTACK_SUCCESS)
user_living.changeNext_move(CLICK_CD_MELEE)
return TRUE
return ..()
/mob/living/carbon/human/CtrlClick(mob/user)
if(!iscarbon(user) || !user.CanReach(src) || user.incapacitated())
return ..()
if(world.time < user.next_move)
return FALSE
if (ishuman(user))
var/mob/living/carbon/human/human_user = user
if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art))
human_user.changeNext_move(CLICK_CD_MELEE)
return TRUE
else if(isalien(user))
var/mob/living/carbon/alien/humanoid/alien_boy = user
if(alien_boy.grab(src))
alien_boy.changeNext_move(CLICK_CD_MELEE)
return TRUE
return ..()
/mob/proc/CtrlMiddleClickOn(atom/A)
if(check_rights_for(client, R_ADMIN))
client.toggle_tag_datum(A)
else
A.CtrlClick(src)
return
/**
* Alt click
* Unused except for AI
*/
/mob/proc/AltClickOn(atom/A)
. = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON, A)
if(. & COMSIG_MOB_CANCEL_CLICKON)
return
A.AltClick(src)
/atom/proc/AltClick(mob/user)
if(!user.can_interact_with(src))
return FALSE
if(SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) & COMPONENT_CANCEL_CLICK_ALT)
return
var/turf/T = get_turf(src)
if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T) && !HAS_TRAIT(user, TRAIT_MOVE_VENTCRAWLING))
user.listed_turf = T
user.client.stat_panel.send_message("create_listedturf", T.name)
///The base proc of when something is right clicked on when alt is held - generally use alt_click_secondary instead
/atom/proc/alt_click_on_secondary(atom/A)
. = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON_SECONDARY, A)
if(. & COMSIG_MOB_CANCEL_CLICKON)
return
A.alt_click_secondary(src)
///The base proc of when something is right clicked on when alt is held
/atom/proc/alt_click_secondary(mob/user)
if(!user.can_interact_with(src))
return FALSE
if(SEND_SIGNAL(src, COMSIG_CLICK_ALT_SECONDARY, user) & COMPONENT_CANCEL_CLICK_ALT_SECONDARY)
return
if(isobserver(user) && user.client && check_rights_for(user.client, R_DEBUG))
user.client.toggle_tag_datum(src)
return
/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction
/atom/proc/AltClickNoInteract(mob/user, atom/A)
var/turf/T = get_turf(A)
if(T && user.TurfAdjacent(T))
user.listed_turf = T
user.client.stat_panel.send_message("create_listedturf", T.name)
/mob/proc/TurfAdjacent(turf/T)
return T.Adjacent(src)
/**
* Control+Shift click
* Unused except for AI
*/
/mob/proc/CtrlShiftClickOn(atom/A)
A.CtrlShiftClick(src)
return
/mob/proc/ShiftMiddleClickOn(atom/A)
src.pointed(A)
return
/atom/proc/CtrlShiftClick(mob/user)
if(!can_interact(user))
return FALSE
SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT, user)
return
/*
Misc helpers
face_atom: turns the mob towards what you clicked on
*/
/// Simple helper to face what you clicked on, in case it should be needed in more than one place
/mob/proc/face_atom(atom/A)
if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y )
return
var/dx = A.x - x
var/dy = A.y - y
if(!dx && !dy) // Wall items are graphically shifted but on the floor
if(A.pixel_y > 16)
setDir(NORTH)
else if(A.pixel_y < -16)
setDir(SOUTH)
else if(A.pixel_x > 16)
setDir(EAST)
else if(A.pixel_x < -16)
setDir(WEST)
return
if(abs(dx) < abs(dy))
if(dy > 0)
setDir(NORTH)
else
setDir(SOUTH)
else
if(dx > 0)
setDir(EAST)
else
setDir(WEST)
//debug
/atom/movable/screen/proc/scale_to(x1,y1)
if(!y1)
y1 = x1
var/matrix/M = new
M.Scale(x1,y1)
transform = M
/atom/movable/screen/click_catcher
icon = 'icons/hud/screen_gen.dmi'
icon_state = "catcher"
plane = CLICKCATCHER_PLANE
mouse_opacity = MOUSE_OPACITY_OPAQUE
screen_loc = "CENTER"
#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size)
#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose.
/atom/movable/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15)
var/icon/newicon = icon('icons/hud/screen_gen.dmi', "catcher")
var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x)
var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y)
var/px = view_size_x * world.icon_size
var/py = view_size_y * world.icon_size
var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px)
var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py)
newicon.Scale(sx, sy)
icon = newicon
screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]"
var/matrix/M = new
M.Scale(px/sx, py/sy)
transform = M
/atom/movable/screen/click_catcher/Click(location, control, params)
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, MIDDLE_CLICK) && iscarbon(usr))
var/mob/living/carbon/C = usr
C.swap_hand()
else
var/turf/click_turf = parse_caught_click_modifiers(modifiers, get_turf(usr.client ? usr.client.eye : usr), usr.client)
if (click_turf)
modifiers["catcher"] = TRUE
click_turf.Click(click_turf, control, list2params(modifiers))
. = 1
/// MouseWheelOn
/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params)
SEND_SIGNAL(src, COMSIG_MOUSE_SCROLL_ON, A, delta_x, delta_y, params)
/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params)
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, SHIFT_CLICK))
var/view = 0
if(delta_y > 0)
view = -1
else
view = 1
add_view_range(view)
/mob/proc/check_click_intercept(params,A)
//Client level intercept
if(client?.click_intercept)
if(call(client.click_intercept, "InterceptClickOn")(src, params, A))
return TRUE
//Mob level intercept
if(click_intercept)
if(call(click_intercept, "InterceptClickOn")(src, params, A))
return TRUE
return FALSE