From 2e377cdc40b8dbb52f1a255a7ff249d048c2e265 Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:31:01 -0700 Subject: [PATCH] [MIRROR] Pathfinding circuitry and ID lockable assemblies (#11246) Co-authored-by: Aura Dusklight <46622484+NovaDusklight@users.noreply.github.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- .../integrated_electronics/core/assemblies.dm | 61 ++++++-- .../integrated_electronics/subtypes/smart.dm | 131 +++++++++++++++++- code/modules/mob/living/bot/bot.dm | 13 ++ 3 files changed, 191 insertions(+), 14 deletions(-) diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm index 34d166ef6d..2fd1c5a4a8 100644 --- a/code/modules/integrated_electronics/core/assemblies.dm +++ b/code/modules/integrated_electronics/core/assemblies.dm @@ -15,6 +15,9 @@ var/obj/item/cell/device/battery = null // Internal cell which most circuits need to work. var/net_power = 0 // Set every tick, to display how much power is being drawn in total. var/detail_color = COLOR_ASSEMBLY_BLACK + var/locked = FALSE // If true, the assembly cannot be opened with a crowbar + var/obj/item/card/id/locked_by = null // The ID that locked this assembly + var/obj/item/card/id/access_card = null // ID card for door access /obj/item/electronic_assembly/Initialize(mapload) @@ -191,6 +194,9 @@ /obj/item/electronic_assembly/update_icon() if(opened) icon_state = initial(icon_state) + "-open" + // else if(locked) + // For when I finish the locked assembly sprites. + // icon_state = initial(icon_state) // + "-locked" would be added once sprites are made else icon_state = initial(icon_state) cut_overlays() @@ -200,19 +206,6 @@ detail_overlay.color = detail_color add_overlay(detail_overlay) - -/obj/item/electronic_assembly/GetAccess() - . = list() - for(var/obj/item/integrated_circuit/part in contents) - . |= part.GetAccess() - -/obj/item/electronic_assembly/GetIdCard() - . = list() - for(var/obj/item/integrated_circuit/part in contents) - var/id_card = part.GetIdCard() - if(id_card) - return id_card - /obj/item/electronic_assembly/examine(mob/user) . = ..() if(Adjacent(user)) @@ -300,12 +293,46 @@ return TRUE else if(I.has_tool_quality(TOOL_CROWBAR)) + if(locked) + to_chat(user, span_warning("\The [src] is locked! You cannot open it with a crowbar.")) + return FALSE playsound(src, 'sound/items/Crowbar.ogg', 50, 1) opened = !opened to_chat(user, span_notice("You [opened ? "opened" : "closed"] \the [src].")) update_icon() return TRUE + else if((istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) && !opened) + var/obj/item/card/id/id_card = null + + if(istype(I, /obj/item/card/id)) + id_card = I + else + var/obj/item/pda/pda = I + id_card = pda.id + + if(!id_card) + to_chat(user, span_warning("You need an ID card to lock this assembly!")) + return FALSE + + if(locked) + // Trying to unlock + if(locked_by && id_card.registered_name == locked_by.registered_name) + locked = FALSE + locked_by = null + to_chat(user, span_notice("You unlock \the [src].")) + update_icon() + else + to_chat(user, span_warning("Access denied. This assembly was locked by [locked_by ? locked_by.registered_name : "someone else"].")) + return TRUE + else + // Trying to lock + locked = TRUE + locked_by = id_card + to_chat(user, span_notice("You lock \the [src]. Now only your ID card can unlock it.")) + update_icon() + return TRUE + else if(istype(I, /obj/item/integrated_electronics/wirer) || istype(I, /obj/item/integrated_electronics/debugger) || I.has_tool_quality(TOOL_SCREWDRIVER)) if(opened) tgui_interact(user) @@ -404,6 +431,14 @@ for(var/obj/item/integrated_circuit/IC in contents) IC.on_unanchored() +// Bump functionality, for pathfinding circuits. (Droid circuit assembly types) +/obj/item/electronic_assembly/Bump(atom/AM) + ..() + if(istype(AM, /obj/machinery/door) && can_move()) + var/obj/machinery/door/D = AM + if(D.check_access(src)) + D.open() + // Returns TRUE if I is something that could/should have a valid interaction. Used to tell circuitclothes to hit the circuit with something instead of the clothes /obj/item/electronic_assembly/proc/is_valid_tool(var/obj/item/I) return I.has_tool_quality(TOOL_CROWBAR) || I.has_tool_quality(TOOL_SCREWDRIVER) || istype(I, /obj/item/integrated_circuit) || istype(I, /obj/item/cell/device) || istype(I, /obj/item/integrated_electronics) diff --git a/code/modules/integrated_electronics/subtypes/smart.dm b/code/modules/integrated_electronics/subtypes/smart.dm index 3c07331406..1a83295c8a 100644 --- a/code/modules/integrated_electronics/subtypes/smart.dm +++ b/code/modules/integrated_electronics/subtypes/smart.dm @@ -31,7 +31,7 @@ set_pin_data(IC_OUTPUT, 1, desired_dir) push_data() - activate_pin(2) //CHOMPEdit + activate_pin(2) //CHOMPEdit Begin /obj/item/integrated_circuit/smart/advanced_pathfinder @@ -79,3 +79,132 @@ push_data() activate_pin(2) //CHOMPEdit End + +/obj/item/integrated_circuit/smart/targeted_pathfinder + name = "targeted pathfinder" + desc = "Finds the best direction to a target using advanced pathfinding, when pulsed." + extended_desc = "This circuit uses a miniturized, integrated camera and advanced pathfinding to determine the best direction to a target, when pulsed." + icon_state = "numberpad" + complexity = 25 + inputs = list( + "target" = IC_PINTYPE_REF + ) + outputs = list( + "dir" = IC_PINTYPE_DIR + ) + activators = list( + "calculate dir" = IC_PINTYPE_PULSE_IN, + "on calculated" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 4, TECH_DATA = 5) + power_draw_per_use = 40 + +/obj/item/integrated_circuit/smart/targeted_pathfinder/do_work() + var/datum/integrated_io/I = inputs[1] + set_pin_data(IC_OUTPUT, 1, null) + + if(!isweakref(I.data)) + push_data() + activate_pin(2) + return + + var/atom/A = I.data.resolve() + if(!A) + push_data() + activate_pin(2) + return + + var/turf/start = get_turf(src) + var/turf/goal = get_turf(A) + if(!start || !goal) + push_data() + activate_pin(2) + return + + if(!(A in view(start))) + push_data() + activate_pin(2) + return // Can't see the target. + + // Pathfinding start + var/list/path = AStar(start, goal, /turf/proc/AdjacentTurfsWithAccess, /turf/proc/Distance, 0, 30, id = assembly) + if(path && path.len > 1) + var/turf/next = path[2] // path[1] is current location + var/desired_dir = get_dir(start, next) + set_pin_data(IC_OUTPUT, 1, desired_dir) + else + // Fallback if pathfinding fails + var/desired_dir = get_dir(start, goal) + set_pin_data(IC_OUTPUT, 1, desired_dir) + + push_data() + activate_pin(2) + +/obj/item/integrated_circuit/smart/pathfinding_locomotion + name = "pathfinding locomotion controller" + desc = "A specialized circuit that uses pathfinding to move towards a target when pulsed." + extended_desc = "This circuit will calculate a path to the target and move one step along that path each time it's pulsed." + icon_state = "numberpad" + complexity = 40 + inputs = list( + "target" = IC_PINTYPE_REF + ) + outputs = list( + "current direction" = IC_PINTYPE_DIR, + "distance to target" = IC_PINTYPE_NUMBER + ) + activators = list( + "step toward target" = IC_PINTYPE_PULSE_IN, + "on successful move" = IC_PINTYPE_PULSE_OUT, + "on failed move" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_RESEARCH + origin_tech = list(TECH_ENGINEERING = 5, TECH_DATA = 5) + power_draw_per_use = 60 + cooldown_per_use = 2 // 3 delay on a ticker circuit from testing. + +/obj/item/integrated_circuit/smart/pathfinding_locomotion/do_work() + var/datum/integrated_io/I = inputs[1] + if(!isweakref(I.data)) + activate_pin(3) + return + + var/atom/A = I.data.resolve() + if(!A) + activate_pin(3) + return + + var/turf/start = get_turf(src) + var/turf/goal = get_turf(A) + if(!start || !goal) + activate_pin(3) + return + + var/desired_dir + + // Only calculate full path if target is in view + if(A in view(start)) + var/list/path = AStar(start, goal, /turf/proc/AdjacentTurfsWithAccess, /turf/proc/Distance, 0, 30, id = assembly) + if(path && path.len > 1) + var/turf/next = path[2] + desired_dir = get_dir(start, next) + else + desired_dir = get_dir(start, goal) + else + // If we can't see the target, just move in its direction + desired_dir = get_dir(start, goal) + + set_pin_data(IC_OUTPUT, 1, desired_dir) + set_pin_data(IC_OUTPUT, 2, get_dist(start, goal)) + push_data() + + // Move the assembly. + if(assembly && !assembly.anchored && assembly.can_move()) + var/move_result = step(assembly, desired_dir) + if(move_result) + activate_pin(2) + else + activate_pin(3) + else + activate_pin(3) diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm index 3a7adbfe9d..ff9d2ba0d7 100644 --- a/code/modules/mob/living/bot/bot.dm +++ b/code/modules/mob/living/bot/bot.dm @@ -444,6 +444,19 @@ L.Add(T) return L +// Diagonal-friendly version of CardinalTurfWithAccess +/turf/proc/AdjacentTurfsWithAccess(obj/item/card/id/ID) + var/list/L = new() + + for(var/dir_to_check in GLOB.alldirs) // Cardinals first. + var/turf/T = get_step(src, dir_to_check) + if(!T || !T.Adjacent(src)) + continue + if(!LinkBlockedWithAccess(src, T, ID)) + L.Add(T) + + + return L // Similar to above but not restricted to just GLOB.cardinal directions. /turf/proc/TurfsWithAccess(var/obj/item/card/id/ID)