/* 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/i in status_effects) var/datum/status_effect/S = i mod *= S.nextmove_modifier() adj += S.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 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/UnarmedAttack(atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves * atom/attackby(item,user) - used only when adjacent * item/afterattack(atom,user,adjacent,params) - used both ranged and adjacent * mob/RangedAttack(atom,params) - 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)) return if(notransform) return var/list/modifiers = params2list(params) if(modifiers["shift"] && modifiers["middle"]) ShiftMiddleClickOn(A) return if(modifiers["shift"] && modifiers["ctrl"]) CtrlShiftClickOn(A) return if(modifiers["middle"]) MiddleClickOn(A) return if(modifiers["shift"]) ShiftClickOn(A) return if(modifiers["alt"]) // alt and alt-gr (rightalt) AltClickOn(A) return if(modifiers["ctrl"]) CtrlClickOn(A) return if(incapacitated(ignore_restraints = 1)) return face_atom(A) if(next_move > world.time) // in the year 2000... return if(!modifiers["catcher"] && A.IsObscured()) return if(ismecha(loc)) var/obj/mecha/M = loc return M.click_action(A,src,params) if(restrained()) changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow RestrainedClickOn(A) return if(in_throw_mode) throw_item(A) return var/obj/item/W = get_active_held_item() if(W == A) W.attack_self(src) update_inv_hands() 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) return //Can't reach anything else in lockers or other weirdness if(!loc.AllowClick()) return //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) else if(W) W.afterattack(A,src,0,params) else RangedAttack(A,params) //Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it /atom/proc/IsObscured() 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/atom/movable/AM in src) if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density) return TRUE return FALSE /atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) // A backwards depth-limited breadth-first-search to see if the target is // logically "in" anything adjacent to us. 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 closed[target] = TRUE if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks return TRUE if (!target.loc) continue if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH)) 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 ..() + GetAllContents() /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 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. */ /mob/proc/UnarmedAttack(atom/A, proximity_flag) 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, params) SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) /* Restrained ClickOn Used when you are handcuffed and click things. Not currently used by anything but could easily be. */ /mob/proc/RestrainedClickOn(atom/A) return /* Middle click Only used for swapping hands */ /mob/proc/MiddleClickOn(atom/A) return /mob/living/carbon/MiddleClickOn(atom/A) if(!stat && mind && iscarbon(A) && A != src) var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) if(C && C.chosen_sting) C.chosen_sting.try_to_sting(src,A) next_click = world.time + 5 return swap_hand() /mob/living/simple_animal/drone/MiddleClickOn(atom/A) swap_hand() // In case of use break glass /* /atom/proc/MiddleClick(mob/M as mob) return */ /* 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) SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) if(user.client && user.client.eye == user || user.client.eye == user.loc) 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) var/mob/living/ML = user if(istype(ML)) ML.pulled(src) /mob/living/carbon/human/CtrlClick(mob/user) if(ishuman(user) && Adjacent(user) && !user.incapacitated()) if(world.time < user.next_move) return FALSE var/mob/living/carbon/human/H = user H.dna.species.grab(H, src, H.mind.martial_art) H.changeNext_move(CLICK_CD_MELEE) else ..() /* Alt click Unused except for AI */ /mob/proc/AltClickOn(atom/A) var/result = SEND_SIGNAL(src, COMSIG_ALT_CLICK_ON, A) //yogs start - has to be like this, otherwise stuff breaks if(!result) A.AltClick(src) return //yogs end /mob/living/carbon/AltClickOn(atom/A) if(!stat && mind && iscarbon(A) && A != src) var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) if(C && C.chosen_sting) C.chosen_sting.try_to_sting(src,A) next_click = world.time + 5 return ..() /atom/proc/AltClick(mob/user) SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) var/turf/T = get_turf(src) if(T && user.TurfAdjacent(T)) if(user.listed_turf == T) user.listed_turf = null else user.listed_turf = T user.client.statpanel = 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) SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) return /* Misc helpers Laser Eyes: as the name implies, handles this since nothing else does currently face_atom: turns the mob towards what you clicked on */ /mob/proc/LaserEyes(atom/A, params) return /mob/living/LaserEyes(atom/A, params) changeNext_move(CLICK_CD_RANGE) var/obj/item/projectile/beam/LE = new /obj/item/projectile/beam( loc ) LE.icon = 'icons/effects/genetics.dmi' LE.icon_state = "eyelasers" playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) LE.firer = src LE.def_zone = get_organ_target() LE.preparePixelProjectile(A, src, params) LE.fire() // 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 /obj/screen/proc/scale_to(x1,y1) if(!y1) y1 = x1 var/matrix/M = new M.Scale(x1,y1) transform = M /obj/screen/click_catcher icon = 'icons/mob/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. /obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) var/icon/newicon = icon('icons/mob/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 /obj/screen/click_catcher/Click(location, control, params) var/list/modifiers = params2list(params) if(modifiers["middle"] && iscarbon(usr)) var/mob/living/carbon/C = usr C.swap_hand() else var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) params += "&catcher=1" if(T) T.Click(location, control, params) . = 1 /* MouseWheelOn */ /mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) return /mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) var/list/modifier = params2list(params) if(modifier["shift"]) 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 && 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