diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index 6134860733a..ed3b3d95099 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -20,221 +20,210 @@
return
if(stat & (NOPOWER|BROKEN))
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/robotics/proc/is_authenticated(var/mob/user as mob)
+/obj/machinery/computer/robotics/proc/is_authenticated(mob/user)
+ if(!istype(user))
+ return FALSE
if(user.can_admin_interact())
- return 1
- else if(allowed(user))
- return 1
- return 0
+ return TRUE
+ if(allowed(user))
+ return TRUE
+ return FALSE
-/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/**
+ * Does this borg show up in the console
+ *
+ * Returns TRUE if a robot will show up in the console
+ * Returns FALSE if a robot will not show up in the console
+ * Arguments:
+ * * R - The [mob/living/silicon/robot] to be checked
+ */
+/obj/machinery/computer/robotics/proc/console_shows(mob/living/silicon/robot/R)
+ if(!istype(R))
+ return FALSE
+ if(istype(R, /mob/living/silicon/robot/drone))
+ return FALSE
+ if(R.scrambledcodes)
+ return FALSE
+ if(!atoms_share_level(src, R))
+ return FALSE
+ return TRUE
+
+/**
+ * Check if a user can send a lockdown/detonate command to a specific borg
+ *
+ * Returns TRUE if a user can send the command (does not guarantee it will work)
+ * Returns FALSE if a user cannot
+ * Arguments:
+ * * user - The [mob/user] to be checked
+ * * R - The [mob/living/silicon/robot] to be checked
+ * * telluserwhy - Bool of whether the user should be sent a to_chat message if they don't have access
+ */
+/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R, telluserwhy = FALSE)
+ if(!istype(user))
+ return FALSE
+ if(!console_shows(R))
+ return FALSE
+ if(isAI(user))
+ if(R.connected_ai != user)
+ if(telluserwhy)
+ to_chat(user, "AIs can only control cyborgs which are linked to them.")
+ return FALSE
+ if(isrobot(user))
+ if(R != user)
+ if(telluserwhy)
+ to_chat(user, "Cyborgs cannot control other cyborgs.")
+ return FALSE
+ return TRUE
+
+/**
+ * Check if the user is the right kind of entity to be able to hack borgs
+ *
+ * Returns TRUE if a user is a traitor AI, or aghost
+ * Returns FALSE otherwise
+ * Arguments:
+ * * user - The [mob/user] to be checked
+ */
+/obj/machinery/computer/robotics/proc/can_hack_any(mob/user)
+ if(!istype(user))
+ return FALSE
+ if(user.can_admin_interact())
+ return TRUE
+ if(!isAI(user))
+ return FALSE
+ return (user.mind.special_role && user.mind.original == user)
+
+/**
+ * Check if the user is allowed to hack a specific borg
+ *
+ * Returns TRUE if a user can hack the specific cyborg
+ * Returns FALSE if a user cannot
+ * Arguments:
+ * * user - The [mob/user] to be checked
+ * * R - The [mob/living/silicon/robot] to be checked
+ */
+/obj/machinery/computer/robotics/proc/can_hack(mob/user, mob/living/silicon/robot/R)
+ if(!can_hack_any(user))
+ return FALSE
+ if(!istype(R))
+ return FALSE
+ if(R.emagged)
+ return FALSE
+ if(R.connected_ai != user)
+ return FALSE
+ return TRUE
+
+/obj/machinery/computer/robotics/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "robot_control.tmpl", "Robotic Control Console", 400, 500)
+ ui = new(user, src, ui_key, "RoboticsControlConsole", name, 500, 460, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/robotics/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
- var/list/robots = get_cyborgs(user)
- if(robots.len)
- data["robots"] = robots
+/obj/machinery/computer/robotics/tgui_data(mob/user)
+ var/list/data = list()
+ data["auth"] = is_authenticated(user)
+ data["can_hack"] = can_hack_any(user)
+ data["cyborgs"] = list()
data["safety"] = safety
- // Also applies for cyborgs. Hides the manual self-destruct button.
- data["is_ai"] = issilicon(user)
- data["allowed"] = is_authenticated(user)
+ for(var/mob/living/silicon/robot/R in GLOB.mob_list)
+ if(!console_shows(R))
+ continue
+ var/area/A = get_area(R)
+ var/turf/T = get_turf(R)
+ var/list/cyborg_data = list(
+ name = R.name,
+ uid = R.UID(),
+ locked_down = R.lockcharge,
+ locstring = "[A.name] ([T.x], [T.y])",
+ status = R.stat,
+ health = round(R.health * 100 / R.maxHealth, 0.1),
+ charge = R.cell ? round(R.cell.percent()) : null,
+ cell_capacity = R.cell ? R.cell.maxcharge : null,
+ module = R.module ? R.module.name : "No Module Detected",
+ synchronization = R.connected_ai,
+ is_hacked = R.connected_ai && R.emagged,
+ hackable = can_hack(user, R),
+ )
+ data["cyborgs"] += list(cyborg_data)
+ data["show_detonate_all"] = (data["auth"] && length(data["cyborgs"]) > 0 && ishuman(user))
return data
-/obj/machinery/computer/robotics/Topic(href, href_list)
+/obj/machinery/computer/robotics/tgui_act(action, params)
if(..())
- return 1
-
- var/mob/user = usr
- if(!is_authenticated(user))
- to_chat(user, "Access denied.")
return
-
- // Destroys the cyborg
- if(href_list["detonate"])
- var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["detonate"])
- if(!target || !istype(target))
- return
- if(isAI(user) && (target.connected_ai != user))
- to_chat(user, "Access Denied. This robot is not linked to you.")
- return
- // Cyborgs may blow up themselves via the console
- if((isrobot(user) && user != target) || !is_authenticated(user))
- to_chat(user, "Access Denied.")
- return
- var/choice = input("Really detonate [target.name]?") in list ("Yes", "No")
- if(choice != "Yes")
- return
- if(!target || !istype(target))
- return
-
- // Antagonistic cyborgs? Left here for downstream
- if(target.mind && target.mind.special_role && target.emagged)
- to_chat(target, "Extreme danger. Termination codes detected. Scrambling security codes and automatic AI unlink triggered.")
- target.ResetSecurityCodes()
- else
- message_admins("[key_name_admin(usr)] detonated [key_name_admin(target)] (JMP)!")
- log_game("\[key_name(usr)] detonated [key_name(target)]!")
- to_chat(target, "Self-destruct command received.")
- if(target.connected_ai)
- to_chat(target.connected_ai, "
ALERT - Cyborg detonation detected: [target.name]
")
- spawn(10)
- target.self_destruct()
-
- // Locks or unlocks the cyborg
- else if(href_list["lockdown"])
- var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["lockdown"])
- if(!target || !istype(target))
- return
-
- if(isAI(user) && (target.connected_ai != user))
- to_chat(user, "Access Denied. This robot is not linked to you.")
- return
-
- if(isrobot(user))
- to_chat(user, "Access Denied.")
- return
-
- var/choice = input("Really [target.lockcharge ? "unlock" : "lockdown"] [target.name] ?") in list ("Yes", "No")
- if(choice != "Yes")
- return
-
- if(!target || !istype(target))
- return
-
- message_admins("[key_name_admin(usr)] [target.canmove ? "locked down" : "released"] [key_name_admin(target)]!")
- log_game("[key_name(usr)] [target.canmove ? "locked down" : "released"] [key_name(target)]!")
- target.SetLockdown(!target.lockcharge)
- to_chat(target, "[!target.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]")
- if(target.connected_ai)
- to_chat(target.connected_ai, "[!target.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [target.name]
")
-
- // Remotely hacks the cyborg. Only antag AIs can do this and only to linked cyborgs.
- else if(href_list["hack"])
- var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["hack"])
- if(!target || !istype(target))
- return
-
- // Antag AI checks
- if(!istype(user, /mob/living/silicon/ai) || !(user.mind.special_role && user.mind.original == user))
- to_chat(user, "Access Denied.")
- return
-
- if(target.connected_ai != user)
- to_chat(user, "Access Denied. This robot is not linked to you.")
- return
-
- if(target.emagged)
- to_chat(user, "Robot is already hacked.")
- return
-
- var/choice = input("Really hack [target.name]? This cannot be undone.") in list("Yes", "No")
- if(choice != "Yes")
- return
-
- if(!target || !istype(target))
- return
-
- message_admins("[key_name_admin(usr)] emagged [key_name_admin(target)] using robotic console!")
- log_game("[key_name(usr)] emagged [key_name(target)] using robotic console!")
- target.emagged = 1
- to_chat(target, "Failsafe protocols overriden. New tools available.")
-
- // Arms the emergency self-destruct system
- else if(href_list["arm"])
- if(istype(user, /mob/living/silicon))
- to_chat(user, "Access Denied.")
- return
-
- safety = !safety
- to_chat(user, "You [safety ? "disarm" : "arm"] the emergency self destruct.")
-
- // Destroys all accessible cyborgs if safety is disabled
- else if(href_list["nuke"])
- if(istype(user, /mob/living/silicon))
- to_chat(user, "Access Denied")
- return
- if(safety)
- to_chat(user, "Self-destruct aborted - safety active")
- return
-
- message_admins("[key_name_admin(usr)] detonated all cyborgs!")
- log_game("\[key_name(usr)] detonated all cyborgs!")
-
- for(var/mob/living/silicon/robot/R in GLOB.mob_list)
- if(istype(R, /mob/living/silicon/robot/drone))
- continue
- // Ignore antagonistic cyborgs
- if(R.scrambledcodes)
- continue
+ . = FALSE
+ if(!is_authenticated(usr))
+ to_chat(usr, "Access denied.")
+ return
+ switch(action)
+ if("arm") // Arms the emergency self-destruct system
+ if(issilicon(usr))
+ to_chat(usr, "Access Denied (silicon detected)")
+ return
+ safety = !safety
+ to_chat(usr, "You [safety ? "disarm" : "arm"] the emergency self destruct.")
+ . = TRUE
+ if("nuke") // Destroys all accessible cyborgs if safety is disabled
+ if(issilicon(usr))
+ to_chat(usr, "Access Denied (silicon detected)")
+ return
+ if(safety)
+ to_chat(usr, "Self-destruct aborted - safety active")
+ return
+ message_admins("[key_name_admin(usr)] detonated all cyborgs!")
+ log_game("\[key_name(usr)] detonated all cyborgs!")
+ for(var/mob/living/silicon/robot/R in GLOB.mob_list)
+ if(istype(R, /mob/living/silicon/robot/drone))
+ continue
+ // Ignore antagonistic cyborgs
+ if(R.scrambledcodes)
+ continue
+ to_chat(R, "Self-destruct command received.")
+ if(R.connected_ai)
+ to_chat(R.connected_ai, "
ALERT - Cyborg detonation detected: [R.name]
")
+ R.self_destruct()
+ . = TRUE
+ if("killbot") // destroys one specific cyborg
+ var/mob/living/silicon/robot/R = locateUID(params["uid"])
+ if(!can_control(usr, R, TRUE))
+ return
+ if(R.mind && R.mind.special_role && R.emagged)
+ to_chat(R, "Extreme danger! Termination codes detected. Scrambling security codes and automatic AI unlink triggered.")
+ R.ResetSecurityCodes()
+ . = TRUE
+ return
+ var/turf/T = get_turf(R)
+ message_admins("[key_name_admin(usr)] detonated [key_name_admin(R)] ([ADMIN_COORDJMP(T)])!")
+ log_game("\[key_name(usr)] detonated [key_name(R)]!")
to_chat(R, "Self-destruct command received.")
if(R.connected_ai)
to_chat(R.connected_ai, "
ALERT - Cyborg detonation detected: [R.name]
")
- spawn(10)
- R.self_destruct()
-
-// Proc: get_cyborgs()
-// Parameters: 1 (operator - mob which is operating the console.)
-// Description: Returns NanoUI-friendly list of accessible cyborgs.
-/obj/machinery/computer/robotics/proc/get_cyborgs(var/mob/operator)
- var/list/robots = list()
-
- for(var/mob/living/silicon/robot/R in GLOB.mob_list)
- // Ignore drones
- if(istype(R, /mob/living/silicon/robot/drone))
- continue
- // Ignore antagonistic cyborgs
- if(R.scrambledcodes)
- continue
-
- var/list/robot = list()
- robot["name"] = R.name
- if(R.stat)
- robot["status"] = "Not Responding"
- else if(!R.canmove)
- robot["status"] = "Lockdown"
- else
- robot["status"] = "Operational"
-
- if(R.cell)
- robot["cell"] = 1
- robot["cell_capacity"] = R.cell.maxcharge
- robot["cell_current"] = R.cell.charge
- robot["cell_percentage"] = round(R.cell.percent())
- else
- robot["cell"] = 0
-
- var/turf/pos = get_turf(R)
- var/area/bot_area = get_area(R)
- robot["xpos"] = pos.x
- robot["ypos"] = pos.y
- robot["zpos"] = pos.z
- robot["area"] = format_text(bot_area.name)
-
- robot["health"] = round(R.health * 100 / R.maxHealth,0.1)
-
- robot["module"] = R.module ? R.module.name : "None"
- robot["master_ai"] = R.connected_ai ? R.connected_ai.name : "None"
- robot["hackable"] = 0
- // Antag AIs know whether linked cyborgs are hacked or not.
- if(operator && istype(operator, /mob/living/silicon/ai) && (R.connected_ai == operator) && (operator.mind.special_role && operator.mind.original == operator))
- robot["hacked"] = R.emagged ? 1 : 0
- robot["hackable"] = R.emagged? 0 : 1
- robots.Add(list(robot))
- return robots
-
-// Proc: get_cyborg_by_name()
-// Parameters: 1 (name - Cyborg we are trying to find)
-// Description: Helper proc for finding cyborg by name
-/obj/machinery/computer/robotics/proc/get_cyborg_by_name(var/name)
- if(!name)
- return
- for(var/mob/living/silicon/robot/R in GLOB.mob_list)
- if(R.name == name)
- return R
+ R.self_destruct()
+ . = TRUE
+ if("stopbot") // lock or unlock the borg
+ if(isrobot(usr))
+ to_chat(usr, "Access Denied.")
+ return
+ var/mob/living/silicon/robot/R = locateUID(params["uid"])
+ if(!can_control(usr, R, TRUE))
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!")
+ log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!")
+ R.SetLockdown(!R.lockcharge)
+ to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]")
+ if(R.connected_ai)
+ to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
")
+ . = TRUE
+ if("hackbot") // AIs hacking/emagging a borg
+ var/mob/living/silicon/robot/R = locateUID(params["uid"])
+ if(!can_hack(usr, R))
+ return
+ var/choice = input("Really hack [R.name]? This cannot be undone.") in list("Yes", "No")
+ if(choice != "Yes")
+ return
+ log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!")
+ message_admins("[key_name_admin(usr)] emagged [key_name_admin(R)] using robotic console!")
+ R.emagged = TRUE
+ to_chat(R, "Failsafe protocols overriden. New tools available.")
+ . = TRUE
diff --git a/nano/templates/robot_control.tmpl b/nano/templates/robot_control.tmpl
deleted file mode 100644
index 6e97ffaf3d7..00000000000
--- a/nano/templates/robot_control.tmpl
+++ /dev/null
@@ -1,100 +0,0 @@
-{{if data.robots}}
- {{if !data.is_ai}}
-