From 333754285373d811bf99af8ffaa49e4db70955de Mon Sep 17 00:00:00 2001 From: mcbalaam <104003807+mcbalaam@users.noreply.github.com> Date: Thu, 2 Jul 2026 07:43:07 +0700 Subject: [PATCH] feat: the AI can now point to things through the holopad (#96719) ## About The Pull Request The AI can now send point clicks through their "camera" to be rendered by the available holopads. https://github.com/user-attachments/assets/427ea857-b303-4405-bc2e-91482abcbe29 Normal `point`ing timing applies, though one holopad is limited to projecting one arrow at a time. ## Why It's Good For The Game This is a thing I've been thinking about for a while. As the AI is usually the "AI open" "AI where is [...]" "AI how do I [...]" kinda role, it would make total sense there to be some way for the AI to point to things. Didn't think it should be it's own machinery since we already have way too much wallmount stuff. ## Changelog :cl: add: AIs can now point to things using holopads /:cl: --- code/_onclick/ai.dm | 12 ++++++++ code/game/machinery/hologram.dm | 39 ++++++++++++++++++++++++ code/modules/mob/living/silicon/ai/ai.dm | 25 +++++++++++++++ code/modules/point/point.dm | 7 +++++ 4 files changed, 83 insertions(+) diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 394fe4cd206..df95a6031c7 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -52,6 +52,9 @@ if(LAZYACCESS(modifiers, CTRL_CLICK)) CtrlShiftClickOn(A) return + if(LAZYACCESS(modifiers, MIDDLE_CLICK)) + ShiftMiddleClickOn(A) + return ShiftClickOn(A) return if(LAZYACCESS(modifiers, ALT_CLICK)) // alt and alt-gr (rightalt) @@ -269,6 +272,15 @@ togglelock(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN +/mob/living/silicon/ai/ShiftMiddleClickOn(atom/A) + if(control_disabled || incapacitated) + return + if(!can_see(A)) + return + if(!point_at(A, TRUE)) + return + log_message("points at [A] using holopad", LOG_EMOTE) + /* AI Turrets */ /obj/machinery/turretid/ai_click_alt(mob/living/silicon/ai/user) //toggles lethal on turrets if(ailock) diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 881caa03028..7284793063a 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -89,6 +89,8 @@ Possible to do for anyone motivated enough: var/secure = FALSE /// If we are currently calling another holopad var/calling = FALSE + /// Whether this pad is currently projecting a pointing arrow + var/pointing = FALSE ///bitfield. used to turn on and off hearing sensitivity depending on if we can act on Hear() at all - meant for lowering the number of unessesary hearable atoms var/can_hear_flags = NONE @@ -738,6 +740,43 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/ else ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) +/// Project a holographic pointing arrow from this holopad toward a target +/obj/machinery/holopad/proc/holo_point(atom/target, invisibility = 0) + if(pointing) + return + var/turf/pad_turf = get_turf(src) + var/turf/target_turf = get_turf(target) + if(!pad_turf || !target_turf) + return + pointing = TRUE + var/obj/effect/temp_visual/point/holo/visual = new(pad_turf, invisibility) + var/obj/effect/overlay/holoray/ray = new(pad_turf) + var/disty = target_turf.y - pad_turf.y + var/distx = target_turf.x - pad_turf.x + var/distance = sqrt(distx*distx + disty*disty) + var/angle + if(!disty) + angle = (distx >= 0) ? 90 : 270 + else + angle = arctan(distx/disty) + if(disty < 0) + angle += 180 + else if(distx < 0) + angle += 360 + var/matrix/M = matrix() + ray.transform = turn(M.Scale(1, distance), angle) + animate(visual, pixel_x = (target_turf.x - pad_turf.x) * ICON_SIZE_X + target.pixel_x, pixel_y = (target_turf.y - pad_turf.y) * ICON_SIZE_Y + target.pixel_y, time = 1.7, easing = EASE_OUT) + set_light(2) + icon_state = "[base_icon_state]1" + addtimer(CALLBACK(src, PROC_REF(clear_holo_point), ray), 2.5 SECONDS) + return visual + +/// Called after the holo-point expires to restore the holopad's state +/obj/machinery/holopad/proc/clear_holo_point(obj/effect/overlay/holoray/ray) + qdel(ray) + pointing = FALSE + SetLightsAndPower() + // RECORDED MESSAGES /obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 05c8f2cb269..0689c2c59e5 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1279,6 +1279,31 @@ . += emissive_appearance(icon, lights_state, src) +/mob/living/silicon/ai/point_at(atom/pointed_atom, intentional = FALSE) + if(pointed_atom in src) + return FALSE + var/turf/target_turf = get_turf(pointed_atom) + if(!target_turf) + return FALSE + var/obj/machinery/holopad/best_pad + for(var/obj/machinery/holopad/pad as anything in SSmachines.get_machines_by_type(/obj/machinery/holopad)) + if(!pad.on_network || !pad.is_operational || pad.pointing) + continue + if(!pad.validate_location(target_turf)) + continue + var/turf/pad_turf = get_turf(pad) + if(!SScameras.is_visible_by_cameras(pad_turf)) + continue + if(!best_pad || get_dist(pad_turf, target_turf) < get_dist(get_turf(best_pad), target_turf)) + best_pad = pad + if(!best_pad) + return FALSE + var/obj/visual = best_pad.holo_point(pointed_atom, invisibility) + if(!visual) + return FALSE + SEND_SIGNAL(src, COMSIG_MOVABLE_POINTED, pointed_atom, visual, intentional) + return TRUE + #undef HOLOGRAM_CHOICE_CHARACTER #undef CHARACTER_TYPE_SELF #undef CHARACTER_TYPE_CREWMEMBER diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm index 00b2e995f24..f72e5a97438 100644 --- a/code/modules/point/point.dm +++ b/code/modules/point/point.dm @@ -84,6 +84,13 @@ pixel_y = old_loc.pixel_y SetInvisibility(set_invis) +/obj/effect/temp_visual/point/holo + icon_state = "arrow_large_white" + +/obj/effect/temp_visual/point/holo/Initialize(mapload, set_invis = 0) + . = ..() + makeHologram() + #undef POINT_TIME /**