[MIRROR] TGUI Communications console (#1286)

* TGUI Communications console

* fix

Co-authored-by: Jared-Fogle <35135081+Jared-Fogle@users.noreply.github.com>
Co-authored-by: Gandalf <jzo123@hotmail.com>
This commit is contained in:
SkyratBot
2020-10-14 18:45:53 +02:00
committed by GitHub
parent d00d567c2c
commit bcb5fbc83c
10 changed files with 1127 additions and 721 deletions
+13 -16
View File
@@ -187,31 +187,26 @@ SUBSYSTEM_DEF(shuttle)
return S
WARNING("couldn't find dock with id: [id]")
/// Check if we can call the evac shuttle.
/// Returns TRUE if we can. Otherwise, returns a string detailing the problem.
/datum/controller/subsystem/shuttle/proc/canEvac(mob/user)
var/srd = CONFIG_GET(number/shuttle_refuel_delay)
if(world.time - SSticker.round_start_time < srd)
to_chat(user, "<span class='alert'>The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.</span>")
return FALSE
return "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before attempting to call."
switch(emergency.mode)
if(SHUTTLE_RECALL)
to_chat(user, "<span class='alert'>The emergency shuttle may not be called while returning to CentCom.</span>")
return FALSE
return "The emergency shuttle may not be called while returning to CentCom."
if(SHUTTLE_CALL)
to_chat(user, "<span class='alert'>The emergency shuttle is already on its way.</span>")
return FALSE
return "The emergency shuttle is already on its way."
if(SHUTTLE_DOCKED)
to_chat(user, "<span class='alert'>The emergency shuttle is already here.</span>")
return FALSE
return "The emergency shuttle is already here."
if(SHUTTLE_IGNITING)
to_chat(user, "<span class='alert'>The emergency shuttle is firing its engines to leave.</span>")
return FALSE
return "The emergency shuttle is firing its engines to leave."
if(SHUTTLE_ESCAPE)
to_chat(user, "<span class='alert'>The emergency shuttle is moving away to a safe distance.</span>")
return FALSE
if(SHUTTLE_STRANDED, SHUTTLE_DISABLED)
to_chat(user, "<span class='alert'>The emergency shuttle has been disabled by CentCom.</span>")
return FALSE
return "The emergency shuttle is moving away to a safe distance."
if(SHUTTLE_STRANDED)
return "The emergency shuttle has been disabled by CentCom."
return TRUE
@@ -229,7 +224,9 @@ SUBSYSTEM_DEF(shuttle)
Good luck.")
emergency = backup_shuttle
if(!canEvac(user))
var/can_evac_or_fail_reason = SSshuttle.canEvac(user)
if(can_evac_or_fail_reason != TRUE)
to_chat(user, "<span class='alert'>[can_evac_or_fail_reason]</span>")
return
call_reason = trim(html_encode(call_reason))
+1 -1
View File
@@ -99,7 +99,7 @@
minor_announce(input["message"], "Incoming message from [input["message_sender"]]")
for(var/obj/machinery/computer/communications/CM in GLOB.machines)
CM.overrideCooldown()
CM.override_cooldown()
/datum/world_topic/news_report
keyword = "News_Report"
File diff suppressed because it is too large Load Diff
@@ -187,7 +187,6 @@
name = "Communications (Computer Board)"
icon_state = "engineering"
build_path = /obj/machinery/computer/communications
var/lastTimeUsed = 0
/obj/item/circuitboard/computer/message_monitor
name = "Message Monitor (Computer Board)"
+17 -14
View File
@@ -38,10 +38,10 @@
cross.icon_state = "holylight"
font_color = "blue"
prayer_type = "SPIRITUAL PRAYER"
var/msg_tmp = msg
msg = "<span class='adminnotice'>[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> <span class='linkify'>[msg]</span></span>"
for(var/client/C in GLOB.admins)
if(C.prefs.chat_toggles & CHAT_PRAYER)
to_chat(C, msg, confidential = TRUE)
@@ -53,23 +53,26 @@
SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//log_admin("HELP: [key_name(src)]: [msg]")
/proc/CentCom_announce(text , mob/Sender)
/// Used by communications consoles to message CentCom
/proc/message_centcom(text, mob/sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>CENTCOM:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]:</b> [msg]</span>"
msg = "<span class='adminnotice'><b><font color=orange>CENTCOM:</font>[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg, confidential = TRUE)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
for(var/obj/machinery/computer/communications/console in GLOB.machines)
console.override_cooldown()
/proc/Syndicate_announce(text , mob/Sender)
/// Used by communications consoles to message the Syndicate
/proc/message_syndicate(text, mob/sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=crimson>SYNDICATE:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]:</b> [msg]</span>"
msg = "<span class='adminnotice'><b><font color=crimson>SYNDICATE:</font>[ADMIN_FULLMONTY(sender)] [ADMIN_SYNDICATE_REPLY(sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg, confidential = TRUE)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
for(var/obj/machinery/computer/communications/console in GLOB.machines)
console.override_cooldown()
/proc/Nuke_request(text , mob/Sender)
/// Used by communications consoles to request the nuclear launch codes
/proc/nuke_request(text, mob/sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>NUKE CODE REQUEST:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]:</b> [msg]</span>"
msg = "<span class='adminnotice'><b><font color=orange>NUKE CODE REQUEST:</font>[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)] [ADMIN_SET_SD_CODE]:</b> [msg]</span>"
to_chat(GLOB.admins, msg, confidential = TRUE)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
for(var/obj/machinery/computer/communications/console in GLOB.machines)
console.override_cooldown()
+3 -1
View File
@@ -313,7 +313,9 @@
to_chat(usr, "<span class='warning'>Wireless control is disabled!</span>")
return
if(!SSshuttle.canEvac(src))
var/can_evac_or_fail_reason = SSshuttle.canEvac(src)
if(can_evac_or_fail_reason != TRUE)
to_chat(usr, "<span class='alert'>[can_evac_or_fail_reason]</span>")
return
var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text
@@ -0,0 +1,667 @@
import { sortBy } from "common/collections";
import { capitalize } from "common/string";
import { useBackend, useLocalState } from "../backend";
import { Box, Button, Flex, Input, Modal, Section, Table, TextArea } from "../components";
import { Window } from "../layouts";
const STATE_BUYING_SHUTTLE = "buying_shuttle";
const STATE_CHANGING_STATUS = "changing_status";
const STATE_MAIN = "main";
const STATE_MESSAGES = "messages";
// Used for whether or not you need to swipe to confirm an alert level change
const SWIPE_NEEDED = "SWIPE_NEEDED";
const sortByCreditCost = sortBy(shuttle => shuttle.creditCost);
const AlertButton = (props, context) => {
const { act, data } = useBackend(context);
const { alertLevelTick, canSetAlertLevel } = data;
const { alertLevel, setShowAlertLevelConfirm } = props;
const thisIsCurrent = data.alertLevel === alertLevel;
return (
<Button
icon="exclamation-triangle"
color={thisIsCurrent && "good"}
content={capitalize(alertLevel)}
onClick={() => {
if (thisIsCurrent) {
return;
}
if (canSetAlertLevel === SWIPE_NEEDED) {
setShowAlertLevelConfirm([alertLevel, alertLevelTick]);
} else {
act("changeSecurityLevel", {
newSecurityLevel: alertLevel,
});
}
}}
/>
);
};
const MessageModal = (props, context) => {
const { data } = useBackend(context);
const { maxMessageLength } = data;
const [input, setInput] = useLocalState(context, props.label, "");
const longEnough = props.minLength === undefined
|| input.length >= props.minLength;
return (
<Modal>
<Flex direction="column">
<Flex.Item fontSize="16px" maxWidth="90vw" mb={1}>
{props.label}:
</Flex.Item>
<Flex.Item mr={2} mb={1}>
<TextArea
fluid
height="20vh"
width="80vw"
backgroundColor="black"
textColor="white"
onInput={(_, value) => {
setInput(value.substring(0, maxMessageLength));
}}
value={input}
/>
</Flex.Item>
<Flex.Item>
<Button
icon={props.icon}
content={props.buttonText}
color="good"
disabled={!longEnough}
tooltip={!longEnough ? "You need a longer reason." : ""}
tooltipPosition="right"
onClick={() => {
if (longEnough) {
setInput("");
props.onSubmit(input);
}
}}
/>
<Button
icon="times"
content="Cancel"
color="bad"
onClick={props.onBack}
/>
</Flex.Item>
{!!props.notice && (
<Flex.Item maxWidth="90vw">{props.notice}</Flex.Item>
)}
</Flex>
</Modal>
);
};
const PageBuyingShuttle = (props, context) => {
const { act, data } = useBackend(context);
return (
<Box>
<Section>
<Button
icon="chevron-left"
content="Back"
onClick={() => act("setState", { state: STATE_MAIN })}
/>
</Section>
<Section>
Budget: <b>{data.budget.toLocaleString()}</b> credits
</Section>
{sortByCreditCost(data.shuttles).map(shuttle => (
<Section
title={(
<span
style={{
display: "inline-block",
width: "70%",
}}>
{shuttle.name}
</span>
)}
key={shuttle.ref}
buttons={(
<Button
content={`${shuttle.creditCost.toLocaleString()} credits`}
disabled={data.budget < shuttle.creditCost}
onClick={() => act("purchaseShuttle", {
shuttle: shuttle.ref,
})}
tooltip={
data.budget < shuttle.creditCost
? `You need ${shuttle.creditCost - data.budget} more credits.`
: undefined
}
tooltipPosition="left"
/>
)}>
<Box>{shuttle.description}</Box>
{
shuttle.prerequisites
? <b>Prerequisites: {shuttle.prerequisites}</b>
: null
}
</Section>
))}
</Box>
);
};
const PageChangingStatus = (props, context) => {
const { act, data } = useBackend(context);
const { maxStatusLineLength } = data;
const [lineOne, setLineOne] = useLocalState(context, "lineOne", data.lineOne);
const [lineTwo, setLineTwo] = useLocalState(context, "lineTwo", data.lineTwo);
return (
<Box>
<Section>
<Button
icon="chevron-left"
content="Back"
onClick={() => act("setState", { state: STATE_MAIN })}
/>
</Section>
<Section>
<Flex direction="column">
<Flex.Item>
<Button
icon="times"
content="Clear Alert"
color="bad"
onClick={() => act("setStatusPicture", { picture: "blank" })}
/>
</Flex.Item>
<Flex.Item mt={1}>
<Button
icon="check-square-o"
content="Default"
onClick={() => act("setStatusPicture", { picture: "default" })}
/>
<Button
icon="bell-o"
content="Red Alert"
onClick={() => act("setStatusPicture", { picture: "redalert" })}
/>
<Button
icon="exclamation-triangle"
content="Lockdown"
onClick={() => act("setStatusPicture", { picture: "lockdown" })}
/>
<Button
icon="exclamation-circle"
content="Biohazard"
onClick={() => act("setStatusPicture", { picture: "biohazard" })}
/>
<Button
icon="space-shuttle"
content="Shuttle ETA"
onClick={() => act("setStatusPicture", { picture: "shuttle" })}
/>
</Flex.Item>
</Flex>
</Section>
<Section title="Message">
<Flex direction="column">
<Flex.Item mb={1}>
<Input
maxLength={maxStatusLineLength}
value={lineOne}
width="200px"
onChange={(_, value) => setLineOne(value)}
/>
</Flex.Item>
<Flex.Item mb={1}>
<Input
maxLength={maxStatusLineLength}
value={lineTwo}
width="200px"
onChange={(_, value) => setLineTwo(value)}
/>
</Flex.Item>
<Flex.Item>
<Button
icon="comment-o"
content="Message"
onClick={() => act("setStatusMessage", {
lineOne,
lineTwo,
})}
/>
</Flex.Item>
</Flex>
</Section>
</Box>
);
};
const PageMain = (props, context) => {
const { act, data } = useBackend(context);
const {
alertLevel,
alertLevelTick,
callShuttleReasonMinLength,
canBuyShuttles,
canMakeAnnouncement,
canMessageAssociates,
canRequestNuke,
canSendToSectors,
canSetAlertLevel,
canToggleEmergencyAccess,
emagged,
emergencyAccess,
importantActionReady,
sectors,
shuttleCalled,
shuttleCalledPreviously,
shuttleCanEvacOrFailReason,
shuttleLastCalled,
shuttleRecallable,
} = data;
const [callingShuttle, setCallingShuttle] = useLocalState(
context, "calling_shuttle", false);
const [messagingAssociates, setMessagingAssociates] = useLocalState(
context, "messaging_associates", false);
const [messagingSector, setMessagingSector] = useLocalState(
context, "messaing_sector", null);
const [requestingNukeCodes, setRequestingNukeCodes] = useLocalState(
context, "requesting_nuke_codes", false);
const [
[showAlertLevelConfirm, confirmingAlertLevelTick],
setShowAlertLevelConfirm,
] = useLocalState(context, "showConfirmPrompt", [null, null]);
return (
<Box>
<Section title="Emergency Shuttle">
{
shuttleCalled
? <Button.Confirm
icon="space-shuttle"
content="Recall Emergency Shuttle"
color="bad"
disabled={!shuttleRecallable}
tooltip={!shuttleRecallable && "It's too late for the emergency shuttle to be recalled."}
tooltipPosition="bottom-right"
onClick={() => act("recallShuttle")}
/>
: <Button
icon="space-shuttle"
content="Call Emergency Shuttle"
disabled={shuttleCanEvacOrFailReason !== 1}
tooltip={
shuttleCanEvacOrFailReason !== 1
? shuttleCanEvacOrFailReason
: undefined
}
tooltipPosition="bottom-right"
onClick={() => setCallingShuttle(true)}
/>
}
{!!shuttleCalledPreviously && (
shuttleLastCalled && (
<Box>
Most recent shuttle call/recall traced to:
{" "}<b>{shuttleLastCalled}</b>
</Box>
) || (
<Box>Unable to trace most recent shuttle/recall signal.</Box>
)
)}
</Section>
{!!canSetAlertLevel && (
<Section title="Alert Level">
<Flex justify="space-between">
<Flex.Item>
<Box>
Currently on <b>{capitalize(alertLevel)}</b> Alert
</Box>
</Flex.Item>
<Flex.Item>
<AlertButton
alertLevel="green"
showAlertLevelConfirm={showAlertLevelConfirm}
setShowAlertLevelConfirm={setShowAlertLevelConfirm}
/>
<AlertButton
alertLevel="blue"
showAlertLevelConfirm={showAlertLevelConfirm}
setShowAlertLevelConfirm={setShowAlertLevelConfirm}
/>
</Flex.Item>
</Flex>
</Section>
)}
<Section title="Functions">
<Flex
direction="column">
{!!canMakeAnnouncement && <Button
icon="bullhorn"
content="Make Priority Announcement"
onClick={() => act("makePriorityAnnouncement")}
/>}
{!!canToggleEmergencyAccess && <Button.Confirm
icon="id-card-o"
content={`${emergencyAccess ? "Disable" : "Enable"} Emergency Maintenance Access`}
color={emergencyAccess ? "bad" : undefined}
onClick={() => act("toggleEmergencyAccess")}
/>}
<Button
icon="desktop"
content="Set Status Display"
onClick={() => act("setState", { state: STATE_CHANGING_STATUS })}
/>
<Button
icon="envelope-o"
content="Message List"
onClick={() => act("setState", { state: STATE_MESSAGES })}
/>
{(canBuyShuttles !== 0) && <Button
icon="shopping-cart"
content="Purchase Shuttle"
disabled={canBuyShuttles !== 1}
// canBuyShuttles is a string detailing the fail reason
// if one can be given
tooltip={canBuyShuttles !== 1 ? canBuyShuttles : undefined}
tooltipPosition="right"
onClick={() => act("setState", { state: STATE_BUYING_SHUTTLE })}
/>}
{!!canMessageAssociates && <Button
icon="comment-o"
content={`Send message to ${emagged ? "[UNKNOWN]" : "CentCom"}`}
disabled={!importantActionReady}
onClick={() => setMessagingAssociates(true)}
/>}
{!!canRequestNuke && <Button
icon="radiation"
content="Request Nuclear Authentication Codes"
disabled={!importantActionReady}
onClick={() => setRequestingNukeCodes(true)}
/>}
{!!emagged && <Button
icon="undo"
content="Restore Backup Routing Data"
onClick={() => act("restoreBackupRoutingData")}
/>}
</Flex>
</Section>
{!!canMessageAssociates && messagingAssociates && <MessageModal
label={`Message to transmit to ${emagged ? "[ABNORMAL ROUTING COORDINATES]" : "CentCom"} via quantum entanglement`}
notice="Please be aware that this process is very expensive, and abuse will lead to...termination. Transmission does not guarantee a response."
icon="bullhorn"
buttonText="Send"
onBack={() => setMessagingAssociates(false)}
onSubmit={message => {
setMessagingAssociates(false);
act("messageAssociates", {
message,
});
}}
/>}
{!!canRequestNuke && requestingNukeCodes && <MessageModal
label="Reason for requesting nuclear self-destruct codes"
notice="Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response."
icon="bomb"
buttonText="Request Codes"
onBack={() => setRequestingNukeCodes(false)}
onSubmit={reason => {
setRequestingNukeCodes(false);
act("requestNukeCodes", {
reason,
});
}}
/>}
{!!callingShuttle && <MessageModal
label="Nature of emergency"
icon="space-shuttle"
buttonText="Call Shuttle"
minLength={callShuttleReasonMinLength}
onBack={() => setCallingShuttle(false)}
onSubmit={reason => {
setCallingShuttle(false);
act("callShuttle", {
reason,
});
}}
/>}
{
!!canSetAlertLevel
&& showAlertLevelConfirm
&& confirmingAlertLevelTick === alertLevelTick
&& (
<Modal>
<Flex
direction="column"
textAlign="center"
width="300px">
<Flex.Item fontSize="16px" mb={2}>
Swipe ID to confirm change
</Flex.Item>
<Flex.Item mr={2} mb={1}>
<Button
icon="id-card-o"
content="Swipe ID"
color="good"
fontSize="16px"
onClick={() => act("changeSecurityLevel", {
newSecurityLevel: showAlertLevelConfirm,
})}
/>
<Button
icon="times"
content="Cancel"
color="bad"
fontSize="16px"
onClick={() => setShowAlertLevelConfirm(false)}
/>
</Flex.Item>
</Flex>
</Modal>
)
}
{
!!canSendToSectors
&& sectors.length > 0
&& (
<Section title="Allied Sectors">
<Flex>
{
sectors.map(sectorName => (
<Button
content={
`Send a message to station in ${sectorName} sector`
}
disabled={!importantActionReady}
key={sectorName}
onClick={() => setMessagingSector(sectorName)}
/>
))
}
{sectors.length > 2 && (
<Button
content="Send a message to all allied stations"
disabled={!importantActionReady}
onClick={() => setMessagingSector("all")}
/>
)}
</Flex>
</Section>
)
}
{
!!canSendToSectors
&& sectors.length > 0
&& messagingSector
&& <MessageModal
label="Message to send to allied station"
notice="Please be aware that this process is very expensive, and abuse will lead to...termination."
icon="bullhorn"
buttonText="Send"
onBack={() => setMessagingSector(null)}
onSubmit={message => {
act("sendToOtherSector", {
destination: messagingSector,
message,
});
setMessagingSector(null);
}}
/>
}
</Box>
);
};
const PageMessages = (props, context) => {
const { act, data } = useBackend(context);
const messages = data.messages || [];
const children = [];
children.push((
<Section>
<Button
icon="chevron-left"
content="Back"
onClick={() => act("setState", { state: STATE_MAIN })}
/>
</Section>
));
const messageElements = [];
for (const [messageIndex, message] of Object.entries(messages)) {
let answers = null;
if (message.possibleAnswers.length > 0) {
answers = (
<Box mt={1}>
{message.possibleAnswers.map((answer, answerIndex) => (
<Button
content={answer}
color={message.answered === answerIndex + 1 ? "good" : undefined}
key={answerIndex}
onClick={message.answered ? undefined : () => act("answerMessage", {
message: messageIndex + 1,
answer: answerIndex + 1,
})}
/>
))}
</Box>
);
}
messageElements.push((
<Section
title={message.title}
key={messageIndex}
buttons={(
<Button.Confirm
icon="trash"
content="Delete"
color="red"
onClick={() => act("deleteMessage", {
message: messageIndex + 1,
})}
/>
)}>
<Box>{message.content}</Box>
{answers}
</Section>
));
}
children.push(messageElements.reverse());
return children;
};
export const CommunicationsConsole = (props, context) => {
const { act, data } = useBackend(context);
const {
authenticated,
authorizeName,
canLogOut,
emagged,
page,
} = data;
return (
<Window
width={400}
height={650}
theme={emagged ? "syndicate" : undefined}
resizable>
<Window.Content scrollable>
{(canLogOut || !authenticated)
? (
<Section title="Authentication">
<Button
icon={authenticated ? "sign-out-alt" : "sign-in-alt"}
content={authenticated ? `Log Out${authorizeName ? ` (${authorizeName})` : ""}` : "Log In"}
color={authenticated ? "bad" : "good"}
onClick={() => act("toggleAuthentication")}
/>
</Section>
)
: null}
{!!authenticated && (
page === STATE_BUYING_SHUTTLE && <PageBuyingShuttle />
|| page === STATE_CHANGING_STATUS && <PageChangingStatus />
|| page === STATE_MAIN && <PageMain />
|| page === STATE_MESSAGES && <PageMessages />
|| <Box>Page not implemented: {page}</Box>
)}
</Window.Content>
</Window>
);
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long