Merge pull request #14089 from Kyep/tgui_robotics_console

[TGUI] Robotics Control Console
This commit is contained in:
AffectedArc07
2020-09-15 19:25:59 +01:00
committed by GitHub
4 changed files with 339 additions and 307 deletions
+193 -204
View File
@@ -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, "<span class='warning'>AIs can only control cyborgs which are linked to them.</span>")
return FALSE
if(isrobot(user))
if(R != user)
if(telluserwhy)
to_chat(user, "<span class='warning'>Cyborgs cannot control other cyborgs.</span>")
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, "<span class='warning'>Access denied.</span>")
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, "<span class='warning'>Access Denied. This robot is not linked to you.</span>")
return
// Cyborgs may blow up themselves via the console
if((isrobot(user) && user != target) || !is_authenticated(user))
to_chat(user, "<span class='warning'>Access Denied.</span>")
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("<span class='notice'>[key_name_admin(usr)] detonated [key_name_admin(target)] (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[target.x];Y=[target.y];Z=[target.z]'>JMP</a>)!</span>")
log_game("\<span class='notice'>[key_name(usr)] detonated [key_name(target)]!</span>")
to_chat(target, "<span class='danger'>Self-destruct command received.</span>")
if(target.connected_ai)
to_chat(target.connected_ai, "<br><br><span class='alert'>ALERT - Cyborg detonation detected: [target.name]</span><br>")
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, "<span class='warning'>Access Denied. This robot is not linked to you.</span>")
return
if(isrobot(user))
to_chat(user, "<span class='warning'>Access Denied.</span>")
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("<span class='notice'>[key_name_admin(usr)] [target.canmove ? "locked down" : "released"] [key_name_admin(target)]!</span>")
log_game("[key_name(usr)] [target.canmove ? "locked down" : "released"] [key_name(target)]!")
target.SetLockdown(!target.lockcharge)
to_chat(target, "[!target.lockcharge ? "<span class='notice'>Your lockdown has been lifted!</span>" : "<span class='alert'>You have been locked down!</span>"]")
if(target.connected_ai)
to_chat(target.connected_ai, "[!target.lockcharge ? "<span class='notice'>NOTICE - Cyborg lockdown lifted</span>" : "<span class='alert'>ALERT - Cyborg lockdown detected</span>"]: <a href='?src=[target.connected_ai.UID()];track=[html_encode(target.name)]'>[target.name]</a></span><br>")
// 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, "<span class='warning'>Access Denied.</span>")
return
if(target.connected_ai != user)
to_chat(user, "<span class='warning'>Access Denied. This robot is not linked to you.</span>")
return
if(target.emagged)
to_chat(user, "<span class='warning'>Robot is already hacked.</span>")
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("<span class='notice'>[key_name_admin(usr)] emagged [key_name_admin(target)] using robotic console!</span>")
log_game("[key_name(usr)] emagged [key_name(target)] using robotic console!")
target.emagged = 1
to_chat(target, "<span class='notice'>Failsafe protocols overriden. New tools available.</span>")
// Arms the emergency self-destruct system
else if(href_list["arm"])
if(istype(user, /mob/living/silicon))
to_chat(user, "<span class='warning'>Access Denied.</span>")
return
safety = !safety
to_chat(user, "<span class='notice'>You [safety ? "disarm" : "arm"] the emergency self destruct.</span>")
// 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("<span class='notice'>[key_name_admin(usr)] detonated all cyborgs!</span>")
log_game("\<span class='notice'>[key_name(usr)] detonated all cyborgs!</span>")
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, "<span class='warning'>Access denied.</span>")
return
switch(action)
if("arm") // Arms the emergency self-destruct system
if(issilicon(usr))
to_chat(usr, "<span class='danger'>Access Denied (silicon detected)</span>")
return
safety = !safety
to_chat(usr, "<span class='notice'>You [safety ? "disarm" : "arm"] the emergency self destruct.</span>")
. = TRUE
if("nuke") // Destroys all accessible cyborgs if safety is disabled
if(issilicon(usr))
to_chat(usr, "<span class='danger'>Access Denied (silicon detected)</span>")
return
if(safety)
to_chat(usr, "<span class='danger'>Self-destruct aborted - safety active</span>")
return
message_admins("<span class='notice'>[key_name_admin(usr)] detonated all cyborgs!</span>")
log_game("\<span class='notice'>[key_name(usr)] detonated all cyborgs!</span>")
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, "<span class='danger'>Self-destruct command received.</span>")
if(R.connected_ai)
to_chat(R.connected_ai, "<br><br><span class='alert'>ALERT - Cyborg detonation detected: [R.name]</span><br>")
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, "<span class='userdanger'>Extreme danger! Termination codes detected. Scrambling security codes and automatic AI unlink triggered.</span>")
R.ResetSecurityCodes()
. = TRUE
return
var/turf/T = get_turf(R)
message_admins("<span class='notice'>[key_name_admin(usr)] detonated [key_name_admin(R)] ([ADMIN_COORDJMP(T)])!</span>")
log_game("\<span class='notice'>[key_name(usr)] detonated [key_name(R)]!</span>")
to_chat(R, "<span class='danger'>Self-destruct command received.</span>")
if(R.connected_ai)
to_chat(R.connected_ai, "<br><br><span class='alert'>ALERT - Cyborg detonation detected: [R.name]</span><br>")
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, "<span class='danger'>Access Denied.</span>")
return
var/mob/living/silicon/robot/R = locateUID(params["uid"])
if(!can_control(usr, R, TRUE))
return
message_admins("<span class='notice'>[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!</span>")
log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!")
R.SetLockdown(!R.lockcharge)
to_chat(R, "[!R.lockcharge ? "<span class='notice'>Your lockdown has been lifted!" : "<span class='alert'>You have been locked down!"]</span>")
if(R.connected_ai)
to_chat(R.connected_ai, "[!R.lockcharge ? "<span class='notice'>NOTICE - Cyborg lockdown lifted</span>" : "<span class='alert'>ALERT - Cyborg lockdown detected</span>"]: <a href='?src=[R.connected_ai.UID()];track=[html_encode(R.name)]'>[R.name]</a></span><br>")
. = 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("<span class='notice'>[key_name_admin(usr)] emagged [key_name_admin(R)] using robotic console!</span>")
R.emagged = TRUE
to_chat(R, "<span class='notice'>Failsafe protocols overriden. New tools available.</span>")
. = TRUE
-100
View File
@@ -1,100 +0,0 @@
{{if data.robots}}
{{if !data.is_ai}}
<div class='notice'>
<div class="itemContentSmall" style="width: 180px">
Emergency Self-Destruct:
</div>
<div class="itemContentFull">
{{if data.safety}}
{{:helper.link('ARM', 'unlock', {'arm' : 1}, !data.allowed ? 'disabled' : null)}}
{{:helper.link('DETONATE', 'exclamation-circle', {'nuke' : 1}, 'disabled')}}
{{else}}
{{:helper.link('DISARM', 'lock', {'arm' : 1}, !data.allowed ? 'disabled' : null)}}
{{:helper.link('DETONATE', 'exclamation-circle', {'nuke' : 1}, !data.allowed ? 'disabled' : null, 'redButton')}}
{{/if}}
</div>
<div class="clearBoth"></div>
</div>
{{/if}}
{{for data.robots}}
<hr>
<div class='item'>
<h2>{{:value.name}}</h2>
<h3>Information</h3>
<span class="itemLabel">
Status:
</span>
<span class="itemContent">
{{:value.status}}
</span>
<span class="itemLabel">
System Integrity:
</span>
<span class="itemContent">
{{:helper.smoothRound(value.health)}}%
</span>
<span class="itemLabel">
Location:
</span>
<span class="itemContent">
({{:value.xpos}}, {{:value.ypos}}, {{:value.zpos}}): {{:value.area}}
</span>
<span class="itemLabel">
Master AI:
</span>
<span class="itemContent">
{{:value.master_ai}}
</span>
<span class="itemLabel">
Module:
</span>
<span class="itemContent">
{{:value.module}}
</span>
{{if value.hackable}}
<span class="itemLabel">
Safeties:
</span>
<span class="itemContent">
ENABLED
</span>
{{else value.hacked}}
<span class="itemLabel">
Safeties:
</span>
<span class="itemContent">
DISABLED
</span>
{{/if}}
<h3>Power Cell</h3>
{{if value.cell}}
<span class="itemLabel">
Rating:
</span>
<span class="itemContent">
{{:value.cell_capacity}}
</span>
{{:helper.displayBar(value.cell_percentage, 0, 100, (value.cell_percentage >= 50) ? 'good' : (value.cell_percentage >= 25) ? 'average' : 'bad')}}
<b> {{:helper.smoothRound(value.cell_percentage)}}%</b>
{{else}}
<b><i>Not Installed</i></b>
{{:helper.displayBar(100, 0, 100, 'bad')}}
{{/if}}
<h3>Actions</h3>
{{if value.status == "Operational"}}
{{:helper.link('Lockdown', 'lock', {'lockdown' : value.name}, !data.allowed ? 'disabled' : null)}}
{{else}}
{{:helper.link('Unlock', 'unlock', {'lockdown' : value.name}, !data.allowed ? 'disabled' : null)}}
{{/if}}
{{:helper.link('Self-Destruct', 'exclamation-circle', {'detonate' : value.name}, !data.allowed ? 'disabled' : null, 'redButton')}}
{{if value.hackable}}
{{:helper.link('Hack', 'calculator', {'hack' : value.name}, !data.allowed ? 'disabled' : null, 'redButton')}}
{{/if}}
</div>
{{/for}}
{{else}}
<div class='item'>
<h3>No robots were found.</h3>
</div>
{{/if}}
@@ -0,0 +1,143 @@
import { Fragment } from 'inferno';
import { useBackend, useSharedState } from '../backend';
import { Box, Button, LabeledList, ProgressBar, NoticeBox, Section, Tabs } from '../components';
import { Window } from '../layouts';
export const RoboticsControlConsole = (props, context) => {
const { act, data } = useBackend(context);
const {
can_hack,
safety,
show_detonate_all,
cyborgs = [],
} = data;
return (
<Window resizable>
<Window.Content scrollable>
{!!show_detonate_all && (
<Section title="Emergency Self Destruct">
<Button
icon={safety ? 'lock' : 'unlock'}
content={safety ? 'Disable Safety' : 'Enable Safety'}
selected={safety}
onClick={() => act('arm', {})} />
<Button
icon="bomb"
disabled={safety}
content="Destroy ALL Cyborgs"
color="bad"
onClick={() => act('nuke', {})} />
</Section>
)}
<Cyborgs cyborgs={cyborgs} can_hack={can_hack} />
</Window.Content>
</Window>
);
};
const Cyborgs = (props, context) => {
const { cyborgs, can_hack } = props;
const { act, data } = useBackend(context);
if (!cyborgs.length) {
return (
<NoticeBox>
No cyborg units detected within access parameters.
</NoticeBox>
);
}
return cyborgs.map(cyborg => {
return (
<Section
key={cyborg.uid}
title={cyborg.name}
buttons={(
<Fragment>
{!!cyborg.hackable && !cyborg.emagged && (
<Button
icon="terminal"
content="Hack"
color="bad"
onClick={() => act('hackbot', {
uid: cyborg.uid,
})} />
)}
<Button.Confirm
icon={cyborg.locked_down ? 'unlock' : 'lock'}
color={cyborg.locked_down ? 'good' : 'default'}
content={cyborg.locked_down ? "Release" : "Lockdown"}
disabled={!data.auth}
onClick={() => act('stopbot', {
uid: cyborg.uid,
})} />
<Button.Confirm
icon="bomb"
content="Detonate"
disabled={!data.auth}
color="bad"
onClick={() => act('killbot', {
uid: cyborg.uid,
})} />
</Fragment>
)}>
<LabeledList>
<LabeledList.Item label="Status">
<Box color={cyborg.status
? 'bad'
: cyborg.locked_down
? 'average'
: 'good'}>
{cyborg.status
? "Not Responding"
: cyborg.locked_down
? "Locked Down"
: "Nominal"}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Location">
<Box>
{cyborg.locstring}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Integrity">
<ProgressBar
color={cyborg.health > 50 ? "good" : "bad"}
value={cyborg.health / 100} />
</LabeledList.Item>
{(typeof cyborg.charge === 'number') && (
<Fragment>
<LabeledList.Item label="Cell Charge">
<ProgressBar
color={cyborg.charge > 30 ? "good" : "bad"}
value={cyborg.charge / 100} />
</LabeledList.Item>
<LabeledList.Item label="Cell Capacity">
<Box color={cyborg.cell_capacity < 30000 ? "average" : "good"}>
{cyborg.cell_capacity}
</Box>
</LabeledList.Item>
</Fragment>
) || (
<LabeledList.Item label="Cell">
<Box color="bad">
No Power Cell
</Box>
</LabeledList.Item>
)}
{!!cyborg.is_hacked && (
<LabeledList.Item label="Safeties">
<Box color="bad">DISABLED</Box>
</LabeledList.Item>
)}
<LabeledList.Item label="Module">
{cyborg.module}
</LabeledList.Item>
<LabeledList.Item label="Master AI">
<Box color={cyborg.synchronization ? 'default' : 'average'}>
{cyborg.synchronization || "None"}
</Box>
</LabeledList.Item>
</LabeledList>
</Section>
);
});
};
File diff suppressed because one or more lines are too long